xref: /aosp_15_r20/external/ltp/testcases/kernel/syscalls/fanotify/fanotify23.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2022 CTERA Networks.  All Rights Reserved.
4  *
5  * Author: Amir Goldstein <[email protected]>
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 
39 static int old_cache_pressure;
40 static int fd_notify;
41 
42 static unsigned long long event_set[EVENT_MAX];
43 
44 static char event_buf[EVENT_BUF_LEN];
45 
fsync_file(const char * path)46 static 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 */
mount_cycle(void)55 static 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 
verify_mark_removed(const char * path,const char * when)61 static 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 
test_fanotify(void)86 static 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 
setup(void)229 static void setup(void)
230 {
231 	SAFE_TOUCH(TEST_FILE, 0666, NULL);
232 
233 	REQUIRE_MARK_TYPE_SUPPORTED_ON_FS(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 
cleanup(void)243 static 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 
251 static 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