xref: /freebsd/share/examples/inotify/inotify.c (revision ae07a5805b1906f29e786f415d67bef334557bd3)
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