Path: blob/master/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c
26304 views
// SPDX-License-Identifier: GPL-2.0-or-later1// Copyright (c) 2025 Miklos Szeredi <[email protected]>23#define _GNU_SOURCE4#include <fcntl.h>5#include <sched.h>6#include <stdio.h>7#include <string.h>8#include <sys/stat.h>9#include <sys/mount.h>10#include <unistd.h>11#include <sys/syscall.h>1213#include "../../kselftest_harness.h"14#include "../statmount/statmount.h"15#include "../utils.h"1617// Needed for linux/fanotify.h18#ifndef __kernel_fsid_t19typedef struct {20int val[2];21} __kernel_fsid_t;22#endif2324#include <sys/fanotify.h>2526static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";2728static const int mark_cmds[] = {29FAN_MARK_ADD,30FAN_MARK_REMOVE,31FAN_MARK_FLUSH32};3334#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)3536FIXTURE(fanotify) {37int fan_fd[NUM_FAN_FDS];38char buf[256];39unsigned int rem;40void *next;41char root_mntpoint[sizeof(root_mntpoint_templ)];42int orig_root;43int ns_fd;44uint64_t root_id;45};4647FIXTURE_SETUP(fanotify)48{49int i, ret;5051ASSERT_EQ(unshare(CLONE_NEWNS), 0);5253self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);54ASSERT_GE(self->ns_fd, 0);5556ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0);5758strcpy(self->root_mntpoint, root_mntpoint_templ);59ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);6061self->orig_root = open("/", O_PATH | O_CLOEXEC);62ASSERT_GE(self->orig_root, 0);6364ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);6566ASSERT_EQ(chroot(self->root_mntpoint), 0);6768ASSERT_EQ(chdir("/"), 0);6970ASSERT_EQ(mkdir("a", 0700), 0);7172ASSERT_EQ(mkdir("b", 0700), 0);7374self->root_id = get_unique_mnt_id("/");75ASSERT_NE(self->root_id, 0);7677for (i = 0; i < NUM_FAN_FDS; i++) {78self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,790);80ASSERT_GE(self->fan_fd[i], 0);81ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |82FAN_MARK_MNTNS,83FAN_MNT_ATTACH | FAN_MNT_DETACH,84self->ns_fd, NULL);85ASSERT_EQ(ret, 0);86// On fd[0] we do an extra ADD that changes nothing.87// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.88ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |89FAN_MARK_MNTNS,90FAN_MNT_ATTACH | FAN_MNT_DETACH,91self->ns_fd, NULL);92ASSERT_EQ(ret, 0);93}9495self->rem = 0;96}9798FIXTURE_TEARDOWN(fanotify)99{100int i;101102ASSERT_EQ(self->rem, 0);103for (i = 0; i < NUM_FAN_FDS; i++)104close(self->fan_fd[i]);105106ASSERT_EQ(fchdir(self->orig_root), 0);107108ASSERT_EQ(chroot("."), 0);109110EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);111EXPECT_EQ(chdir(self->root_mntpoint), 0);112EXPECT_EQ(chdir("/"), 0);113EXPECT_EQ(rmdir(self->root_mntpoint), 0);114}115116static uint64_t expect_notify(struct __test_metadata *const _metadata,117FIXTURE_DATA(fanotify) *self,118uint64_t *mask)119{120struct fanotify_event_metadata *meta;121struct fanotify_event_info_mnt *mnt;122unsigned int thislen;123124if (!self->rem) {125ssize_t len;126int i;127128for (i = NUM_FAN_FDS - 1; i >= 0; i--) {129len = read(self->fan_fd[i], self->buf,130sizeof(self->buf));131if (i > 0) {132// Groups 1,2 should get EAGAIN133ASSERT_EQ(len, -1);134ASSERT_EQ(errno, EAGAIN);135} else {136// Group 0 should get events137ASSERT_GT(len, 0);138}139}140141self->rem = len;142self->next = (void *) self->buf;143}144145meta = self->next;146ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));147148thislen = meta->event_len;149self->rem -= thislen;150self->next += thislen;151152*mask = meta->mask;153thislen -= sizeof(*meta);154155mnt = ((void *) meta) + meta->event_len - thislen;156157ASSERT_EQ(thislen, sizeof(*mnt));158159return mnt->mnt_id;160}161162static void expect_notify_n(struct __test_metadata *const _metadata,163FIXTURE_DATA(fanotify) *self,164unsigned int n, uint64_t mask[], uint64_t mnts[])165{166unsigned int i;167168for (i = 0; i < n; i++)169mnts[i] = expect_notify(_metadata, self, &mask[i]);170}171172static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,173FIXTURE_DATA(fanotify) *self,174uint64_t expect_mask)175{176uint64_t mntid, mask;177178mntid = expect_notify(_metadata, self, &mask);179ASSERT_EQ(expect_mask, mask);180181return mntid;182}183184185static void expect_notify_mask_n(struct __test_metadata *const _metadata,186FIXTURE_DATA(fanotify) *self,187uint64_t mask, unsigned int n, uint64_t mnts[])188{189unsigned int i;190191for (i = 0; i < n; i++)192mnts[i] = expect_notify_mask(_metadata, self, mask);193}194195static void verify_mount_ids(struct __test_metadata *const _metadata,196const uint64_t list1[], const uint64_t list2[],197size_t num)198{199unsigned int i, j;200201// Check that neither list has any duplicates202for (i = 0; i < num; i++) {203for (j = 0; j < num; j++) {204if (i != j) {205ASSERT_NE(list1[i], list1[j]);206ASSERT_NE(list2[i], list2[j]);207}208}209}210// Check that all list1 memebers can be found in list2. Together with211// the above it means that the list1 and list2 represent the same sets.212for (i = 0; i < num; i++) {213for (j = 0; j < num; j++) {214if (list1[i] == list2[j])215break;216}217ASSERT_NE(j, num);218}219}220221static void check_mounted(struct __test_metadata *const _metadata,222const uint64_t mnts[], size_t num)223{224ssize_t ret;225uint64_t *list;226227list = malloc((num + 1) * sizeof(list[0]));228ASSERT_NE(list, NULL);229230ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);231ASSERT_EQ(ret, num);232233verify_mount_ids(_metadata, mnts, list, num);234235free(list);236}237238static void setup_mount_tree(struct __test_metadata *const _metadata,239int log2_num)240{241int ret, i;242243ret = mount("", "/", NULL, MS_SHARED, NULL);244ASSERT_EQ(ret, 0);245246for (i = 0; i < log2_num; i++) {247ret = mount("/", "/", NULL, MS_BIND, NULL);248ASSERT_EQ(ret, 0);249}250}251252TEST_F(fanotify, bind)253{254int ret;255uint64_t mnts[2] = { self->root_id };256257ret = mount("/", "/", NULL, MS_BIND, NULL);258ASSERT_EQ(ret, 0);259260mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);261ASSERT_NE(mnts[0], mnts[1]);262263check_mounted(_metadata, mnts, 2);264265// Cleanup266uint64_t detach_id;267ret = umount("/");268ASSERT_EQ(ret, 0);269270detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);271ASSERT_EQ(detach_id, mnts[1]);272273check_mounted(_metadata, mnts, 1);274}275276TEST_F(fanotify, move)277{278int ret;279uint64_t mnts[2] = { self->root_id };280uint64_t move_id;281282ret = mount("/", "/a", NULL, MS_BIND, NULL);283ASSERT_EQ(ret, 0);284285mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);286ASSERT_NE(mnts[0], mnts[1]);287288check_mounted(_metadata, mnts, 2);289290ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);291ASSERT_EQ(ret, 0);292293move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);294ASSERT_EQ(move_id, mnts[1]);295296// Cleanup297ret = umount("/b");298ASSERT_EQ(ret, 0);299300check_mounted(_metadata, mnts, 1);301}302303TEST_F(fanotify, propagate)304{305const unsigned int log2_num = 4;306const unsigned int num = (1 << log2_num);307uint64_t mnts[num];308309setup_mount_tree(_metadata, log2_num);310311expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);312313mnts[0] = self->root_id;314check_mounted(_metadata, mnts, num);315316// Cleanup317int ret;318uint64_t mnts2[num];319ret = umount2("/", MNT_DETACH);320ASSERT_EQ(ret, 0);321322ret = mount("", "/", NULL, MS_PRIVATE, NULL);323ASSERT_EQ(ret, 0);324325mnts2[0] = self->root_id;326expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);327verify_mount_ids(_metadata, mnts, mnts2, num);328329check_mounted(_metadata, mnts, 1);330}331332TEST_F(fanotify, fsmount)333{334int ret, fs, mnt;335uint64_t mnts[2] = { self->root_id };336337fs = fsopen("tmpfs", 0);338ASSERT_GE(fs, 0);339340ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);341ASSERT_EQ(ret, 0);342343mnt = fsmount(fs, 0, 0);344ASSERT_GE(mnt, 0);345346close(fs);347348ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);349ASSERT_EQ(ret, 0);350351close(mnt);352353mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);354ASSERT_NE(mnts[0], mnts[1]);355356check_mounted(_metadata, mnts, 2);357358// Cleanup359uint64_t detach_id;360ret = umount("/a");361ASSERT_EQ(ret, 0);362363detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);364ASSERT_EQ(detach_id, mnts[1]);365366check_mounted(_metadata, mnts, 1);367}368369TEST_F(fanotify, reparent)370{371uint64_t mnts[6] = { self->root_id };372uint64_t dmnts[3];373uint64_t masks[3];374unsigned int i;375int ret;376377// Create setup with a[1] -> b[2] propagation378ret = mount("/", "/a", NULL, MS_BIND, NULL);379ASSERT_EQ(ret, 0);380381ret = mount("", "/a", NULL, MS_SHARED, NULL);382ASSERT_EQ(ret, 0);383384ret = mount("/a", "/b", NULL, MS_BIND, NULL);385ASSERT_EQ(ret, 0);386387ret = mount("", "/b", NULL, MS_SLAVE, NULL);388ASSERT_EQ(ret, 0);389390expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);391392check_mounted(_metadata, mnts, 3);393394// Mount on a[3], which is propagated to b[4]395ret = mount("/", "/a", NULL, MS_BIND, NULL);396ASSERT_EQ(ret, 0);397398expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);399400check_mounted(_metadata, mnts, 5);401402// Mount on b[5], not propagated403ret = mount("/", "/b", NULL, MS_BIND, NULL);404ASSERT_EQ(ret, 0);405406mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);407408check_mounted(_metadata, mnts, 6);409410// Umount a[3], which is propagated to b[4], but not b[5]411// This will result in b[5] "falling" on b[2]412ret = umount("/a");413ASSERT_EQ(ret, 0);414415expect_notify_n(_metadata, self, 3, masks, dmnts);416verify_mount_ids(_metadata, mnts + 3, dmnts, 3);417418for (i = 0; i < 3; i++) {419if (dmnts[i] == mnts[5]) {420ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);421} else {422ASSERT_EQ(masks[i], FAN_MNT_DETACH);423}424}425426mnts[3] = mnts[5];427check_mounted(_metadata, mnts, 4);428429// Cleanup430ret = umount("/b");431ASSERT_EQ(ret, 0);432433ret = umount("/a");434ASSERT_EQ(ret, 0);435436ret = umount("/b");437ASSERT_EQ(ret, 0);438439expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);440verify_mount_ids(_metadata, mnts + 1, dmnts, 3);441442check_mounted(_metadata, mnts, 1);443}444445TEST_F(fanotify, rmdir)446{447uint64_t mnts[3] = { self->root_id };448int ret;449450ret = mount("/", "/a", NULL, MS_BIND, NULL);451ASSERT_EQ(ret, 0);452453ret = mount("/", "/a/b", NULL, MS_BIND, NULL);454ASSERT_EQ(ret, 0);455456expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);457458check_mounted(_metadata, mnts, 3);459460ret = chdir("/a");461ASSERT_EQ(ret, 0);462463ret = fork();464ASSERT_GE(ret, 0);465466if (ret == 0) {467chdir("/");468unshare(CLONE_NEWNS);469mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);470umount2("/a", MNT_DETACH);471// This triggers a detach in the other namespace472rmdir("/a");473exit(0);474}475wait(NULL);476477expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);478check_mounted(_metadata, mnts, 1);479480// Cleanup481ret = chdir("/");482ASSERT_EQ(ret, 0);483}484485TEST_F(fanotify, pivot_root)486{487uint64_t mnts[3] = { self->root_id };488uint64_t mnts2[3];489int ret;490491ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);492ASSERT_EQ(ret, 0);493494mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);495496ret = mkdir("/a/new", 0700);497ASSERT_EQ(ret, 0);498499ret = mkdir("/a/old", 0700);500ASSERT_EQ(ret, 0);501502ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);503ASSERT_EQ(ret, 0);504505mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);506check_mounted(_metadata, mnts, 3);507508ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");509ASSERT_EQ(ret, 0);510511expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);512verify_mount_ids(_metadata, mnts, mnts2, 2);513check_mounted(_metadata, mnts, 3);514515// Cleanup516ret = syscall(SYS_pivot_root, "/old", "/old/a/new");517ASSERT_EQ(ret, 0);518519ret = umount("/a/new");520ASSERT_EQ(ret, 0);521522ret = umount("/a");523ASSERT_EQ(ret, 0);524525check_mounted(_metadata, mnts, 1);526}527528TEST_HARNESS_MAIN529530531