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