1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2025 Klara, Inc.
5 */
6
7 #include <sys/capsicum.h>
8 #include <sys/filio.h>
9 #include <sys/inotify.h>
10 #include <sys/ioccom.h>
11 #include <sys/mount.h>
12 #include <sys/socket.h>
13 #include <sys/stat.h>
14 #include <sys/sysctl.h>
15 #include <sys/un.h>
16
17 #include <dirent.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <limits.h>
21 #include <mntopts.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include <atf-c.h>
28
29 static const char *
ev2name(int event)30 ev2name(int event)
31 {
32 switch (event) {
33 case IN_ACCESS:
34 return ("IN_ACCESS");
35 case IN_ATTRIB:
36 return ("IN_ATTRIB");
37 case IN_CLOSE_WRITE:
38 return ("IN_CLOSE_WRITE");
39 case IN_CLOSE_NOWRITE:
40 return ("IN_CLOSE_NOWRITE");
41 case IN_CREATE:
42 return ("IN_CREATE");
43 case IN_DELETE:
44 return ("IN_DELETE");
45 case IN_DELETE_SELF:
46 return ("IN_DELETE_SELF");
47 case IN_MODIFY:
48 return ("IN_MODIFY");
49 case IN_MOVE_SELF:
50 return ("IN_MOVE_SELF");
51 case IN_MOVED_FROM:
52 return ("IN_MOVED_FROM");
53 case IN_MOVED_TO:
54 return ("IN_MOVED_TO");
55 case IN_OPEN:
56 return ("IN_OPEN");
57 default:
58 return (NULL);
59 }
60 }
61
62 static void
close_checked(int fd)63 close_checked(int fd)
64 {
65 ATF_REQUIRE(close(fd) == 0);
66 }
67
68 /*
69 * Make sure that no other events are pending, and close the inotify descriptor.
70 */
71 static void
close_inotify(int fd)72 close_inotify(int fd)
73 {
74 int n;
75
76 ATF_REQUIRE(ioctl(fd, FIONREAD, &n) == 0);
77 ATF_REQUIRE(n == 0);
78 close_checked(fd);
79 }
80
81 static uint32_t
consume_event_cookie(int ifd,int wd,unsigned int event,unsigned int flags,const char * name)82 consume_event_cookie(int ifd, int wd, unsigned int event, unsigned int flags,
83 const char *name)
84 {
85 struct inotify_event *ev;
86 size_t evsz, namelen;
87 ssize_t n;
88 uint32_t cookie;
89
90 /* Only read one record. */
91 namelen = name == NULL ? 0 : strlen(name);
92 evsz = sizeof(*ev) + _IN_NAMESIZE(namelen);
93 ev = malloc(evsz);
94 ATF_REQUIRE(ev != NULL);
95
96 n = read(ifd, ev, evsz);
97 ATF_REQUIRE_MSG(n >= 0, "failed to read event %s", ev2name(event));
98 ATF_REQUIRE((size_t)n >= sizeof(*ev));
99 ATF_REQUIRE((size_t)n == sizeof(*ev) + ev->len);
100 ATF_REQUIRE((size_t)n == evsz);
101
102 ATF_REQUIRE_MSG((ev->mask & IN_ALL_EVENTS) == event,
103 "expected event %#x, got %#x", event, ev->mask);
104 ATF_REQUIRE_MSG((ev->mask & _IN_ALL_RETFLAGS) == flags,
105 "expected flags %#x, got %#x", flags, ev->mask);
106 ATF_REQUIRE_MSG(ev->wd == wd,
107 "expected wd %d, got %d", wd, ev->wd);
108 ATF_REQUIRE_MSG(name == NULL || strcmp(name, ev->name) == 0,
109 "expected name '%s', got '%s'", name, ev->name);
110 cookie = ev->cookie;
111 if ((ev->mask & (IN_MOVED_FROM | IN_MOVED_TO)) == 0)
112 ATF_REQUIRE(cookie == 0);
113 free(ev);
114 return (cookie);
115 }
116
117 /*
118 * Read an event from the inotify file descriptor and check that it
119 * matches the expected values.
120 */
121 static void
consume_event(int ifd,int wd,unsigned int event,unsigned int flags,const char * name)122 consume_event(int ifd, int wd, unsigned int event, unsigned int flags,
123 const char *name)
124 {
125 (void)consume_event_cookie(ifd, wd, event, flags, name);
126 }
127
128 static int
inotify(int flags)129 inotify(int flags)
130 {
131 int ifd;
132
133 ifd = inotify_init1(flags);
134 ATF_REQUIRE(ifd != -1);
135 return (ifd);
136 }
137
138 static void
mount_nullfs(char * dir,char * src)139 mount_nullfs(char *dir, char *src)
140 {
141 struct iovec *iov;
142 char errmsg[1024];
143 int error, iovlen;
144
145 iov = NULL;
146 iovlen = 0;
147
148 build_iovec(&iov, &iovlen, "fstype", "nullfs", (size_t)-1);
149 build_iovec(&iov, &iovlen, "fspath", dir, (size_t)-1);
150 build_iovec(&iov, &iovlen, "target", src, (size_t)-1);
151 build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
152
153 errmsg[0] = '\0';
154 error = nmount(iov, iovlen, 0);
155 ATF_REQUIRE_MSG(error == 0,
156 "mount nullfs %s %s: %s", src, dir,
157 errmsg[0] == '\0' ? strerror(errno) : errmsg);
158
159 free_iovec(&iov, &iovlen);
160 }
161
162 static void
mount_tmpfs(const char * dir)163 mount_tmpfs(const char *dir)
164 {
165 struct iovec *iov;
166 char errmsg[1024];
167 int error, iovlen;
168
169 iov = NULL;
170 iovlen = 0;
171
172 build_iovec(&iov, &iovlen, "fstype", "tmpfs", (size_t)-1);
173 build_iovec(&iov, &iovlen, "fspath", __DECONST(char *, dir),
174 (size_t)-1);
175 build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
176
177 errmsg[0] = '\0';
178 error = nmount(iov, iovlen, 0);
179 ATF_REQUIRE_MSG(error == 0,
180 "mount tmpfs %s: %s", dir,
181 errmsg[0] == '\0' ? strerror(errno) : errmsg);
182
183 free_iovec(&iov, &iovlen);
184 }
185
186 static int
watch_file(int ifd,int events,char * path)187 watch_file(int ifd, int events, char *path)
188 {
189 int fd, wd;
190
191 strncpy(path, "test.XXXXXX", PATH_MAX);
192 fd = mkstemp(path);
193 ATF_REQUIRE(fd != -1);
194 close_checked(fd);
195
196 wd = inotify_add_watch(ifd, path, events);
197 ATF_REQUIRE(wd != -1);
198
199 return (wd);
200 }
201
202 static int
watch_dir(int ifd,int events,char * path)203 watch_dir(int ifd, int events, char *path)
204 {
205 char *p;
206 int wd;
207
208 strlcpy(path, "test.XXXXXX", PATH_MAX);
209 p = mkdtemp(path);
210 ATF_REQUIRE(p == path);
211
212 wd = inotify_add_watch(ifd, path, events);
213 ATF_REQUIRE(wd != -1);
214
215 return (wd);
216 }
217
218 /*
219 * Verify that Capsicum restrictions are applied as expected.
220 */
221 ATF_TC_WITHOUT_HEAD(inotify_capsicum);
ATF_TC_BODY(inotify_capsicum,tc)222 ATF_TC_BODY(inotify_capsicum, tc)
223 {
224 int error, dfd, ifd, wd;
225
226 ifd = inotify(IN_NONBLOCK);
227 ATF_REQUIRE(ifd != -1);
228
229 dfd = open(".", O_RDONLY | O_DIRECTORY);
230 ATF_REQUIRE(dfd != -1);
231
232 error = mkdirat(dfd, "testdir", 0755);
233 ATF_REQUIRE(error == 0);
234
235 error = cap_enter();
236 ATF_REQUIRE(error == 0);
237
238 /*
239 * Plain inotify_add_watch() is disallowed.
240 */
241 wd = inotify_add_watch(ifd, ".", IN_DELETE_SELF);
242 ATF_REQUIRE_ERRNO(ECAPMODE, wd == -1);
243 wd = inotify_add_watch_at(ifd, dfd, "testdir", IN_DELETE_SELF);
244 ATF_REQUIRE(wd >= 0);
245
246 /*
247 * Generate a record and consume it.
248 */
249 error = unlinkat(dfd, "testdir", AT_REMOVEDIR);
250 ATF_REQUIRE(error == 0);
251 consume_event(ifd, wd, IN_DELETE_SELF, IN_ISDIR, NULL);
252 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
253
254 close_checked(dfd);
255 close_inotify(ifd);
256 }
257
258 /*
259 * Make sure that duplicate, back-to-back events are coalesced.
260 */
261 ATF_TC_WITHOUT_HEAD(inotify_coalesce);
ATF_TC_BODY(inotify_coalesce,tc)262 ATF_TC_BODY(inotify_coalesce, tc)
263 {
264 char file[PATH_MAX], path[PATH_MAX];
265 int fd, fd1, ifd, n, wd;
266
267 ifd = inotify(IN_NONBLOCK);
268
269 /* Create a directory and watch it. */
270 wd = watch_dir(ifd, IN_OPEN, path);
271 /* Create a file in the directory and open it. */
272 snprintf(file, sizeof(file), "%s/file", path);
273 fd = open(file, O_RDWR | O_CREAT, 0644);
274 ATF_REQUIRE(fd != -1);
275 close_checked(fd);
276 fd = open(file, O_RDWR);
277 ATF_REQUIRE(fd != -1);
278 fd1 = open(file, O_RDONLY);
279 ATF_REQUIRE(fd1 != -1);
280 close_checked(fd1);
281 close_checked(fd);
282
283 consume_event(ifd, wd, IN_OPEN, 0, "file");
284 ATF_REQUIRE(ioctl(ifd, FIONREAD, &n) == 0);
285 ATF_REQUIRE(n == 0);
286
287 close_inotify(ifd);
288 }
289
290 /*
291 * Check handling of IN_MASK_CREATE.
292 */
293 ATF_TC_WITHOUT_HEAD(inotify_mask_create);
ATF_TC_BODY(inotify_mask_create,tc)294 ATF_TC_BODY(inotify_mask_create, tc)
295 {
296 char path[PATH_MAX];
297 int ifd, wd, wd1;
298
299 ifd = inotify(IN_NONBLOCK);
300
301 /* Create a directory and watch it. */
302 wd = watch_dir(ifd, IN_CREATE, path);
303 /* Updating the watch with IN_MASK_CREATE should result in an error. */
304 wd1 = inotify_add_watch(ifd, path, IN_MODIFY | IN_MASK_CREATE);
305 ATF_REQUIRE_ERRNO(EEXIST, wd1 == -1);
306 /* It's an error to specify IN_MASK_ADD with IN_MASK_CREATE. */
307 wd1 = inotify_add_watch(ifd, path, IN_MODIFY | IN_MASK_ADD |
308 IN_MASK_CREATE);
309 ATF_REQUIRE_ERRNO(EINVAL, wd1 == -1);
310 /* Updating the watch without IN_MASK_CREATE should work. */
311 wd1 = inotify_add_watch(ifd, path, IN_MODIFY);
312 ATF_REQUIRE(wd1 != -1);
313 ATF_REQUIRE_EQ(wd, wd1);
314
315 close_inotify(ifd);
316 }
317
318 /*
319 * Make sure that inotify cooperates with nullfs: if a lower vnode is the
320 * subject of an event, the upper vnode should be notified, and if the upper
321 * vnode is the subject of an event, the lower vnode should be notified.
322 */
323 ATF_TC_WITH_CLEANUP(inotify_nullfs);
ATF_TC_HEAD(inotify_nullfs,tc)324 ATF_TC_HEAD(inotify_nullfs, tc)
325 {
326 atf_tc_set_md_var(tc, "require.user", "root");
327 }
ATF_TC_BODY(inotify_nullfs,tc)328 ATF_TC_BODY(inotify_nullfs, tc)
329 {
330 char path[PATH_MAX], *p;
331 int dfd, error, fd, ifd, mask, wd;
332
333 mask = IN_CREATE | IN_OPEN;
334
335 ifd = inotify(IN_NONBLOCK);
336
337 strlcpy(path, "./test.XXXXXX", sizeof(path));
338 p = mkdtemp(path);
339 ATF_REQUIRE(p == path);
340
341 error = mkdir("./mnt", 0755);
342 ATF_REQUIRE(error == 0);
343
344 /* Mount the testdir onto ./mnt. */
345 mount_nullfs("./mnt", path);
346
347 wd = inotify_add_watch(ifd, "./mnt", mask);
348 ATF_REQUIRE(wd != -1);
349
350 /* Create a file in the lower directory and open it. */
351 dfd = open(path, O_RDONLY | O_DIRECTORY);
352 ATF_REQUIRE(dfd != -1);
353 fd = openat(dfd, "file", O_RDWR | O_CREAT, 0644);
354 close_checked(fd);
355 close_checked(dfd);
356
357 /* We should see events via the nullfs mount. */
358 consume_event(ifd, wd, IN_OPEN, IN_ISDIR, NULL);
359 consume_event(ifd, wd, IN_CREATE, 0, "file");
360 consume_event(ifd, wd, IN_OPEN, 0, "file");
361
362 error = inotify_rm_watch(ifd, wd);
363 ATF_REQUIRE(error == 0);
364 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
365
366 /* Watch the lower directory. */
367 wd = inotify_add_watch(ifd, path, mask);
368 ATF_REQUIRE(wd != -1);
369 /* ... and create a file in the upper directory and open it. */
370 dfd = open("./mnt", O_RDONLY | O_DIRECTORY);
371 ATF_REQUIRE(dfd != -1);
372 fd = openat(dfd, "file2", O_RDWR | O_CREAT, 0644);
373 ATF_REQUIRE(fd != -1);
374 close_checked(fd);
375 close_checked(dfd);
376
377 /* We should see events via the lower directory. */
378 consume_event(ifd, wd, IN_OPEN, IN_ISDIR, NULL);
379 consume_event(ifd, wd, IN_CREATE, 0, "file2");
380 consume_event(ifd, wd, IN_OPEN, 0, "file2");
381
382 close_inotify(ifd);
383 }
ATF_TC_CLEANUP(inotify_nullfs,tc)384 ATF_TC_CLEANUP(inotify_nullfs, tc)
385 {
386 int error;
387
388 error = unmount("./mnt", 0);
389 if (error != 0) {
390 perror("unmount");
391 exit(1);
392 }
393 }
394
395 /*
396 * Watch a file in a nullfs mount, and remove it from the lower mount. Make
397 * sure that we get an IN_DELETE_SELF event and that the watch is removed.
398 */
399 ATF_TC_WITH_CLEANUP(inotify_nullfs_remove);
ATF_TC_HEAD(inotify_nullfs_remove,tc)400 ATF_TC_HEAD(inotify_nullfs_remove, tc)
401 {
402 atf_tc_set_md_var(tc, "require.user", "root");
403 }
ATF_TC_BODY(inotify_nullfs_remove,tc)404 ATF_TC_BODY(inotify_nullfs_remove, tc)
405 {
406 char dir[PATH_MAX], path[PATH_MAX], *p;
407 int error, fd, ifd, wd;
408
409 strlcpy(dir, "./test.XXXXXX", sizeof(dir));
410 p = mkdtemp(dir);
411 ATF_REQUIRE(p == dir);
412
413 error = mkdir("./mnt", 0755);
414 ATF_REQUIRE(error == 0);
415
416 /* Mount the testdir onto ./mnt. */
417 mount_nullfs("./mnt", dir);
418
419 snprintf(path, sizeof(path), "%s/file", dir);
420 fd = open(path, O_RDWR | O_CREAT, 0644);
421 ATF_REQUIRE(fd != -1);
422 close_checked(fd);
423
424 ifd = inotify(IN_NONBLOCK);
425 wd = inotify_add_watch(ifd, "./mnt/file", IN_DELETE_SELF);
426 ATF_REQUIRE(wd != -1);
427
428 error = unlink(path);
429 ATF_REQUIRE(error == 0);
430
431 consume_event(ifd, wd, IN_DELETE_SELF, 0, NULL);
432 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
433
434 close_inotify(ifd);
435 }
ATF_TC_CLEANUP(inotify_nullfs_remove,tc)436 ATF_TC_CLEANUP(inotify_nullfs_remove, tc)
437 {
438 int error;
439
440 error = unmount("./mnt", 0);
441 if (error != 0) {
442 perror("unmount");
443 exit(1);
444 }
445 }
446
447 /*
448 * Exercise a scenario where a watched lower vnode is deleted by a rename. The
449 * deletion causes the upper vnode to be reclaimed, and after that point it
450 * should stop trying to forward events back to the (now detached) lower vnode.
451 */
452 ATF_TC_WITH_CLEANUP(inotify_nullfs_rename);
ATF_TC_HEAD(inotify_nullfs_rename,tc)453 ATF_TC_HEAD(inotify_nullfs_rename, tc)
454 {
455 atf_tc_set_md_var(tc, "require.user", "root");
456 }
ATF_TC_BODY(inotify_nullfs_rename,tc)457 ATF_TC_BODY(inotify_nullfs_rename, tc)
458 {
459 char dir[PATH_MAX], path1[PATH_MAX], path2[PATH_MAX], *p;
460 int error, fd, ifd, wd;
461
462 strlcpy(dir, "./test.XXXXXX", sizeof(dir));
463 p = mkdtemp(dir);
464 ATF_REQUIRE(p == dir);
465
466 error = mkdir("./mnt", 0755);
467 ATF_REQUIRE(error == 0);
468
469 /* Mount the testdir onto ./mnt. */
470 mount_nullfs("./mnt", dir);
471
472 ifd = inotify(IN_NONBLOCK);
473
474 /* Create two files, they will be renamed in the upper layer. */
475 snprintf(path1, sizeof(path1), "%s/file1", dir);
476 fd = open(path1, O_RDWR | O_CREAT, 0644);
477 ATF_REQUIRE(fd != -1);
478 close_checked(fd);
479 snprintf(path2, sizeof(path2), "%s/file2", dir);
480 fd = open(path2, O_RDWR | O_CREAT, 0644);
481 ATF_REQUIRE(fd != -1);
482 close_checked(fd);
483
484 wd = inotify_add_watch(ifd, "./mnt/file1", IN_DELETE_SELF);
485 ATF_REQUIRE(wd != -1);
486 error = rename("./mnt/file2", "./mnt/file1");
487 ATF_REQUIRE(error == 0);
488
489 consume_event(ifd, wd, IN_DELETE_SELF, 0, NULL);
490 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
491
492 close_inotify(ifd);
493 }
ATF_TC_CLEANUP(inotify_nullfs_rename,tc)494 ATF_TC_CLEANUP(inotify_nullfs_rename, tc)
495 {
496 int error;
497
498 error = unmount("./mnt", 0);
499 if (error != 0) {
500 perror("unmount");
501 exit(1);
502 }
503 }
504
505 /*
506 * Make sure that exceeding max_events pending events results in an overflow
507 * event.
508 */
509 ATF_TC_WITHOUT_HEAD(inotify_queue_overflow);
ATF_TC_BODY(inotify_queue_overflow,tc)510 ATF_TC_BODY(inotify_queue_overflow, tc)
511 {
512 char path[PATH_MAX];
513 size_t size;
514 int error, dfd, ifd, max, wd;
515
516 size = sizeof(max);
517 error = sysctlbyname("vfs.inotify.max_queued_events", &max, &size, NULL,
518 0);
519 ATF_REQUIRE(error == 0);
520
521 ifd = inotify(IN_NONBLOCK);
522
523 /* Create a directory and watch it for file creation events. */
524 wd = watch_dir(ifd, IN_CREATE, path);
525 dfd = open(path, O_DIRECTORY);
526 ATF_REQUIRE(dfd != -1);
527 /* Generate max+1 file creation events. */
528 for (int i = 0; i < max + 1; i++) {
529 char name[NAME_MAX];
530 int fd;
531
532 (void)snprintf(name, sizeof(name), "file%d", i);
533 fd = openat(dfd, name, O_CREAT | O_RDWR, 0644);
534 ATF_REQUIRE(fd != -1);
535 close_checked(fd);
536 }
537
538 /*
539 * Read our events. We should see files 0..max-1 and then an overflow
540 * event.
541 */
542 for (int i = 0; i < max; i++) {
543 char name[NAME_MAX];
544
545 (void)snprintf(name, sizeof(name), "file%d", i);
546 consume_event(ifd, wd, IN_CREATE, 0, name);
547 }
548
549 /* Look for an overflow event. */
550 consume_event(ifd, -1, 0, IN_Q_OVERFLOW, NULL);
551
552 close_checked(dfd);
553 close_inotify(ifd);
554 }
555
556 ATF_TC_WITHOUT_HEAD(inotify_event_access_file);
ATF_TC_BODY(inotify_event_access_file,tc)557 ATF_TC_BODY(inotify_event_access_file, tc)
558 {
559 char path[PATH_MAX], buf[16];
560 off_t nb;
561 ssize_t n;
562 int error, fd, fd1, ifd, s[2], wd;
563
564 ifd = inotify(IN_NONBLOCK);
565
566 wd = watch_file(ifd, IN_ACCESS, path);
567
568 fd = open(path, O_RDWR);
569 n = write(fd, "test", 4);
570 ATF_REQUIRE(n == 4);
571
572 /* A simple read(2) should generate an access. */
573 ATF_REQUIRE(lseek(fd, 0, SEEK_SET) == 0);
574 n = read(fd, buf, sizeof(buf));
575 ATF_REQUIRE(n == 4);
576 ATF_REQUIRE(memcmp(buf, "test", 4) == 0);
577 consume_event(ifd, wd, IN_ACCESS, 0, NULL);
578
579 /* copy_file_range(2) should as well. */
580 ATF_REQUIRE(lseek(fd, 0, SEEK_SET) == 0);
581 fd1 = open("sink", O_RDWR | O_CREAT, 0644);
582 ATF_REQUIRE(fd1 != -1);
583 n = copy_file_range(fd, NULL, fd1, NULL, 4, 0);
584 ATF_REQUIRE(n == 4);
585 close_checked(fd1);
586 consume_event(ifd, wd, IN_ACCESS, 0, NULL);
587
588 /* As should sendfile(2). */
589 error = socketpair(AF_UNIX, SOCK_STREAM, 0, s);
590 ATF_REQUIRE(error == 0);
591 error = sendfile(fd, s[0], 0, 4, NULL, &nb, 0);
592 ATF_REQUIRE(error == 0);
593 ATF_REQUIRE(nb == 4);
594 consume_event(ifd, wd, IN_ACCESS, 0, NULL);
595 close_checked(s[0]);
596 close_checked(s[1]);
597
598 close_checked(fd);
599
600 close_inotify(ifd);
601 }
602
603 ATF_TC_WITHOUT_HEAD(inotify_event_access_dir);
ATF_TC_BODY(inotify_event_access_dir,tc)604 ATF_TC_BODY(inotify_event_access_dir, tc)
605 {
606 char root[PATH_MAX], path[PATH_MAX];
607 struct dirent *ent;
608 DIR *dir;
609 int error, ifd, wd;
610
611 ifd = inotify(IN_NONBLOCK);
612
613 wd = watch_dir(ifd, IN_ACCESS, root);
614 snprintf(path, sizeof(path), "%s/dir", root);
615 error = mkdir(path, 0755);
616 ATF_REQUIRE(error == 0);
617
618 /* Read an entry and generate an access. */
619 dir = opendir(path);
620 ATF_REQUIRE(dir != NULL);
621 ent = readdir(dir);
622 ATF_REQUIRE(ent != NULL);
623 ATF_REQUIRE(strcmp(ent->d_name, ".") == 0 ||
624 strcmp(ent->d_name, "..") == 0);
625 ATF_REQUIRE(closedir(dir) == 0);
626 consume_event(ifd, wd, IN_ACCESS, IN_ISDIR, "dir");
627
628 /*
629 * Reading the watched directory should generate an access event.
630 * This is contrary to Linux's inotify man page, which states that
631 * IN_ACCESS is only generated for accesses to objects in a watched
632 * directory.
633 */
634 dir = opendir(root);
635 ATF_REQUIRE(dir != NULL);
636 ent = readdir(dir);
637 ATF_REQUIRE(ent != NULL);
638 ATF_REQUIRE(strcmp(ent->d_name, ".") == 0 ||
639 strcmp(ent->d_name, "..") == 0);
640 ATF_REQUIRE(closedir(dir) == 0);
641 consume_event(ifd, wd, IN_ACCESS, IN_ISDIR, NULL);
642
643 close_inotify(ifd);
644 }
645
646 ATF_TC_WITHOUT_HEAD(inotify_event_attrib);
ATF_TC_BODY(inotify_event_attrib,tc)647 ATF_TC_BODY(inotify_event_attrib, tc)
648 {
649 char path[PATH_MAX];
650 int error, ifd, fd, wd;
651
652 ifd = inotify(IN_NONBLOCK);
653
654 wd = watch_file(ifd, IN_ATTRIB, path);
655
656 fd = open(path, O_RDWR);
657 ATF_REQUIRE(fd != -1);
658 error = fchmod(fd, 0600);
659 ATF_REQUIRE(error == 0);
660 consume_event(ifd, wd, IN_ATTRIB, 0, NULL);
661
662 error = fchown(fd, getuid(), getgid());
663 ATF_REQUIRE(error == 0);
664 consume_event(ifd, wd, IN_ATTRIB, 0, NULL);
665
666 close_checked(fd);
667 close_inotify(ifd);
668 }
669
670 ATF_TC_WITHOUT_HEAD(inotify_event_close_nowrite);
ATF_TC_BODY(inotify_event_close_nowrite,tc)671 ATF_TC_BODY(inotify_event_close_nowrite, tc)
672 {
673 char file[PATH_MAX], file1[PATH_MAX], dir[PATH_MAX];
674 int ifd, fd, wd1, wd2;
675
676 ifd = inotify(IN_NONBLOCK);
677
678 wd1 = watch_dir(ifd, IN_CLOSE_NOWRITE, dir);
679 wd2 = watch_file(ifd, IN_CLOSE_NOWRITE | IN_CLOSE_WRITE, file);
680
681 fd = open(dir, O_DIRECTORY);
682 ATF_REQUIRE(fd != -1);
683 close_checked(fd);
684 consume_event(ifd, wd1, IN_CLOSE_NOWRITE, IN_ISDIR, NULL);
685
686 fd = open(file, O_RDONLY);
687 ATF_REQUIRE(fd != -1);
688 close_checked(fd);
689 consume_event(ifd, wd2, IN_CLOSE_NOWRITE, 0, NULL);
690
691 snprintf(file1, sizeof(file1), "%s/file", dir);
692 fd = open(file1, O_RDONLY | O_CREAT, 0644);
693 ATF_REQUIRE(fd != -1);
694 close_checked(fd);
695 consume_event(ifd, wd1, IN_CLOSE_NOWRITE, 0, "file");
696
697 close_inotify(ifd);
698 }
699
700 ATF_TC_WITHOUT_HEAD(inotify_event_close_write);
ATF_TC_BODY(inotify_event_close_write,tc)701 ATF_TC_BODY(inotify_event_close_write, tc)
702 {
703 char path[PATH_MAX];
704 int ifd, fd, wd;
705
706 ifd = inotify(IN_NONBLOCK);
707
708 wd = watch_file(ifd, IN_CLOSE_NOWRITE | IN_CLOSE_WRITE, path);
709
710 fd = open(path, O_RDWR);
711 ATF_REQUIRE(fd != -1);
712 close_checked(fd);
713 consume_event(ifd, wd, IN_CLOSE_WRITE, 0, NULL);
714
715 close_inotify(ifd);
716 }
717
718 /* Verify that various operations in a directory generate IN_CREATE events. */
719 ATF_TC_WITHOUT_HEAD(inotify_event_create);
ATF_TC_BODY(inotify_event_create,tc)720 ATF_TC_BODY(inotify_event_create, tc)
721 {
722 struct sockaddr_un sun;
723 char path[PATH_MAX], path1[PATH_MAX], root[PATH_MAX];
724 ssize_t n;
725 int error, ifd, ifd1, fd, s, wd, wd1;
726 char b;
727
728 ifd = inotify(IN_NONBLOCK);
729
730 wd = watch_dir(ifd, IN_CREATE, root);
731
732 /* Regular file. */
733 snprintf(path, sizeof(path), "%s/file", root);
734 fd = open(path, O_RDWR | O_CREAT, 0644);
735 ATF_REQUIRE(fd != -1);
736 /*
737 * Make sure we get an event triggered by the fd used to create the
738 * file.
739 */
740 ifd1 = inotify(IN_NONBLOCK);
741 wd1 = inotify_add_watch(ifd1, root, IN_MODIFY);
742 b = 42;
743 n = write(fd, &b, sizeof(b));
744 ATF_REQUIRE(n == sizeof(b));
745 close_checked(fd);
746 consume_event(ifd, wd, IN_CREATE, 0, "file");
747 consume_event(ifd1, wd1, IN_MODIFY, 0, "file");
748 close_inotify(ifd1);
749
750 /* Hard link. */
751 snprintf(path1, sizeof(path1), "%s/link", root);
752 error = link(path, path1);
753 ATF_REQUIRE(error == 0);
754 consume_event(ifd, wd, IN_CREATE, 0, "link");
755
756 /* Directory. */
757 snprintf(path, sizeof(path), "%s/dir", root);
758 error = mkdir(path, 0755);
759 ATF_REQUIRE(error == 0);
760 consume_event(ifd, wd, IN_CREATE, IN_ISDIR, "dir");
761
762 /* Symbolic link. */
763 snprintf(path1, sizeof(path1), "%s/symlink", root);
764 error = symlink(path, path1);
765 ATF_REQUIRE(error == 0);
766 consume_event(ifd, wd, IN_CREATE, 0, "symlink");
767
768 /* FIFO. */
769 snprintf(path, sizeof(path), "%s/fifo", root);
770 error = mkfifo(path, 0644);
771 ATF_REQUIRE(error == 0);
772 consume_event(ifd, wd, IN_CREATE, 0, "fifo");
773
774 /* Binding a socket. */
775 s = socket(AF_UNIX, SOCK_STREAM, 0);
776 memset(&sun, 0, sizeof(sun));
777 sun.sun_family = AF_UNIX;
778 sun.sun_len = sizeof(sun);
779 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/socket", root);
780 error = bind(s, (struct sockaddr *)&sun, sizeof(sun));
781 ATF_REQUIRE(error == 0);
782 close_checked(s);
783 consume_event(ifd, wd, IN_CREATE, 0, "socket");
784
785 close_inotify(ifd);
786 }
787
788 ATF_TC_WITHOUT_HEAD(inotify_event_delete);
ATF_TC_BODY(inotify_event_delete,tc)789 ATF_TC_BODY(inotify_event_delete, tc)
790 {
791 char root[PATH_MAX], path[PATH_MAX], file[PATH_MAX];
792 int error, fd, ifd, wd, wd2;
793
794 ifd = inotify(IN_NONBLOCK);
795
796 wd = watch_dir(ifd, IN_DELETE | IN_DELETE_SELF, root);
797
798 snprintf(path, sizeof(path), "%s/file", root);
799 fd = open(path, O_RDWR | O_CREAT, 0644);
800 ATF_REQUIRE(fd != -1);
801 error = unlink(path);
802 ATF_REQUIRE(error == 0);
803 consume_event(ifd, wd, IN_DELETE, 0, "file");
804 close_checked(fd);
805
806 /*
807 * Make sure that renaming over a file generates a delete event when and
808 * only when that file is watched.
809 */
810 fd = open(path, O_RDWR | O_CREAT, 0644);
811 ATF_REQUIRE(fd != -1);
812 close_checked(fd);
813 wd2 = inotify_add_watch(ifd, path, IN_DELETE | IN_DELETE_SELF);
814 ATF_REQUIRE(wd2 != -1);
815 snprintf(file, sizeof(file), "%s/file2", root);
816 fd = open(file, O_RDWR | O_CREAT, 0644);
817 ATF_REQUIRE(fd != -1);
818 close_checked(fd);
819 error = rename(file, path);
820 ATF_REQUIRE(error == 0);
821 consume_event(ifd, wd2, IN_DELETE_SELF, 0, NULL);
822 consume_event(ifd, wd2, 0, IN_IGNORED, NULL);
823
824 error = unlink(path);
825 ATF_REQUIRE(error == 0);
826 consume_event(ifd, wd, IN_DELETE, 0, "file");
827 error = rmdir(root);
828 ATF_REQUIRE(error == 0);
829 consume_event(ifd, wd, IN_DELETE_SELF, IN_ISDIR, NULL);
830 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
831
832 close_inotify(ifd);
833 }
834
835 ATF_TC_WITHOUT_HEAD(inotify_event_move);
ATF_TC_BODY(inotify_event_move,tc)836 ATF_TC_BODY(inotify_event_move, tc)
837 {
838 char dir1[PATH_MAX], dir2[PATH_MAX], path1[PATH_MAX], path2[PATH_MAX];
839 char path3[PATH_MAX];
840 int error, ifd, fd, wd1, wd2, wd3;
841 uint32_t cookie1, cookie2;
842
843 ifd = inotify(IN_NONBLOCK);
844
845 wd1 = watch_dir(ifd, IN_MOVE | IN_MOVE_SELF, dir1);
846 wd2 = watch_dir(ifd, IN_MOVE | IN_MOVE_SELF, dir2);
847
848 snprintf(path1, sizeof(path1), "%s/file", dir1);
849 fd = open(path1, O_RDWR | O_CREAT, 0644);
850 ATF_REQUIRE(fd != -1);
851 close_checked(fd);
852 snprintf(path2, sizeof(path2), "%s/file2", dir2);
853 error = rename(path1, path2);
854 ATF_REQUIRE(error == 0);
855 cookie1 = consume_event_cookie(ifd, wd1, IN_MOVED_FROM, 0, "file");
856 cookie2 = consume_event_cookie(ifd, wd2, IN_MOVED_TO, 0, "file2");
857 ATF_REQUIRE_MSG(cookie1 == cookie2,
858 "expected cookie %u, got %u", cookie1, cookie2);
859
860 snprintf(path2, sizeof(path2), "%s/dir", dir2);
861 error = rename(dir1, path2);
862 ATF_REQUIRE(error == 0);
863 consume_event(ifd, wd1, IN_MOVE_SELF, IN_ISDIR, NULL);
864 consume_event(ifd, wd2, IN_MOVED_TO, IN_ISDIR, "dir");
865
866 wd3 = watch_file(ifd, IN_MOVE_SELF, path3);
867 error = rename(path3, "foo");
868 ATF_REQUIRE(error == 0);
869 consume_event(ifd, wd3, IN_MOVE_SELF, 0, NULL);
870
871 close_inotify(ifd);
872 }
873
874 ATF_TC_WITHOUT_HEAD(inotify_event_move_dir);
ATF_TC_BODY(inotify_event_move_dir,tc)875 ATF_TC_BODY(inotify_event_move_dir, tc)
876 {
877 char dir[PATH_MAX], subdir1[PATH_MAX], subdir2[PATH_MAX];
878 uint32_t cookie1, cookie2;
879 int error, ifd, wd1, wd2;
880
881 ifd = inotify(IN_NONBLOCK);
882
883 wd1 = watch_dir(ifd, IN_MOVE, dir);
884 snprintf(subdir1, sizeof(subdir1), "%s/subdir", dir);
885 error = mkdir(subdir1, 0755);
886 ATF_REQUIRE(error == 0);
887 wd2 = inotify_add_watch(ifd, subdir1, IN_MOVE);
888 ATF_REQUIRE(wd2 != -1);
889
890 snprintf(subdir2, sizeof(subdir2), "%s/newsubdir", dir);
891 error = rename(subdir1, subdir2);
892 ATF_REQUIRE(error == 0);
893
894 cookie1 = consume_event_cookie(ifd, wd1, IN_MOVED_FROM, IN_ISDIR,
895 "subdir");
896 cookie2 = consume_event_cookie(ifd, wd1, IN_MOVED_TO, IN_ISDIR,
897 "newsubdir");
898 ATF_REQUIRE_MSG(cookie1 == cookie2,
899 "expected cookie %u, got %u", cookie1, cookie2);
900
901 close_inotify(ifd);
902 }
903
904 ATF_TC_WITHOUT_HEAD(inotify_event_open);
ATF_TC_BODY(inotify_event_open,tc)905 ATF_TC_BODY(inotify_event_open, tc)
906 {
907 char root[PATH_MAX], path[PATH_MAX];
908 int error, ifd, fd, wd;
909
910 ifd = inotify(IN_NONBLOCK);
911
912 wd = watch_dir(ifd, IN_OPEN, root);
913
914 snprintf(path, sizeof(path), "%s/file", root);
915 fd = open(path, O_RDWR | O_CREAT, 0644);
916 ATF_REQUIRE(fd != -1);
917 close_checked(fd);
918 consume_event(ifd, wd, IN_OPEN, 0, "file");
919
920 fd = open(path, O_PATH);
921 ATF_REQUIRE(fd != -1);
922 close_checked(fd);
923 consume_event(ifd, wd, IN_OPEN, 0, "file");
924
925 fd = open(root, O_DIRECTORY);
926 ATF_REQUIRE(fd != -1);
927 close_checked(fd);
928 consume_event(ifd, wd, IN_OPEN, IN_ISDIR, NULL);
929
930 snprintf(path, sizeof(path), "%s/fifo", root);
931 error = mkfifo(path, 0644);
932 ATF_REQUIRE(error == 0);
933 fd = open(path, O_RDWR);
934 ATF_REQUIRE(fd != -1);
935 close_checked(fd);
936 consume_event(ifd, wd, IN_OPEN, 0, "fifo");
937
938 close_inotify(ifd);
939 }
940
941 ATF_TC_WITH_CLEANUP(inotify_event_unmount);
ATF_TC_HEAD(inotify_event_unmount,tc)942 ATF_TC_HEAD(inotify_event_unmount, tc)
943 {
944 atf_tc_set_md_var(tc, "require.user", "root");
945 }
ATF_TC_BODY(inotify_event_unmount,tc)946 ATF_TC_BODY(inotify_event_unmount, tc)
947 {
948 int error, fd, ifd, wd;
949
950 ifd = inotify(IN_NONBLOCK);
951
952 error = mkdir("./root", 0755);
953 ATF_REQUIRE(error == 0);
954
955 mount_tmpfs("./root");
956
957 error = mkdir("./root/dir", 0755);
958 ATF_REQUIRE(error == 0);
959 wd = inotify_add_watch(ifd, "./root/dir", IN_OPEN);
960 ATF_REQUIRE(wd >= 0);
961
962 fd = open("./root/dir", O_RDONLY | O_DIRECTORY);
963 ATF_REQUIRE(fd != -1);
964 consume_event(ifd, wd, IN_OPEN, IN_ISDIR, NULL);
965 close_checked(fd);
966
967 /* A regular unmount should fail, as inotify holds a vnode reference. */
968 error = unmount("./root", 0);
969 ATF_REQUIRE_ERRNO(EBUSY, error == -1);
970 error = unmount("./root", MNT_FORCE);
971 ATF_REQUIRE_MSG(error == 0,
972 "unmounting ./root failed: %s", strerror(errno));
973
974 consume_event(ifd, wd, 0, IN_UNMOUNT, NULL);
975 consume_event(ifd, wd, 0, IN_IGNORED, NULL);
976
977 close_inotify(ifd);
978 }
ATF_TC_CLEANUP(inotify_event_unmount,tc)979 ATF_TC_CLEANUP(inotify_event_unmount, tc)
980 {
981 (void)unmount("./root", MNT_FORCE);
982 }
983
ATF_TP_ADD_TCS(tp)984 ATF_TP_ADD_TCS(tp)
985 {
986 /* Tests for the inotify syscalls. */
987 ATF_TP_ADD_TC(tp, inotify_capsicum);
988 ATF_TP_ADD_TC(tp, inotify_coalesce);
989 ATF_TP_ADD_TC(tp, inotify_mask_create);
990 ATF_TP_ADD_TC(tp, inotify_nullfs);
991 ATF_TP_ADD_TC(tp, inotify_nullfs_remove);
992 ATF_TP_ADD_TC(tp, inotify_nullfs_rename);
993 ATF_TP_ADD_TC(tp, inotify_queue_overflow);
994 /* Tests for the various inotify event types. */
995 ATF_TP_ADD_TC(tp, inotify_event_access_file);
996 ATF_TP_ADD_TC(tp, inotify_event_access_dir);
997 ATF_TP_ADD_TC(tp, inotify_event_attrib);
998 ATF_TP_ADD_TC(tp, inotify_event_close_nowrite);
999 ATF_TP_ADD_TC(tp, inotify_event_close_write);
1000 ATF_TP_ADD_TC(tp, inotify_event_create);
1001 ATF_TP_ADD_TC(tp, inotify_event_delete);
1002 ATF_TP_ADD_TC(tp, inotify_event_move);
1003 ATF_TP_ADD_TC(tp, inotify_event_move_dir);
1004 ATF_TP_ADD_TC(tp, inotify_event_open);
1005 ATF_TP_ADD_TC(tp, inotify_event_unmount);
1006 return (atf_no_error());
1007 }
1008