// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2020 CTERA Networks. All Rights Reserved.
 *
 * Started by Amir Goldstein <amir73il@gmail.com>
 */

/*\
 * [Description]
 * Check fanotify directory entry modification events, events on child and
 * on self with group init flags:
 *
 * - FAN_REPORT_DFID_NAME (dir fid + name)
 * - FAN_REPORT_DIR_FID   (dir fid)
 * - FAN_REPORT_DIR_FID | FAN_REPORT_FID   (dir fid + child fid)
 * - FAN_REPORT_DFID_NAME | FAN_REPORT_FID (dir fid + name + child fid)
 * - FAN_REPORT_DFID_NAME_TARGET (dir fid + name + created/deleted file fid)
 */

#define _GNU_SOURCE
#include "config.h"

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include "tst_test.h"

#ifdef HAVE_SYS_FANOTIFY_H
#include "fanotify.h"

#define EVENT_MAX 20

/* Size of the event structure, not including file handle */
#define EVENT_SIZE (sizeof(struct fanotify_event_metadata) + \
		    sizeof(struct fanotify_event_info_fid))

/* Tripple events buffer size to account for file handles and names */
#define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE * 3)

#define BUF_SIZE 256

#ifdef HAVE_NAME_TO_HANDLE_AT
struct event_t {
	unsigned long long mask;
	struct fanotify_fid_t *fid;
	struct fanotify_fid_t *child_fid;
	char name[BUF_SIZE];
	char name2[BUF_SIZE];
	char *old_name;
	char *new_name;
};

static char fname1[BUF_SIZE + 11], fname2[BUF_SIZE + 11];
static char dname1[BUF_SIZE], dname2[BUF_SIZE], tmpdir[BUF_SIZE];
static int fd_notify;

static struct event_t event_set[EVENT_MAX];

static char event_buf[EVENT_BUF_LEN];

#define DIR_NAME1 "test_dir1"
#define DIR_NAME2 "test_dir2"
#define FILE_NAME1 "test_file1"
#define FILE_NAME2 "test_file2"
#define MOUNT_PATH "fs_mnt"
#define TEMP_DIR MOUNT_PATH "/temp_dir"

static int fan_report_target_fid_unsupported;
static int filesystem_mark_unsupported;
static int rename_events_unsupported;

static struct test_case_t {
	const char *tname;
	struct fanotify_group_type group;
	struct fanotify_mark_type mark;
	unsigned long mask;
	struct fanotify_mark_type sub_mark;
	unsigned long sub_mask;
	unsigned long tmpdir_ignored_mask;
} test_cases[] = {
	{
		"FAN_REPORT_DFID_NAME monitor filesystem for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME monitor directories for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DIR_FID monitor filesystem for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DIR_FID),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DIR_FID monitor directories for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DIR_FID),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_FID monitor filesystem for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_FID),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_FID monitor directories for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_FID),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor filesystem for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor directories for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_TARGET monitor filesystem for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_TARGET),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_TARGET monitor directories for create/delete/move/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_TARGET),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor filesystem for create/delete/move/rename/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor directories for create/delete/move/rename/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_TARGET monitor filesystem for create/delete/move/rename/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_TARGET),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_TARGET monitor directories for create/delete/move/rename/open/close",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_TARGET),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		0,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor directories and ignore FAN_RENAME events to/from temp directory",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_ONDIR,
		/* Watches for self events on subdir and events on subdir's children */
		INIT_FANOTIFY_MARK_TYPE(INODE),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR |
		FAN_OPEN | FAN_CLOSE | FAN_EVENT_ON_CHILD,
		/* Ignore FAN_RENAME to/from tmpdir */
		FAN_MOVE | FAN_RENAME,
	},
	{
		"FAN_REPORT_DFID_NAME_FID monitor filesystem and ignore FAN_RENAME events to/from temp directory",
		INIT_FANOTIFY_GROUP_TYPE(REPORT_DFID_NAME_FID),
		INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
		FAN_CREATE | FAN_DELETE | FAN_MOVE | FAN_RENAME | FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR,
		/* Mount watch for events possible on children */
		INIT_FANOTIFY_MARK_TYPE(MOUNT),
		FAN_OPEN | FAN_CLOSE | FAN_ONDIR,
		/* Ignore FAN_RENAME to/from tmpdir */
		FAN_MOVE | FAN_RENAME,
	},
};

