// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved. * * Started by Matthew Bobrowski */ /*\ * [Description] * Validate that the values returned within an event when FAN_REPORT_FID is * specified matches those that are obtained via explicit invocation to system * calls statfs(2) and name_to_handle_at(2). */ /* * This is also regression test for: * c285a2f01d69 ("fanotify: update connector fsid cache on add mark") */ #define _GNU_SOURCE #include "config.h" #include #include #include #include #include #include #include #include "tst_test.h" #ifdef HAVE_SYS_FANOTIFY_H #include "fanotify.h" #define PATH_LEN 128 #define BUF_SIZE 256 #define DIR_ONE "dir_one" #define FILE_ONE "file_one" #define FILE_TWO "file_two" #define MOUNT_PATH "mntpoint" #define EVENT_MAX ARRAY_SIZE(objects) #define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE #define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE #define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO #if defined(HAVE_NAME_TO_HANDLE_AT) struct event_t { unsigned long long expected_mask; }; static struct object_t { const char *path; int is_dir; struct fanotify_fid_t fid; } objects[] = { {FILE_PATH_ONE, 0, {}}, {FILE_PATH_TWO, 0, {}}, {DIR_PATH_ONE, 1, {}} }; static struct test_case_t { struct fanotify_mark_type mark; unsigned long long mask; } test_cases[] = { { INIT_FANOTIFY_MARK_TYPE(INODE), FAN_OPEN | FAN_CLOSE_NOWRITE }, { INIT_FANOTIFY_MARK_TYPE(INODE), FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR }, { INIT_FANOTIFY_MARK_TYPE(MOUNT), FAN_OPEN | FAN_CLOSE_NOWRITE }, { INIT_FANOTIFY_MARK_TYPE(MOUNT), FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR }, { INIT_FANOTIFY_MARK_TYPE(FILESYSTEM), FAN_OPEN | FAN_CLOSE_NOWRITE }, { INIT_FANOTIFY_MARK_TYPE(FILESYSTEM), FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR } }; static int nofid_fd; static int fanotify_fd; static int filesystem_mark_unsupported; static char events_buf[BUF_SIZE]; static struct event_t event_set[EVENT_MAX]; static void create_objects(void) { unsigned int i; for (i = 0; i < ARRAY_SIZE(objects); i++) { if (objects[i].is_dir) SAFE_MKDIR(objects[i].path, 0755); else SAFE_FILE_PRINTF(objects[i].path, "0"); } } static void get_object_stats(void) { unsigned int i; for (i = 0; i < ARRAY_SIZE(objects); i++) fanotify_save_fid(objects[i].path, &objects[i].fid); } static int setup_marks(unsigned int fd, struct test_case_t *tc) { unsigned int i; struct fanotify_mark_type *mark = &tc->mark; for (i = 0; i < ARRAY_SIZE(objects); i++) { SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask, AT_FDCWD, objects[i].path); /* Setup the expected mask for each generated event */ event_set[i].expected_mask = tc->mask; if (!objects[i].is_dir) event_set[i].expected_mask &= ~FAN_ONDIR; } return 0; } static void do_test(unsigned int number) { unsigned int i; int len, fds[ARRAY_SIZE(objects)]; struct file_handle *event_file_handle; struct fanotify_event_metadata *metadata; struct fanotify_event_info_fid *event_fid; struct test_case_t *tc = &test_cases[number]; struct fanotify_mark_type *mark = &tc->mark; tst_res(TINFO, "Test #%d: FAN_REPORT_FID with mark flag: %s", number, mark->name); if (filesystem_mark_unsupported && mark->flag & FAN_MARK_FILESYSTEM) { tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?"); return; } fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY); /* * Place marks on a set of objects and setup the expected masks * for each event that is expected to be generated. */ if (setup_marks(fanotify_fd, tc) != 0) goto out; /* Generate sequence of FAN_OPEN events on objects */ for (i = 0; i < ARRAY_SIZE(objects); i++) fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY); /* * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each * FAN_CLOSE_NOWRITE event is expected to be merged with its * respective FAN_OPEN event that was performed on the same object. */ for (i = 0; i < ARRAY_SIZE(objects); i++) { if (fds[i] > 0) SAFE_CLOSE(fds[i]); } /* Read events from event queue */ len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE); /* Iterate over event queue */ for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf; FAN_EVENT_OK(metadata, len); metadata = FAN_EVENT_NEXT(metadata, len), i++) { struct fanotify_fid_t *expected_fid = &objects[i].fid; event_fid = (struct fanotify_event_info_fid *) (metadata + 1); event_file_handle = (struct file_handle *) event_fid->handle; /* File descriptor is redundant with FAN_REPORT_FID */ if (metadata->fd != FAN_NOFD) tst_res(TFAIL, "Unexpectedly received file descriptor %d in " "event. Expected to get FAN_NOFD(%d)", metadata->fd, FAN_NOFD); /* Ensure that the correct mask has been reported in event */ if (metadata->mask != event_set[i].expected_mask) tst_res(TFAIL, "Unexpected mask received: %llx (expected: " "%llx) in event", metadata->mask, event_set[i].expected_mask); /* Verify handle_bytes returned in event */ if (event_file_handle->handle_bytes != expected_fid->handle.handle_bytes) { tst_res(TFAIL, "handle_bytes (%x) returned in event does not " "equal to handle_bytes (%x) returned in " "name_to_handle_at(2)", event_file_handle->handle_bytes, expected_fid->handle.handle_bytes); continue; } /* Verify handle_type returned in event */ if (event_file_handle->handle_type != expected_fid->handle.handle_type) { tst_res(TFAIL, "handle_type (%x) returned in event does not " "equal to handle_type (%x) returned in " "name_to_handle_at(2)", event_file_handle->handle_type, expected_fid->handle.handle_type); continue; } /* Verify file identifier f_handle returned in event */ if (memcmp(event_file_handle->f_handle, expected_fid->handle.f_handle, expected_fid->handle.handle_bytes) != 0) { tst_res(TFAIL, "file_handle returned in event does not match " "the file_handle returned in " "name_to_handle_at(2)"); continue; } /* Verify filesystem ID fsid returned in event */ if (memcmp(&event_fid->fsid, &expected_fid->fsid, sizeof(expected_fid->fsid)) != 0) { tst_res(TFAIL, "event_fid.fsid != stat.f_fsid that was " "obtained via statfs(2)"); continue; } tst_res(TPASS, "got event: mask=%llx, pid=%d, fid=%x.%x.%lx values " "returned in event match those returned in statfs(2) " "and name_to_handle_at(2)", metadata->mask, getpid(), FSID_VAL_MEMBER(event_fid->fsid, 0), FSID_VAL_MEMBER(event_fid->fsid, 1), *(unsigned long *) event_file_handle->f_handle); } out: SAFE_CLOSE(fanotify_fd); } static void do_setup(void) { REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, MOUNT_PATH); filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM); nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY); /* Create file and directory objects for testing */ create_objects(); /* * Create a mark on first inode without FAN_REPORT_FID, to test * uninitialized connector->fsid cache. This mark remains for all test * cases and is not expected to get any events (no writes in this test). */ SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD, FILE_PATH_ONE); /* Get the filesystem fsid and file handle for each created object */ get_object_stats(); } static void do_cleanup(void) { SAFE_CLOSE(nofid_fd); if (fanotify_fd > 0) SAFE_CLOSE(fanotify_fd); } static struct tst_test test = { .test = do_test, .tcnt = ARRAY_SIZE(test_cases), .setup = do_setup, .cleanup = do_cleanup, .needs_root = 1, .mount_device = 1, .mntpoint = MOUNT_PATH, .all_filesystems = 1, .tags = (const struct tst_tag[]) { {"linux-git", "c285a2f01d69"}, {} } }; #else TST_TEST_TCONF("System does not have required name_to_handle_at() support"); #endif #else TST_TEST_TCONF("System does not have required fanotify support"); #endif