blob: 03073428595c273e625078d22eb66133d5ca88a7 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
*
* Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
*
* 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 <stdio.h>
#include <string.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include "tst_test.h"
#include "fanotify.h"
#if defined(HAVE_SYS_FANOTIFY_H)
#include <sys/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;
__kernel_fsid_t fsid;
struct file_handle handle;
char buf[MAX_HANDLE_SZ];
};
static struct object_t {
const char *path;
int is_dir;
} 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 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++) {
event_set[i].handle.handle_bytes = MAX_HANDLE_SZ;
fanotify_get_fid(objects[i].path, &event_set[i].fsid,
&event_set[i].handle);
}
}
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++) {
if (fanotify_mark(fd, FAN_MARK_ADD | mark->flag, tc->mask,
AT_FDCWD, objects[i].path) == -1) {
if (errno == EINVAL &&
mark->flag & FAN_MARK_FILESYSTEM) {
tst_res(TCONF,
"FAN_MARK_FILESYSTEM not supported by "
"kernel");
return 1;
} else if (errno == ENODEV &&
!event_set[i].fsid.val[0] &&
!event_set[i].fsid.val[1]) {
tst_res(TCONF,
"FAN_REPORT_FID not supported on "
"filesystem type %s",
tst_device->fs_type);
return 1;
}
tst_brk(TBROK | TERRNO,
"fanotify_mark(%d, FAN_MARK_ADD, FAN_OPEN, "
"AT_FDCWD, %s) failed",
fanotify_fd, 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);
fanotify_fd = fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
if (fanotify_fd == -1) {
if (errno == EINVAL) {
tst_res(TCONF,
"FAN_REPORT_FID not supported by kernel");
return;
}
tst_brk(TBROK | TERRNO,
"fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_FID, "
"O_RDONLY) failed");
}
/*
* 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++) {
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
!= event_set[i].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,
event_set[i].handle.handle_bytes);
continue;
}
/* Verify handle_type returned in event */
if (event_file_handle->handle_type !=
event_set[i].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,
event_set[i].handle.handle_type);
continue;
}
/* Verify file identifier f_handle returned in event */
if (memcmp(event_file_handle->f_handle,
event_set[i].handle.f_handle,
event_set[i].handle.handle_bytes) != 0) {
tst_res(TFAIL,
"event_file_handle->f_handle does not match "
"event_set[i].handle.f_handle returned in "
"name_to_handle_at(2)");
continue;
}
/* Verify filesystem ID fsid returned in event */
if (memcmp(&event_fid->fsid, &event_set[i].fsid,
sizeof(event_set[i].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(),
event_fid->fsid.val[0],
event_fid->fsid.val[1],
*(unsigned long *) event_file_handle->f_handle);
}
out:
SAFE_CLOSE(fanotify_fd);
}
static void do_setup(void)
{
/* Check for kernel fanotify support */
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).
*/
if (fanotify_mark(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
FILE_PATH_ONE) == -1) {
tst_brk(TBROK | TERRNO,
"fanotify_mark(%d, FAN_MARK_ADD, FAN_CLOSE_WRITE, "
"AT_FDCWD, "FILE_PATH_ONE") failed",
nofid_fd);
}
/* 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,
.needs_tmpdir = 1,
.mount_device = 1,
.mntpoint = MOUNT_PATH,
.all_filesystems = 1
};
#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