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
usage(void)27 usage(void)
28 {
29 xo_errx(1, "usage: inotify <path1> [<path2> ...]");
30 }
31
32 static const char *
ev2str(uint32_t event)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
set_handler(int kq,int sig)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
main(int argc,char ** argv)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