1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 Klara, Inc. 5 */ 6 7 /* 8 * A simple program to demonstrate inotify. Given one or more paths, it watches 9 * all events on those paths and prints them to standard output. 10 */ 11 12 #include <sys/types.h> 13 #include <sys/event.h> 14 #include <sys/inotify.h> 15 16 #include <assert.h> 17 #include <err.h> 18 #include <limits.h> 19 #include <signal.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <unistd.h> 23 24 #include <libxo/xo.h> 25 26 static void 27 usage(void) 28 { 29 xo_errx(1, "usage: inotify <path1> [<path2> ...]"); 30 } 31 32 static const char * 33 ev2str(uint32_t event) 34 { 35 switch (event & IN_ALL_EVENTS) { 36 case IN_ACCESS: 37 return ("IN_ACCESS"); 38 case IN_ATTRIB: 39 return ("IN_ATTRIB"); 40 case IN_CLOSE_WRITE: 41 return ("IN_CLOSE_WRITE"); 42 case IN_CLOSE_NOWRITE: 43 return ("IN_CLOSE_NOWRITE"); 44 case IN_CREATE: 45 return ("IN_CREATE"); 46 case IN_DELETE: 47 return ("IN_DELETE"); 48 case IN_DELETE_SELF: 49 return ("IN_DELETE_SELF"); 50 case IN_MODIFY: 51 return ("IN_MODIFY"); 52 case IN_MOVE_SELF: 53 return ("IN_MOVE_SELF"); 54 case IN_MOVED_FROM: 55 return ("IN_MOVED_FROM"); 56 case IN_MOVED_TO: 57 return ("IN_MOVED_TO"); 58 case IN_OPEN: 59 return ("IN_OPEN"); 60 default: 61 switch (event) { 62 case IN_IGNORED: 63 return ("IN_IGNORED"); 64 case IN_Q_OVERFLOW: 65 return ("IN_Q_OVERFLOW"); 66 case IN_UNMOUNT: 67 return ("IN_UNMOUNT"); 68 } 69 warnx("unknown event %#x", event); 70 assert(0); 71 } 72 } 73 74 static void 75 set_handler(int kq, int sig) 76 { 77 struct kevent kev; 78 79 (void)signal(sig, SIG_IGN); 80 EV_SET(&kev, sig, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 81 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) 82 xo_err(1, "kevent"); 83 } 84 85 int 86 main(int argc, char **argv) 87 { 88 struct inotify_event *iev, *iev1; 89 struct kevent kev; 90 size_t ievsz; 91 int ifd, kq; 92 93 argc = xo_parse_args(argc, argv); 94 if (argc < 2) 95 usage(); 96 argc--; 97 argv++; 98 99 ifd = inotify_init1(IN_NONBLOCK); 100 if (ifd < 0) 101 xo_err(1, "inotify"); 102 for (int i = 0; i < argc; i++) { 103 int wd; 104 105 wd = inotify_add_watch(ifd, argv[i], IN_ALL_EVENTS); 106 if (wd < 0) 107 xo_err(1, "inotify_add_watch(%s)", argv[i]); 108 } 109 110 xo_set_version("1"); 111 xo_open_list("events"); 112 113 kq = kqueue(); 114 if (kq < 0) 115 xo_err(1, "kqueue"); 116 117 /* 118 * Handle signals in the event loop so that we can close the xo list. 119 */ 120 set_handler(kq, SIGINT); 121 set_handler(kq, SIGTERM); 122 set_handler(kq, SIGHUP); 123 set_handler(kq, SIGQUIT); 124 125 /* 126 * Monitor the inotify descriptor for events. 127 */ 128 EV_SET(&kev, ifd, EVFILT_READ, EV_ADD, 0, 0, NULL); 129 if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) 130 xo_err(1, "kevent"); 131 132 ievsz = sizeof(*iev) + NAME_MAX + 1; 133 iev = malloc(ievsz); 134 if (iev == NULL) 135 err(1, "malloc"); 136 137 for (;;) { 138 ssize_t n; 139 const char *ev; 140 141 if (kevent(kq, NULL, 0, &kev, 1, NULL) < 0) 142 xo_err(1, "kevent"); 143 if (kev.filter == EVFILT_SIGNAL) 144 break; 145 146 n = read(ifd, iev, ievsz); 147 if (n < 0) 148 xo_err(1, "read"); 149 assert(n >= (ssize_t)sizeof(*iev)); 150 151 for (iev1 = iev; n > 0;) { 152 assert(n >= (ssize_t)sizeof(*iev1)); 153 154 ev = ev2str(iev1->mask); 155 xo_open_instance("event"); 156 xo_emit("{:wd/%3d} {:event/%16s} {:name/%s}\n", 157 iev1->wd, ev, iev1->len > 0 ? iev1->name : ""); 158 xo_close_instance("event"); 159 160 n -= sizeof(*iev1) + iev1->len; 161 iev1 = (struct inotify_event *)(void *) 162 ((char *)iev1 + sizeof(*iev1) + iev1->len); 163 } 164 (void)xo_flush(); 165 } 166 167 xo_close_list("events"); 168 169 if (xo_finish() < 0) 170 xo_err(1, "stdout"); 171 exit(0); 172 } 173