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