static void do_test(unsigned int number)
{
	int fd, dirfd, len = 0, i = 0, test_num = 0, tst_count = 0;
	struct test_case_t *tc = &test_cases[number];
	struct fanotify_group_type *group = &tc->group;
	struct fanotify_mark_type *mark = &tc->mark;
	struct fanotify_mark_type *sub_mark = &tc->sub_mark;
	struct fanotify_fid_t root_fid, dir_fid, file_fid;
	struct fanotify_fid_t *child_fid = NULL, *subdir_fid = NULL;
	int report_name = (group->flag & FAN_REPORT_NAME);
	int report_target_fid = (group->flag & FAN_REPORT_TARGET_FID);
	int report_rename = (tc->mask & FAN_RENAME);
	int fs_mark = (mark->flag == FAN_MARK_FILESYSTEM);
	int rename_ignored = (tc->tmpdir_ignored_mask & FAN_RENAME);

	tst_res(TINFO, "Test #%d: %s", number, tc->tname);

	if (report_rename && rename_events_unsupported) {
		tst_res(TCONF, "FAN_RENAME not supported in kernel?");
		return;
	}

	if (fan_report_target_fid_unsupported && report_target_fid) {
		FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_TARGET_FID,
					    fan_report_target_fid_unsupported);
		return;
	}

	if (filesystem_mark_unsupported) {
		if (sub_mark && sub_mark->flag != FAN_MARK_INODE)
			mark = sub_mark;

		if (mark->flag != FAN_MARK_INODE) {
			FANOTIFY_MARK_FLAGS_ERR_MSG(mark, filesystem_mark_unsupported);
			return;
		}
	}

	fd_notify = SAFE_FANOTIFY_INIT(group->flag, 0);

	/*
	 * Watch dir modify events with name in filesystem/dir
	 */
	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | mark->flag, tc->mask,
			   AT_FDCWD, MOUNT_PATH);

	/* Save the mount root fid */
	fanotify_save_fid(MOUNT_PATH, &root_fid);

	/*
	 * Create subdir and watch open events "on children" with name.
	 * Make it a mount root.
	 */
	SAFE_MKDIR(dname1, 0755);
	SAFE_MOUNT(dname1, dname1, "none", MS_BIND, NULL);

	/* Save the subdir fid */
	fanotify_save_fid(dname1, &dir_fid);
	/* With FAN_REPORT_TARGET_FID, report subdir fid also for dirent events */
	if (report_target_fid)
		subdir_fid = &dir_fid;

	if (tc->sub_mask)
		SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | sub_mark->flag,
				   tc->sub_mask, AT_FDCWD, dname1);
	/*
	 * ignore FAN_RENAME to/from tmpdir, so we won't get the FAN_RENAME events
	 * when subdir is moved via tmpdir.
	 * FAN_MOVE is also set in ignored mark of tmpdir, but it will have no effect
	 * and the MOVED_FROM/TO events will still be reported.
	 */
	if (tc->tmpdir_ignored_mask)
		SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD |
				   FAN_MARK_IGNORED_MASK |
				   FAN_MARK_IGNORED_SURV_MODIFY,
				   tc->tmpdir_ignored_mask, AT_FDCWD, TEMP_DIR);

	memset(event_set, 0, sizeof(event_set));
	event_set[tst_count].mask = FAN_CREATE | FAN_ONDIR;
	event_set[tst_count].fid = &root_fid;
	event_set[tst_count].child_fid = subdir_fid;
	strcpy(event_set[tst_count].name, DIR_NAME1);
	tst_count++;

	/* Generate modify events "on child" */

	/*
	 * Split SAFE_CREAT() into explicit SAFE_MKNOD() and SAFE_OPEN(),
	 * because with atomic open (e.g. fuse), SAFE_CREAT() generates
	 * FAN_OPEN before FAN_CREATE and it is inconsistent with the order
	 * of events expectated from other filesystems.
	 */
	SAFE_MKNOD(fname1, S_IFREG | 0644, 0);
	fd = SAFE_OPEN(fname1, O_WRONLY);

	/* Save the file fid */
	fanotify_save_fid(fname1, &file_fid);
	/* With FAN_REPORT_TARGET_FID, report child fid also for dirent events */
	if (report_target_fid)
		child_fid = &file_fid;

	SAFE_WRITE(SAFE_WRITE_ALL, fd, "1", 1);
	SAFE_RENAME(fname1, fname2);

	SAFE_CLOSE(fd);

	/* Generate delete events with fname2 */
	SAFE_UNLINK(fname2);

	/* Read events on files in subdir */
	len += SAFE_READ(0, fd_notify, event_buf + len, EVENT_BUF_LEN - len);

	/*
	 * FAN_CREATE|FAN_DELETE|FAN_MOVE events with the same name are merged.
	 */
	event_set[tst_count].mask = FAN_CREATE | FAN_MOVED_FROM;
	event_set[tst_count].fid = &dir_fid;
	event_set[tst_count].child_fid = child_fid;
	strcpy(event_set[tst_count].name, FILE_NAME1);
	tst_count++;
	/*
	 * Event on non-dir child with the same name may be merged with the
	 * directory entry modification events above, unless FAN_REPORT_FID is
	 * set and child fid is reported. If FAN_REPORT_FID is set but
	 * FAN_REPORT_NAME is not set, then FAN_CREATE above is merged with
	 * FAN_DELETE below and FAN_OPEN will be merged with FAN_CLOSE.
	 */
	if (report_name) {
		event_set[tst_count].mask = FAN_OPEN;
		event_set[tst_count].fid = &dir_fid;
		event_set[tst_count].child_fid = &file_fid;
		strcpy(event_set[tst_count].name, FILE_NAME1);
		tst_count++;
	}
	/*
	 * FAN_RENAME event is independent of MOVED_FROM/MOVED_TO and not merged
	 * with any other event because it has different info records.
	 */
	if (report_rename) {
		event_set[tst_count].mask = FAN_RENAME;
		event_set[tst_count].fid = &dir_fid;
		event_set[tst_count].child_fid = child_fid;
		strcpy(event_set[tst_count].name, FILE_NAME1);
		strcpy(event_set[tst_count].name2, FILE_NAME2);
		event_set[tst_count].old_name = event_set[tst_count].name;
		event_set[tst_count].new_name = event_set[tst_count].name2;
		tst_count++;
	}

	event_set[tst_count].mask = FAN_DELETE | FAN_MOVED_TO;
	/*
	 * With FAN_REPORT_TARGET_FID, close of FILE_NAME2 is merged with
	 * moved_to and delete events, because they all have parent and
	 * child fid records.
	 */
	if (report_target_fid)
		event_set[tst_count].mask |= FAN_CLOSE_WRITE;
	event_set[tst_count].fid = &dir_fid;
	event_set[tst_count].child_fid = child_fid;
	strcpy(event_set[tst_count].name, FILE_NAME2);
	tst_count++;
	/*
	 * When not reporting name, open of FILE_NAME1 is merged
	 * with close of FILE_NAME2.
	 */
	if (!report_name) {
		event_set[tst_count].mask = FAN_OPEN | FAN_CLOSE_WRITE;
		event_set[tst_count].fid = &dir_fid;
		event_set[tst_count].child_fid = &file_fid;
		strcpy(event_set[tst_count].name, "");
		tst_count++;
	}
	/*
	 * Directory watch does not get self events on children.
	 * Filesystem watch gets self event w/o name info if FAN_REPORT_FID
	 * is set.
	 */
	if (fs_mark && (group->flag & FAN_REPORT_FID)) {
		event_set[tst_count].mask = FAN_DELETE_SELF | FAN_MOVE_SELF;
		event_set[tst_count].fid = &file_fid;
		event_set[tst_count].child_fid = NULL;
		strcpy(event_set[tst_count].name, "");
		tst_count++;
	}
	/*
	 * Without FAN_REPORT_TARGET_FID, close of FILE_NAME2 is not merged with
	 * open of FILE_NAME1 and it is received after the merged self events.
	 */
	if (report_name && !report_target_fid) {
		event_set[tst_count].mask = FAN_CLOSE_WRITE;
		event_set[tst_count].fid = &dir_fid;
		event_set[tst_count].child_fid = &file_fid;
		strcpy(event_set[tst_count].name, FILE_NAME2);
		tst_count++;
	}

	dirfd = SAFE_OPEN(dname1, O_RDONLY | O_DIRECTORY);
	SAFE_CLOSE(dirfd);

	SAFE_UMOUNT(dname1);

	/*
	 * Directory watch gets open/close events on itself and on its subdirs.
	 * Filesystem watch gets open/close event on all directories with name ".".
	 */
	event_set[tst_count].mask = FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR;
	event_set[tst_count].fid = &dir_fid;
	event_set[tst_count].child_fid = NULL;
	strcpy(event_set[tst_count].name, ".");
	tst_count++;
	/*
	 * Directory watch gets self event on itself and filesystem watch gets
	 * self event on all directories with name ".".
	 */
	event_set[tst_count].mask = FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_ONDIR;
	event_set[tst_count].fid = &dir_fid;
	event_set[tst_count].child_fid = NULL;
	strcpy(event_set[tst_count].name, ".");
	tst_count++;

	/*
	 * If only root dir and subdir are watched, a rename via an unwatched tmpdir
	 * will observe the same MOVED_FROM/MOVED_TO events as a direct rename,
	 * but will observe 2 FAN_RENAME events with 1 info dir+name record each
	 * instead of 1 FAN_RENAME event with 2 dir+name info records.
	 *
	 * If tmpdir is ignoring FAN_RENAME, we will get the MOVED_FROM/MOVED_TO
	 * events and will not get the FAN_RENAME event for rename via tmpdir.
	 */
	if (!fs_mark || rename_ignored) {
		SAFE_RENAME(dname1, tmpdir);
		SAFE_RENAME(tmpdir, dname2);
	} else {
		SAFE_RENAME(dname1, dname2);
	}
	SAFE_RMDIR(dname2);

	/* Read more events on dirs */
	len += SAFE_READ(0, fd_notify, event_buf + len, EVENT_BUF_LEN - len);

	/*
	 * FAN_RENAME event is independent of MOVED_FROM/MOVED_TO and not merged
	 * with any other event because it has different info records.
	 * When renamed via an unwatched tmpdir, the 1st FAN_RENAME event has the
	 * info record of root_fid+DIR_NAME1 and the 2nd FAN_RENAME event has the
	 * info record of root_fid+DIR_NAME2.
	 * If tmpdir is ignoring FAN_RENAME, we get no FAN_RENAME events at all.
	 */
	if (report_rename && !rename_ignored) {
		event_set[tst_count].mask = FAN_RENAME | FAN_ONDIR;
		event_set[tst_count].fid = &root_fid;
		event_set[tst_count].child_fid = subdir_fid;
		strcpy(event_set[tst_count].name, DIR_NAME1);
		event_set[tst_count].old_name = event_set[tst_count].name;
		if (fs_mark) {
			strcpy(event_set[tst_count].name2, DIR_NAME2);
			event_set[tst_count].new_name = event_set[tst_count].name2;
		}
		tst_count++;
	}
	event_set[tst_count].mask = FAN_MOVED_FROM | FAN_ONDIR;
	event_set[tst_count].fid = &root_fid;
	event_set[tst_count].child_fid = subdir_fid;
	strcpy(event_set[tst_count].name, DIR_NAME1);
	tst_count++;
	if (report_rename && !fs_mark && !rename_ignored) {
		event_set[tst_count].mask = FAN_RENAME | FAN_ONDIR;
		event_set[tst_count].fid = &root_fid;
		event_set[tst_count].child_fid = subdir_fid;
		strcpy(event_set[tst_count].name, DIR_NAME2);
		event_set[tst_count].new_name = event_set[tst_count].name;
		tst_count++;
	}
	event_set[tst_count].mask = FAN_DELETE | FAN_MOVED_TO | FAN_ONDIR;
	event_set[tst_count].fid = &root_fid;
	event_set[tst_count].child_fid = subdir_fid;
	strcpy(event_set[tst_count].name, DIR_NAME2);
	tst_count++;
	/* Expect no more events */
	event_set[tst_count].mask = 0;

	/*
	 * Cleanup the marks
	 */
	SAFE_CLOSE(fd_notify);
	fd_notify = -1;

	while (i < len) {
		struct event_t *expected = &event_set[test_num];
		struct fanotify_event_metadata *event;
		struct fanotify_event_info_fid *event_fid;
		struct fanotify_event_info_fid *child_fid;
		struct fanotify_fid_t *expected_fid = expected->fid;
		struct fanotify_fid_t *expected_child_fid = expected->child_fid;
		struct file_handle *file_handle;
		unsigned int fhlen;
		const char *filename;
		int namelen, info_type, mask_match, info_id = 0;

		event = (struct fanotify_event_metadata *)&event_buf[i];
		event_fid = (struct fanotify_event_info_fid *)(event + 1);
		file_handle = (struct file_handle *)event_fid->handle;
		fhlen = file_handle->handle_bytes;
		filename = (char *)file_handle->f_handle + fhlen;
		child_fid = (void *)((char *)event_fid + event_fid->hdr.len);
		namelen = (char *)child_fid - (char *)filename;
		/* End of event_fid could have name, zero padding, both or none */
		if (namelen > 0) {
			namelen = strlen(filename);
		} else {
			filename = "";
			namelen = 0;
		}
		/* Is there a child fid after first fid record? */
		if (((char *)child_fid - (char *)event) >= event->event_len)
			child_fid = NULL;

		if (!(group->flag & FAN_REPORT_FID))
			expected_child_fid = NULL;

		if (!report_name)
			expected->name[0] = 0;

		if (expected->mask & FAN_RENAME) {
			/* If old name is not reported, first record is new name */
			info_type = expected->old_name ?
				FAN_EVENT_INFO_TYPE_OLD_DFID_NAME :
				FAN_EVENT_INFO_TYPE_NEW_DFID_NAME;
			/* The 2nd fid is same as 1st becaue we rename in same parent */
			if (expected->name2[0])
				expected_child_fid = expected_fid;
		} else if (expected->name[0]) {
			info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
		} else if (expected->mask & FAN_ONDIR) {
			info_type = FAN_EVENT_INFO_TYPE_DFID;
		} else if (expected->mask & (FAN_DELETE_SELF | FAN_MOVE_SELF)) {
			/* Self event on non-dir has only child fid */
			info_type = FAN_EVENT_INFO_TYPE_FID;
		} else {
			info_type = FAN_EVENT_INFO_TYPE_DFID;
		}

		/*
		 * Event may contain more than the expected mask, but it must
		 * have all the bits in expected mask.
		 * Expected event on dir must not get event on non dir and the
		 * other way around.
		 */
		mask_match = ((event->mask & expected->mask) &&
			      !(expected->mask & ~event->mask) &&
			      !((event->mask ^ expected->mask) & FAN_ONDIR));

check_match:
		if (test_num >= tst_count) {
			tst_res(TFAIL,
				"got unnecessary event: mask=%llx "
				"pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (!fhlen || namelen < 0) {
			tst_res(TFAIL,
				"got event without fid: mask=%llx pid=%u fd=%d, "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (!mask_match) {
			tst_res(TFAIL,
				"got event: mask=%llx (expected %llx) "
				"pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask, expected->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (info_type != event_fid->hdr.info_type) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u fd=%d, "
				"len=%d info_type=%d expected(%d) info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd,
				event->event_len, event_fid->hdr.info_type,
				info_type, event_fid->hdr.len, fhlen);
		} else if (fhlen != expected_fid->handle.handle_bytes) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d expected(%d) "
				"fh_type=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, info_type,
				event_fid->hdr.len, fhlen,
				expected_fid->handle.handle_bytes,
				file_handle->handle_type);
		} else if (file_handle->handle_type !=
			   expected_fid->handle.handle_type) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d "
				"fh_type=%d expected(%x)",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, info_type,
				event_fid->hdr.len, fhlen,
				file_handle->handle_type,
				expected_fid->handle.handle_type);
		} else if (memcmp(file_handle->f_handle,
				  expected_fid->handle.f_handle, fhlen)) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d "
				"fh_type=%d unexpected file handle (%x...)",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, info_type,
				event_fid->hdr.len, fhlen,
				file_handle->handle_type,
				*(int *)(file_handle->f_handle));
		} else if (memcmp(&event_fid->fsid, &expected_fid->fsid,
				  sizeof(event_fid->fsid)) != 0) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d "
				"fsid=%x.%x (expected %x.%x)",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, info_type,
				event_fid->hdr.len, fhlen,
				FSID_VAL_MEMBER(event_fid->fsid, 0),
				FSID_VAL_MEMBER(event_fid->fsid, 1),
				expected_fid->fsid.val[0],
				expected_fid->fsid.val[1]);
		} else if (strcmp(expected->name, filename)) {
			tst_res(TFAIL,
				"got event: mask=%llx "
				"pid=%u fd=%d name='%s' expected('%s') "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd,
				filename, expected->name,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (event->pid != getpid()) {
			tst_res(TFAIL,
				"got event: mask=%llx pid=%u "
				"(expected %u) fd=%d name='%s' "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid,
				(unsigned int)getpid(),
				event->fd, filename,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (!!child_fid != !!expected_child_fid) {
			tst_res(TFAIL,
				"got event: mask=%llx "
				"pid=%u fd=%d name='%s' num_info=%d (expected %d) "
				"len=%d info_type=%d info_len=%d fh_len=%d",
				(unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd,
				filename, 1 + !!child_fid, 1 + !!expected_child_fid,
				event->event_len, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		} else if (child_fid) {
			tst_res(TINFO,
				"got event #%d: info #%d: info_type=%d info_len=%d fh_len=%d",
				test_num, info_id, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);

			/* Recheck event_fid match with child_fid */
			event_fid = child_fid;
			expected_fid = expected->child_fid;
			info_id = 1;
			info_type = FAN_EVENT_INFO_TYPE_FID;
			/*
			 * With FAN_RENAME event, expect a second record of
			 * type NEW_DFID_NAME, which in our case
			 * has the same fid as the source dir in 1st record.
			 * TODO: check the 2nd name and the 3rd child fid record.
			 */
			if (event->mask & FAN_RENAME && expected->name2[0]) {
				info_type = FAN_EVENT_INFO_TYPE_NEW_DFID_NAME;
				expected_fid = expected->fid;
			}
			file_handle = (struct file_handle *)event_fid->handle;
			fhlen = file_handle->handle_bytes;
			child_fid = NULL;
			expected_child_fid = NULL;
			goto check_match;
		} else {
			tst_res(TPASS,
				"got event #%d: mask=%llx pid=%u fd=%d name='%s' "
				"len=%d; info #%d: info_type=%d info_len=%d fh_len=%d",
				test_num, (unsigned long long)event->mask,
				(unsigned int)event->pid, event->fd, filename,
				event->event_len, info_id, event_fid->hdr.info_type,
				event_fid->hdr.len, fhlen);
		}

		if (test_num < tst_count)
			test_num++;

		if (mask_match) {
			/* In case of merged event match next expected mask */
			event->mask &= ~expected->mask | FAN_ONDIR;
			if (event->mask & ~FAN_ONDIR)
				continue;
		}

		i += event->event_len;
		if (event->fd > 0)
			SAFE_CLOSE(event->fd);
	}

	for (; test_num < tst_count; test_num++) {
		tst_res(TFAIL, "didn't get event: mask=%llx, name='%s'",
			 event_set[test_num].mask, event_set[test_num].name);

	}
}

static void setup(void)
{
	REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_DIR_FID, MOUNT_PATH);
	fan_report_target_fid_unsupported =
		fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME_TARGET, MOUNT_PATH);
	filesystem_mark_unsupported =
		fanotify_flags_supported_on_fs(FAN_REPORT_FID, FAN_MARK_FILESYSTEM, FAN_OPEN,
						MOUNT_PATH);
	rename_events_unsupported =
		fanotify_flags_supported_on_fs(FAN_REPORT_DFID_NAME, 0,
					       FAN_RENAME, MOUNT_PATH);

	SAFE_MKDIR(TEMP_DIR, 0755);
	sprintf(dname1, "%s/%s", MOUNT_PATH, DIR_NAME1);
	sprintf(dname2, "%s/%s", MOUNT_PATH, DIR_NAME2);
	sprintf(tmpdir, "%s/%s", TEMP_DIR, DIR_NAME2);
	sprintf(fname1, "%s/%s", dname1, FILE_NAME1);
	sprintf(fname2, "%s/%s", dname1, FILE_NAME2);
}

static void cleanup(void)
{
	if (fd_notify > 0)
		SAFE_CLOSE(fd_notify);
}

static struct tst_test test = {
	.test = do_test,
	.tcnt = ARRAY_SIZE(test_cases),
	.setup = setup,
	.cleanup = cleanup,
	.mount_device = 1,
	.mntpoint = MOUNT_PATH,
	.all_filesystems = 1,
	.needs_root = 1
};

#else
	TST_TEST_TCONF("system does not have required name_to_handle_at() support");
#endif
#else
	TST_TEST_TCONF("system doesn't have required fanotify support");
#endif
