1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2018 CTERA Networks. All Rights Reserved. 4 * 5 * Started by Amir Goldstein <amir73il@gmail.com> 6 */ 7 8/*\ 9 * [Description] 10 * Check that fanotify handles events on children correctly when both parent and 11 * subdir or mountpoint marks exist. 12 */ 13 14/* 15 * This is a regression test for commit: 16 * 17 * 54a307ba8d3c fanotify: fix logic of events on child 18 * 19 * Test case #1 is a regression test for commit: 20 * 21 * b469e7e47c8a fanotify: fix handling of events on child sub-directory 22 * 23 * Test case #2 is a regression test for commit: 24 * 25 * 55bf882c7f13 fanotify: fix merging marks masks with FAN_ONDIR 26 * 27 * Test case #5 is a regression test for commit: 28 * 29 * 7372e79c9eb9 fanotify: fix logic of reporting name info with watched parent 30 * 31 * Test cases #6-#7 are regression tests for commit: 32 * (from v5.19, unlikely to be backported thus not in .tags): 33 * 34 * e730558adffb fanotify: consistent behavior for parent not watching children 35 */ 36 37#define _GNU_SOURCE 38#include "config.h" 39 40#include <stdio.h> 41#include <sys/stat.h> 42#include <sys/types.h> 43#include <errno.h> 44#include <string.h> 45#include <sys/mount.h> 46#include <sys/syscall.h> 47#include <stdint.h> 48#include "tst_test.h" 49 50#ifdef HAVE_SYS_FANOTIFY_H 51#include "fanotify.h" 52 53#define EVENT_MAX 1024 54/* size of the event structure, not counting name */ 55#define EVENT_SIZE (sizeof(struct fanotify_event_metadata)) 56/* reasonable guess as to size of 1024 events */ 57#define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE) 58 59#define NUM_GROUPS 3 60 61#define BUF_SIZE 256 62static char fname[BUF_SIZE]; 63static char symlnk[BUF_SIZE]; 64static char fdpath[BUF_SIZE]; 65static int fd_notify[NUM_GROUPS]; 66 67static char event_buf[EVENT_BUF_LEN]; 68 69#define MOUNT_PATH "fs_mnt" 70#define MOUNT_NAME "mntpoint" 71#define DIR_NAME "testdir" 72#define FILE2_NAME "testfile" 73static int mount_created; 74 75static int fan_report_dfid_unsupported; 76static int ignore_mark_unsupported; 77 78static struct tcase { 79 const char *tname; 80 struct fanotify_mark_type mark; 81 unsigned int ondir; 82 unsigned int ignore; 83 unsigned int ignore_flags; 84 unsigned int report_name; 85 const char *event_path; 86 int nevents; 87 unsigned int nonfirst_event; 88} tcases[] = { 89 { 90 .tname = "Events on non-dir child with both parent and mount marks", 91 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 92 .event_path = DIR_NAME, 93 .nevents = 1, 94 }, 95 { 96 .tname = "Events on non-dir child and subdir with both parent and mount marks", 97 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 98 .ondir = FAN_ONDIR, 99 .event_path = DIR_NAME, 100 .nevents = 2, 101 }, 102 { 103 .tname = "Events on non-dir child and parent with both parent and mount marks", 104 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 105 .ondir = FAN_ONDIR, 106 .event_path = ".", 107 .nevents = 2, 108 }, 109 { 110 .tname = "Events on non-dir child and subdir with both parent and subdir marks", 111 .mark = INIT_FANOTIFY_MARK_TYPE(INODE), 112 .ondir = FAN_ONDIR, 113 .event_path = DIR_NAME, 114 .nevents = 2, 115 }, 116 { 117 .tname = "Events on non-dir children with both parent and mount marks", 118 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 119 .event_path = FILE2_NAME, 120 .nevents = 2, 121 .nonfirst_event = FAN_CLOSE_NOWRITE, 122 }, 123 { 124 .tname = "Events on non-dir child with both parent and mount marks and filename info", 125 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 126 .report_name = FAN_REPORT_DFID_NAME, 127 .event_path = FILE2_NAME, 128 .nevents = 2, 129 .nonfirst_event = FAN_CLOSE_NOWRITE, 130 }, 131 { 132 .tname = "Events on non-dir child with ignore mask on parent", 133 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 134 .ignore = FAN_MARK_IGNORED_MASK, 135 .event_path = DIR_NAME, 136 .nevents = 1, 137 }, 138 { 139 .tname = "Events on non-dir children with surviving ignore mask on parent", 140 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 141 .ignore = FAN_MARK_IGNORED_MASK | FAN_MARK_IGNORED_SURV_MODIFY, 142 .event_path = FILE2_NAME, 143 .nevents = 2, 144 .nonfirst_event = FAN_CLOSE_NOWRITE, 145 }, 146 /* FAN_MARK_IGNORE test cases: */ 147 { 148 .tname = "Events on dir with ignore mask that does not apply to dirs", 149 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 150 .ondir = FAN_ONDIR, 151 .ignore = FAN_MARK_IGNORE_SURV, 152 .event_path = ".", 153 .nevents = 2, 154 .nonfirst_event = FAN_CLOSE_NOWRITE, 155 }, 156 { 157 .tname = "Events on dir with ignore mask that does apply to dirs", 158 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 159 .ondir = FAN_ONDIR, 160 .ignore = FAN_MARK_IGNORE_SURV, 161 .ignore_flags = FAN_ONDIR, 162 .event_path = ".", 163 .nevents = 2, 164 }, 165 { 166 .tname = "Events on child with ignore mask on parent that does not apply to children", 167 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 168 .ignore = FAN_MARK_IGNORE_SURV, 169 .event_path = FILE2_NAME, 170 .nevents = 2, 171 .nonfirst_event = FAN_CLOSE_NOWRITE, 172 }, 173 { 174 .tname = "Events on child with ignore mask on parent that does apply to children", 175 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 176 .ignore = FAN_MARK_IGNORE_SURV, 177 .ignore_flags = FAN_EVENT_ON_CHILD, 178 .event_path = FILE2_NAME, 179 .nevents = 2, 180 }, 181 { 182 .tname = "Events on subdir with ignore mask on parent that does not apply to children", 183 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 184 .ondir = FAN_ONDIR, 185 .ignore = FAN_MARK_IGNORE_SURV, 186 .ignore_flags = FAN_ONDIR, 187 .event_path = DIR_NAME, 188 .nevents = 2, 189 .nonfirst_event = FAN_CLOSE_NOWRITE, 190 }, 191 { 192 .tname = "Events on subdir with ignore mask on parent that does not apply to dirs", 193 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 194 .ondir = FAN_ONDIR, 195 .ignore = FAN_MARK_IGNORE_SURV, 196 .ignore_flags = FAN_EVENT_ON_CHILD, 197 .event_path = DIR_NAME, 198 .nevents = 2, 199 .nonfirst_event = FAN_CLOSE_NOWRITE, 200 }, 201 { 202 .tname = "Events on subdir with ignore mask on parent that does apply to subdirs", 203 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT), 204 .ondir = FAN_ONDIR, 205 .ignore = FAN_MARK_IGNORE_SURV, 206 .ignore_flags = FAN_EVENT_ON_CHILD | FAN_ONDIR, 207 .event_path = DIR_NAME, 208 .nevents = 2, 209 }, 210}; 211 212static void create_fanotify_groups(struct tcase *tc) 213{ 214 struct fanotify_mark_type *mark = &tc->mark; 215 int i; 216 217 for (i = 0; i < NUM_GROUPS; i++) { 218 /* 219 * The first group may request events with filename info and 220 * events on subdirs and always request events on children. 221 */ 222 unsigned int report_name = tc->report_name; 223 unsigned int mask_flags = tc->ondir | FAN_EVENT_ON_CHILD; 224 unsigned int parent_mask, ignore_mask, ignore = 0; 225 226 /* 227 * The non-first groups may request events on children and 228 * subdirs only when setting an ignore mask on parent dir. 229 * The parent ignore mask may request to ignore events on 230 * children or subdirs. 231 */ 232 if (i > 0) { 233 ignore = tc->ignore; 234 report_name = 0; 235 if (!ignore) 236 mask_flags = 0; 237 } 238 239 fd_notify[i] = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | report_name | 240 FAN_NONBLOCK, O_RDONLY); 241 242 /* 243 * Add subdir or mount mark for each group with CLOSE event, 244 * but only the first group requests events on dir. 245 */ 246 SAFE_FANOTIFY_MARK(fd_notify[i], 247 FAN_MARK_ADD | mark->flag, 248 FAN_CLOSE_NOWRITE | mask_flags, 249 AT_FDCWD, tc->event_path); 250 251 /* 252 * Add inode mark on parent for each group with MODIFY event, 253 * but only the first group requests events on child. 254 * The one mark with FAN_EVENT_ON_CHILD is needed for 255 * setting the DCACHE_FSNOTIFY_PARENT_WATCHED dentry flag. 256 * 257 * The inode mark on non-first group is either with FAN_MODIFY 258 * in mask or FAN_CLOSE_NOWRITE in ignore mask. In either case, 259 * it is not expected to get the modify event on a child, nor 260 * the close event on dir. 261 */ 262 parent_mask = FAN_MODIFY | tc->ondir | mask_flags; 263 ignore_mask = FAN_CLOSE_NOWRITE | tc->ignore_flags; 264 SAFE_FANOTIFY_MARK(fd_notify[i], FAN_MARK_ADD | ignore, 265 ignore ? ignore_mask : parent_mask, 266 AT_FDCWD, "."); 267 } 268} 269 270static void cleanup_fanotify_groups(void) 271{ 272 unsigned int i; 273 274 for (i = 0; i < NUM_GROUPS; i++) { 275 if (fd_notify[i] > 0) 276 SAFE_CLOSE(fd_notify[i]); 277 } 278} 279 280static void check_ignore_mask(int fd) 281{ 282 unsigned int ignored_mask, mflags; 283 char procfdinfo[100]; 284 285 sprintf(procfdinfo, "/proc/%d/fdinfo/%d", (int)getpid(), fd); 286 if (FILE_LINES_SCANF(procfdinfo, "fanotify ino:%*x sdev:%*x mflags: %x mask:0 ignored_mask:%x", 287 &mflags, &ignored_mask) || !ignored_mask) { 288 tst_res(TFAIL, "The ignore mask did not survive"); 289 } else { 290 tst_res(TPASS, "Found mark with ignore mask (ignored_mask=%x, mflags=%x) in %s", 291 ignored_mask, mflags, procfdinfo); 292 } 293} 294 295static void event_res(int ttype, int group, 296 struct fanotify_event_metadata *event, 297 const char *filename) 298{ 299 if (event->fd != FAN_NOFD) { 300 int len = 0; 301 302 sprintf(symlnk, "/proc/self/fd/%d", event->fd); 303 len = readlink(symlnk, fdpath, sizeof(fdpath)); 304 if (len < 0) 305 len = 0; 306 fdpath[len] = 0; 307 filename = fdpath; 308 } 309 310 tst_res(ttype, "group %d got event: mask %llx pid=%u fd=%d filename=%s", 311 group, (unsigned long long)event->mask, 312 (unsigned int)event->pid, event->fd, filename); 313} 314 315static const char *event_filename(struct fanotify_event_metadata *event) 316{ 317 struct fanotify_event_info_fid *event_fid; 318 struct file_handle *file_handle; 319 const char *filename, *end; 320 321 if (event->event_len <= FAN_EVENT_METADATA_LEN) 322 return ""; 323 324 event_fid = (struct fanotify_event_info_fid *)(event + 1); 325 file_handle = (struct file_handle *)event_fid->handle; 326 filename = (char *)file_handle->f_handle + file_handle->handle_bytes; 327 end = (char *)event_fid + event_fid->hdr.len; 328 329 /* End of event_fid could have name, zero padding, both or none */ 330 return (filename == end) ? "" : filename; 331} 332 333static void verify_event(int group, struct fanotify_event_metadata *event, 334 uint32_t expect, const char *expect_filename) 335{ 336 const char *filename = event_filename(event); 337 338 if (event->mask != expect) { 339 tst_res(TFAIL, "group %d got event: mask %llx (expected %llx) " 340 "pid=%u fd=%d filename=%s", group, (unsigned long long)event->mask, 341 (unsigned long long)expect, 342 (unsigned int)event->pid, event->fd, filename); 343 } else if (event->pid != getpid()) { 344 tst_res(TFAIL, "group %d got event: mask %llx pid=%u " 345 "(expected %u) fd=%d filename=%s", group, 346 (unsigned long long)event->mask, (unsigned int)event->pid, 347 (unsigned int)getpid(), event->fd, filename); 348 } else if (strcmp(filename, expect_filename)) { 349 tst_res(TFAIL, "group %d got event: mask %llx pid=%u " 350 "fd=%d filename='%s' (expected '%s')", group, 351 (unsigned long long)event->mask, (unsigned int)event->pid, 352 event->fd, filename, expect_filename); 353 } else { 354 event_res(TPASS, group, event, filename); 355 } 356 if (event->fd != FAN_NOFD) 357 SAFE_CLOSE(event->fd); 358} 359 360static void close_event_fds(struct fanotify_event_metadata *event, int buflen) 361{ 362 /* Close all file descriptors of read events */ 363 for (; FAN_EVENT_OK(event, buflen); FAN_EVENT_NEXT(event, buflen)) { 364 if (event->fd != FAN_NOFD) 365 SAFE_CLOSE(event->fd); 366 } 367} 368 369static void test_fanotify(unsigned int n) 370{ 371 int ret, dirfd; 372 unsigned int i; 373 struct fanotify_event_metadata *event; 374 struct tcase *tc = &tcases[n]; 375 376 tst_res(TINFO, "Test #%d: %s", n, tc->tname); 377 378 if (fan_report_dfid_unsupported && tc->report_name) { 379 FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_DFID_NAME, fan_report_dfid_unsupported); 380 return; 381 } 382 383 if (tc->ignore && tst_kvercmp(5, 19, 0) < 0) { 384 tst_res(TCONF, "ignored mask on parent dir has undefined " 385 "behavior on kernel < 5.19"); 386 return; 387 } 388 389 if (ignore_mark_unsupported && tc->ignore & FAN_MARK_IGNORE) { 390 tst_res(TCONF, "FAN_MARK_IGNORE not supported in kernel?"); 391 return; 392 } 393 394 create_fanotify_groups(tc); 395 396 /* 397 * generate MODIFY event and no FAN_CLOSE_NOWRITE event. 398 */ 399 SAFE_FILE_PRINTF(fname, "1"); 400 /* 401 * generate FAN_CLOSE_NOWRITE event on a child, subdir or "." 402 */ 403 dirfd = SAFE_OPEN(tc->event_path, O_RDONLY); 404 SAFE_CLOSE(dirfd); 405 406 /* 407 * First verify the first group got the file MODIFY event and got just 408 * one FAN_CLOSE_NOWRITE event. 409 */ 410 ret = read(fd_notify[0], event_buf, EVENT_BUF_LEN); 411 if (ret < 0) { 412 if (errno == EAGAIN) { 413 tst_res(TFAIL, "first group did not get event"); 414 } else { 415 tst_brk(TBROK | TERRNO, 416 "reading fanotify events failed"); 417 } 418 } 419 event = (struct fanotify_event_metadata *)event_buf; 420 if (ret < tc->nevents * (int)FAN_EVENT_METADATA_LEN) { 421 tst_res(TFAIL, 422 "short read when reading fanotify events (%d < %d)", 423 ret, tc->nevents * (int)FAN_EVENT_METADATA_LEN); 424 } 425 if (FAN_EVENT_OK(event, ret)) { 426 verify_event(0, event, FAN_MODIFY, tc->report_name ? fname : ""); 427 event = FAN_EVENT_NEXT(event, ret); 428 } 429 if (tc->nevents > 1 && FAN_EVENT_OK(event, ret)) { 430 verify_event(0, event, FAN_CLOSE_NOWRITE, 431 tc->report_name ? (tc->ondir ? "." : tc->event_path) : ""); 432 event = FAN_EVENT_NEXT(event, ret); 433 } 434 if (ret > 0) { 435 tst_res(TFAIL, 436 "first group got more than %d events (%d bytes)", 437 tc->nevents, ret); 438 } 439 close_event_fds(event, ret); 440 441 /* 442 * Then verify the rest of the groups did not get the MODIFY event and 443 * got the FAN_CLOSE_NOWRITE event only on a non-directory. 444 */ 445 for (i = 1; i < NUM_GROUPS; i++) { 446 /* 447 * Verify that ignore mask survived the modify event on child, 448 * which was not supposed to be sent to this group. 449 */ 450 if (tc->ignore) 451 check_ignore_mask(fd_notify[i]); 452 453 ret = read(fd_notify[i], event_buf, EVENT_BUF_LEN); 454 if (ret > 0) { 455 event = (struct fanotify_event_metadata *)event_buf; 456 verify_event(i, event, tc->nonfirst_event, ""); 457 event = FAN_EVENT_NEXT(event, ret); 458 459 close_event_fds(event, ret); 460 continue; 461 } 462 463 if (ret == 0) { 464 tst_res(TFAIL, "group %d zero length read from fanotify fd", i); 465 continue; 466 } 467 468 if (errno != EAGAIN) { 469 tst_brk(TBROK | TERRNO, 470 "reading fanotify events failed"); 471 } 472 473 if (tc->nonfirst_event) 474 tst_res(TFAIL, "group %d expected and got no event", i); 475 else 476 tst_res(TPASS, "group %d got no event as expected", i); 477 } 478 cleanup_fanotify_groups(); 479} 480 481static void setup(void) 482{ 483 fan_report_dfid_unsupported = fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME, 484 MOUNT_PATH); 485 ignore_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_IGNORE_SURV); 486 487 SAFE_MKDIR(MOUNT_NAME, 0755); 488 SAFE_MOUNT(MOUNT_PATH, MOUNT_NAME, "none", MS_BIND, NULL); 489 mount_created = 1; 490 SAFE_CHDIR(MOUNT_NAME); 491 SAFE_MKDIR(DIR_NAME, 0755); 492 493 sprintf(fname, "tfile_%d", getpid()); 494 SAFE_FILE_PRINTF(fname, "1"); 495 SAFE_FILE_PRINTF(FILE2_NAME, "1"); 496} 497 498static void cleanup(void) 499{ 500 cleanup_fanotify_groups(); 501 502 SAFE_CHDIR("../"); 503 504 if (mount_created) 505 SAFE_UMOUNT(MOUNT_NAME); 506} 507 508static struct tst_test test = { 509 .test = test_fanotify, 510 .tcnt = ARRAY_SIZE(tcases), 511 .setup = setup, 512 .cleanup = cleanup, 513 .mount_device = 1, 514 .mntpoint = MOUNT_PATH, 515 .needs_root = 1, 516 .tags = (const struct tst_tag[]) { 517 {"linux-git", "54a307ba8d3c"}, 518 {"linux-git", "b469e7e47c8a"}, 519 {"linux-git", "55bf882c7f13"}, 520 {"linux-git", "7372e79c9eb9"}, 521 {} 522 } 523}; 524 525#else 526 TST_TEST_TCONF("system doesn't have required fanotify support"); 527#endif 528