git: 1d8664d69041 - main - examples: Add a demo program for inotify

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Fri, 04 Jul 2025 14:56:03 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=1d8664d6904149e0be5dbfa0ee35268de2a83f1e

commit 1d8664d6904149e0be5dbfa0ee35268de2a83f1e
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2025-07-03 20:00:55 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2025-07-04 14:42:34 +0000

    examples: Add a demo program for inotify
    
    MFC after:      3 months
    Sponsored by:   Klara, Inc.
---
 etc/mtree/BSD.usr.dist           |   2 +
 share/examples/Makefile          |   5 ++
 share/examples/inotify/Makefile  |   6 ++
 share/examples/inotify/inotify.c | 172 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 185 insertions(+)

diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist
index 97b555e50dc1..ffdd82ae9911 100644
--- a/etc/mtree/BSD.usr.dist
+++ b/etc/mtree/BSD.usr.dist
@@ -304,6 +304,8 @@
             ..
             indent
             ..
+            inotify
+            ..
             ipfilter
             ..
             ipfw
diff --git a/share/examples/Makefile b/share/examples/Makefile
index 560fdae6de5b..f0c050a36306 100644
--- a/share/examples/Makefile
+++ b/share/examples/Makefile
@@ -15,6 +15,7 @@ LDIRS=	BSD_daemon \
 	find_interface \
 	flua \
 	indent \
+	inotify \
 	ipfw \
 	jails \
 	kld \
@@ -97,6 +98,10 @@ SE_FLUA=	libjail.lua
 SE_DIRS+=	indent
 SE_INDENT=	indent.pro
 
+SE_DIRS+=	inotify
+SE_INOTIFY=	inotify.c \
+		Makefile
+
 .if ${MK_IPFILTER} != "no"
 SUBDIR+=	ipfilter
 .endif
diff --git a/share/examples/inotify/Makefile b/share/examples/inotify/Makefile
new file mode 100644
index 000000000000..c54c629c58d7
--- /dev/null
+++ b/share/examples/inotify/Makefile
@@ -0,0 +1,6 @@
+PROG= inotify
+MAN=
+
+LIBADD=	xo
+
+.include <bsd.prog.mk>
diff --git a/share/examples/inotify/inotify.c b/share/examples/inotify/inotify.c
new file mode 100644
index 000000000000..b8e300bc992c
--- /dev/null
+++ b/share/examples/inotify/inotify.c
@@ -0,0 +1,172 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Klara, Inc.
+ */
+
+/*
+ * A simple program to demonstrate inotify.  Given one or more paths, it watches
+ * all events on those paths and prints them to standard output.
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/inotify.h>
+
+#include <assert.h>
+#include <err.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <libxo/xo.h>
+
+static void
+usage(void)
+{
+	xo_errx(1, "usage: inotify <path1> [<path2> ...]");
+}
+
+static const char *
+ev2str(uint32_t event)
+{
+	switch (event & IN_ALL_EVENTS) {
+	case IN_ACCESS:
+		return ("IN_ACCESS");
+	case IN_ATTRIB:
+		return ("IN_ATTRIB");
+	case IN_CLOSE_WRITE:
+		return ("IN_CLOSE_WRITE");
+	case IN_CLOSE_NOWRITE:
+		return ("IN_CLOSE_NOWRITE");
+	case IN_CREATE:
+		return ("IN_CREATE");
+	case IN_DELETE:
+		return ("IN_DELETE");
+	case IN_DELETE_SELF:
+		return ("IN_DELETE_SELF");
+	case IN_MODIFY:
+		return ("IN_MODIFY");
+	case IN_MOVE_SELF:
+		return ("IN_MOVE_SELF");
+	case IN_MOVED_FROM:
+		return ("IN_MOVED_FROM");
+	case IN_MOVED_TO:
+		return ("IN_MOVED_TO");
+	case IN_OPEN:
+		return ("IN_OPEN");
+	default:
+		switch (event) {
+		case IN_IGNORED:
+			return ("IN_IGNORED");
+		case IN_Q_OVERFLOW:
+			return ("IN_Q_OVERFLOW");
+		case IN_UNMOUNT:
+			return ("IN_UNMOUNT");
+		}
+		warnx("unknown event %#x", event);
+		assert(0);
+	}
+}
+
+static void
+set_handler(int kq, int sig)
+{
+	struct kevent kev;
+
+	(void)signal(sig, SIG_IGN);
+	EV_SET(&kev, sig, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+	if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+		xo_err(1, "kevent");
+}
+
+int
+main(int argc, char **argv)
+{
+	struct inotify_event *iev, *iev1;
+	struct kevent kev;
+	size_t ievsz;
+	int ifd, kq;
+
+	argc = xo_parse_args(argc, argv);
+	if (argc < 2)
+		usage();
+	argc--;
+	argv++;
+
+	ifd = inotify_init1(IN_NONBLOCK);
+	if (ifd < 0)
+		xo_err(1, "inotify");
+	for (int i = 0; i < argc; i++) {
+		int wd;
+
+		wd = inotify_add_watch(ifd, argv[i], IN_ALL_EVENTS);
+		if (wd < 0)
+			xo_err(1, "inotify_add_watch(%s)", argv[i]);
+	}
+
+	xo_set_version("1");
+	xo_open_list("events");
+
+	kq = kqueue();
+	if (kq < 0)
+		xo_err(1, "kqueue");
+
+	/*
+	 * Handle signals in the event loop so that we can close the xo list.
+	 */
+	set_handler(kq, SIGINT);
+	set_handler(kq, SIGTERM);
+	set_handler(kq, SIGHUP);
+	set_handler(kq, SIGQUIT);
+
+	/*
+	 * Monitor the inotify descriptor for events.
+	 */
+	EV_SET(&kev, ifd, EVFILT_READ, EV_ADD, 0, 0, NULL);
+	if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0)
+		xo_err(1, "kevent");
+
+	ievsz = sizeof(*iev) + NAME_MAX + 1;
+	iev = malloc(ievsz);
+	if (iev == NULL)
+		err(1, "malloc");
+
+	for (;;) {
+		ssize_t n;
+		const char *ev;
+
+		if (kevent(kq, NULL, 0, &kev, 1, NULL) < 0)
+			xo_err(1, "kevent");
+		if (kev.filter == EVFILT_SIGNAL)
+			break;
+
+		n = read(ifd, iev, ievsz);
+		if (n < 0)
+			xo_err(1, "read");
+		assert(n >= (ssize_t)sizeof(*iev));
+
+		for (iev1 = iev; n > 0;) {
+			assert(n >= (ssize_t)sizeof(*iev1));
+
+			ev = ev2str(iev1->mask);
+			xo_open_instance("event");
+			xo_emit("{:wd/%3d} {:event/%16s} {:name/%s}\n",
+			    iev1->wd, ev, iev1->name);
+			xo_close_instance("event");
+
+			n -= sizeof(*iev1) + iev1->len;
+			iev1 = (struct inotify_event *)(void *)
+			    ((char *)iev1 + sizeof(*iev1) + iev1->len);
+		}
+		(void)xo_flush();
+	}
+
+	xo_close_list("events");
+
+	if (xo_finish() < 0)
+		xo_err(1, "stdout");
+	exit(0);
+}