1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2022 CTERA Networks.  All Rights Reserved.
4 *
5 * Author: Amir Goldstein <amir73il@gmail.com>
6 */
7
8/*\
9 * [Description]
10 * Check evictable fanotify inode marks.
11 */
12
13#define _GNU_SOURCE
14#include "config.h"
15
16#include <stdio.h>
17#include <sys/stat.h>
18#include <sys/types.h>
19#include <errno.h>
20#include <string.h>
21#include <sys/syscall.h>
22#include "tst_test.h"
23
24#ifdef HAVE_SYS_FANOTIFY_H
25#include "fanotify.h"
26
27#define EVENT_MAX 1024
28/* size of the event structure, not counting name */
29#define EVENT_SIZE  (sizeof(struct fanotify_event_metadata))
30/* reasonable guess as to size of 1024 events */
31#define EVENT_BUF_LEN        (EVENT_MAX * EVENT_SIZE)
32
33#define MOUNT_PATH "fs_mnt"
34#define TEST_FILE MOUNT_PATH "/testfile"
35
36#define DROP_CACHES_FILE "/proc/sys/vm/drop_caches"
37#define CACHE_PRESSURE_FILE "/proc/sys/vm/vfs_cache_pressure"
38
39static int old_cache_pressure;
40static int fd_notify;
41
42static unsigned long long event_set[EVENT_MAX];
43
44static char event_buf[EVENT_BUF_LEN];
45
46static void fsync_file(const char *path)
47{
48	int fd = SAFE_OPEN(path, O_RDONLY);
49
50	SAFE_FSYNC(fd);
51	SAFE_CLOSE(fd);
52}
53
54/* Flush out all pending dirty inodes and destructing marks */
55static void mount_cycle(void)
56{
57	SAFE_UMOUNT(MOUNT_PATH);
58	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
59}
60
61static int verify_mark_removed(const char *path, const char *when)
62{
63	int ret;
64
65	/*
66	 * We know that inode with evictable mark was evicted when a
67	 * bogus call remove ACCESS from event mask returns ENOENT.
68	 */
69	errno = 0;
70	ret = fanotify_mark(fd_notify, FAN_MARK_REMOVE,
71			    FAN_ACCESS, AT_FDCWD, path);
72	if (ret == -1 && errno == ENOENT) {
73		tst_res(TPASS,
74			"FAN_MARK_REMOVE failed with ENOENT as expected"
75			" %s", when);
76		return 1;
77	}
78
79	tst_res(TFAIL | TERRNO,
80		"FAN_MARK_REMOVE did not fail with ENOENT as expected"
81		" %s", when);
82
83	return 0;
84}
85
86static void test_fanotify(void)
87{
88	int ret, len, test_num = 0;
89	struct fanotify_event_metadata *event;
90	int tst_count = 0;
91
92	fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID |
93				       FAN_NONBLOCK, O_RDONLY);
94
95	/*
96	 * Verify that evictable mark can be upgraded to non-evictable
97	 * and cannot be downgraded to evictable.
98	 */
99	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | FAN_MARK_EVICTABLE,
100			   FAN_ACCESS,
101			   AT_FDCWD, TEST_FILE);
102	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD,
103			   FAN_ACCESS,
104			   AT_FDCWD, TEST_FILE);
105	errno = 0;
106	ret = fanotify_mark(fd_notify, FAN_MARK_ADD | FAN_MARK_EVICTABLE,
107			    FAN_ACCESS,
108			    AT_FDCWD, TEST_FILE);
109	if (ret == -1 && errno == EEXIST) {
110		tst_res(TPASS,
111			"FAN_MARK_ADD failed with EEXIST as expected"
112			" when trying to downgrade to evictable mark");
113	} else {
114		tst_res(TFAIL | TERRNO,
115			"FAN_MARK_ADD did not fail with EEXIST as expected"
116			" when trying to downgrade to evictable mark");
117	}
118	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_REMOVE,
119			   FAN_ACCESS,
120			   AT_FDCWD, TEST_FILE);
121	verify_mark_removed(TEST_FILE, "after empty mask");
122
123
124	/*
125	 * Watch ATTRIB events on entire mount
126	 */
127	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
128			   FAN_ATTRIB, AT_FDCWD, MOUNT_PATH);
129
130	/*
131	 * Generate events
132	 */
133	SAFE_CHMOD(TEST_FILE, 0600);
134	event_set[tst_count] = FAN_ATTRIB;
135	tst_count++;
136
137	/* Read events so far */
138	ret = SAFE_READ(0, fd_notify, event_buf, EVENT_BUF_LEN);
139	len = ret;
140
141	/*
142	 * Evictable mark on file ignores ATTRIB events
143	 */
144	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD | FAN_MARK_EVICTABLE |
145			   FAN_MARK_IGNORED_MASK | FAN_MARK_IGNORED_SURV_MODIFY,
146			   FAN_ATTRIB, AT_FDCWD, TEST_FILE);
147
148	/* ATTRIB event should be ignored */
149	SAFE_CHMOD(TEST_FILE, 0600);
150
151	/*
152	 * Read events to verify event was ignored
153	 */
154	ret = read(fd_notify, event_buf + len, EVENT_BUF_LEN - len);
155	if (ret < 0 && errno == EAGAIN) {
156		tst_res(TPASS, "Got no events as expected");
157	} else {
158		tst_res(TFAIL, "Got expected events");
159		len += ret;
160	}
161
162	/*
163	 * drop_caches should evict inode from cache and remove evictable mark.
164	 * We call drop_caches twice as once the dentries will just cycle
165	 * through the LRU without being reclaimed and if there are no other
166	 * objects to reclaim, the slab reclaim will just stop instead of
167	 * retrying. Note that this relies on how reclaim of fs objects work
168	 * for the filesystem but this test is restricted to ext2...
169	 */
170	fsync_file(TEST_FILE);
171	SAFE_FILE_PRINTF(DROP_CACHES_FILE, "3");
172	SAFE_FILE_PRINTF(DROP_CACHES_FILE, "3");
173
174	verify_mark_removed(TEST_FILE, "after drop_caches");
175
176	SAFE_CHMOD(TEST_FILE, 0600);
177	event_set[tst_count] = FAN_ATTRIB;
178	tst_count++;
179
180	/* Read events to verify ATTRIB event was properly generated */
181	ret = SAFE_READ(0, fd_notify, event_buf + len, EVENT_BUF_LEN - len);
182	len += ret;
183
184	/*
185	 * Check events
186	 */
187	event = (struct fanotify_event_metadata *)event_buf;
188
189	/* Iterate over and validate events against expected result set */
190	while (FAN_EVENT_OK(event, len) && test_num < tst_count) {
191		if (!(event->mask & event_set[test_num])) {
192			tst_res(TFAIL,
193				"got event: mask=%llx (expected %llx)",
194				(unsigned long long)event->mask,
195				event_set[test_num]);
196		} else {
197			tst_res(TPASS,
198				"got event: mask=%llx",
199				(unsigned long long)event->mask);
200		}
201		/*
202		 * Close fd and invalidate it so that we don't check it again
203		 * unnecessarily
204		 */
205		if (event->fd >= 0)
206			SAFE_CLOSE(event->fd);
207		event->fd = FAN_NOFD;
208		event->mask &= ~event_set[test_num];
209		/* No events left in current mask? Go for next event */
210		if (event->mask == 0)
211			event = FAN_EVENT_NEXT(event, len);
212		test_num++;
213	}
214
215	while (FAN_EVENT_OK(event, len)) {
216		tst_res(TFAIL,
217			"got unnecessary event: mask=%llx",
218			(unsigned long long)event->mask);
219		if (event->fd != FAN_NOFD)
220			SAFE_CLOSE(event->fd);
221		event = FAN_EVENT_NEXT(event, len);
222	}
223
224	SAFE_CLOSE(fd_notify);
225	/* Flush out all pending dirty inodes and destructing marks */
226	mount_cycle();
227}
228
229static void setup(void)
230{
231	SAFE_TOUCH(TEST_FILE, 0666, NULL);
232
233	REQUIRE_MARK_TYPE_SUPPORTED_BY_KERNEL(FAN_MARK_EVICTABLE);
234	REQUIRE_FANOTIFY_EVENTS_SUPPORTED_ON_FS(FAN_CLASS_NOTIF|FAN_REPORT_FID,
235						FAN_MARK_FILESYSTEM,
236						FAN_ATTRIB, ".");
237
238	SAFE_FILE_SCANF(CACHE_PRESSURE_FILE, "%d", &old_cache_pressure);
239	/* Set high priority for evicting inodes */
240	SAFE_FILE_PRINTF(CACHE_PRESSURE_FILE, "500");
241}
242
243static void cleanup(void)
244{
245	if (fd_notify > 0)
246		SAFE_CLOSE(fd_notify);
247
248	SAFE_FILE_PRINTF(CACHE_PRESSURE_FILE, "%d", old_cache_pressure);
249}
250
251static struct tst_test test = {
252	.test_all = test_fanotify,
253	.setup = setup,
254	.cleanup = cleanup,
255	.needs_root = 1,
256	.mount_device = 1,
257	.mntpoint = MOUNT_PATH,
258	/* Shrinkers on other fs do not work reliably enough to guarantee mark eviction on drop_caches */
259	.dev_fs_type = "ext2",
260};
261
262#else
263	TST_TEST_TCONF("system doesn't have required fanotify support");
264#endif
265