summary refs log tree commit diff
diff options
context:
space:
mode:
authorC. McEnroe <june@causal.agency>2021-02-25 15:42:24 -0500
committerC. McEnroe <june@causal.agency>2021-02-25 15:49:37 -0500
commitfd25c666d57fb9a7c0da58620a7cc768c7aa743e (patch)
treeca1c99ceb09e3a590635f799f552f9aebda0e4d2
parentDrop pledge privileges after daemonization (diff)
downloadcatsit-fd25c666d57fb9a7c0da58620a7cc768c7aa743e.tar.gz
catsit-fd25c666d57fb9a7c0da58620a7cc768c7aa743e.zip
Add catsit-watch utility
-rw-r--r--.gitignore1
-rw-r--r--Makefile48
-rw-r--r--catsit-watch.152
-rw-r--r--catsit-watch.c94
-rw-r--r--catsit.conf.51
5 files changed, 182 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index 3b0a7f4..d09ccdd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.o
 catsit
+catsit-watch
 catsit.conf
 catsitd
 config.mk
diff --git a/Makefile b/Makefile
index 392aacc..573c1f3 100644
--- a/Makefile
+++ b/Makefile
@@ -16,16 +16,18 @@ RC_SCRIPT = ${UNAME}/catsitd
 
 -include config.mk
 
-BINS = catsit catsitd
-MAN8 = ${BINS:=.8}
+BINS = catsit-watch
+SBINS = catsit catsitd
+MAN1 = ${BINS:=.1}
 MAN5 = catsit.conf.5
+MAN8 = ${SBINS:=.8}
 
 OBJS += daemon.o
 OBJS += service.o
 
 dev: tags all
 
-all: ${BINS}
+all: ${BINS} ${SBINS}
 
 catsitd: ${OBJS}
 	${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@
@@ -38,22 +40,40 @@ ${OBJS}: daemon.h
 	sed -e 's|%%PREFIX%%|${PREFIX}|g' -e 's|%%RUNDIR%%|${RUNDIR}|g' $< > $@
 	chmod a+x $@
 
-tags: *.c *.h
-	ctags -w *.c *.h
+tags: *.[ch]
+	ctags -w *.[ch]
 
 clean:
-	rm -f ${BINS} ${OBJS} ${RC_SCRIPT} tags
-
-install: ${BINS} ${RC_SCRIPT} ${MAN5} ${MAN8}
-	install -d ${DESTDIR}${PREFIX}/sbin ${DESTDIR}${ETCDIR}/rc.d
-	install -d ${DESTDIR}${MANDIR}/man5 ${DESTDIR}${MANDIR}/man8
-	install ${BINS} ${DESTDIR}${PREFIX}/sbin
+	rm -f ${BINS} ${SBINS} ${OBJS} ${RC_SCRIPT} tags
+
+install: ${BINS} ${SBINS} ${RC_SCRIPT} ${MAN1} ${MAN5} ${MAN8}
+	install -d ${DESTDIR}${PREFIX}/bin
+	install -d ${DESTDIR}${PREFIX}/sbin
+	install -d ${DESTDIR}${MANDIR}/man1
+	install -d ${DESTDIR}${MANDIR}/man5
+	install -d ${DESTDIR}${MANDIR}/man8
+	install -d ${DESTDIR}${ETCDIR}/rc.d
+	install ${BINS} ${DESTDIR}${PREFIX}/bin
+	install ${SBINS} ${DESTDIR}${PREFIX}/sbin
 	install ${RC_SCRIPT} ${DESTDIR}${ETCDIR}/rc.d
+	install -m 644 ${MAN1} ${DESTDIR}${MANDIR}/man1
 	install -m 644 ${MAN5} ${DESTDIR}${MANDIR}/man5
 	install -m 644 ${MAN8} ${DESTDIR}${MANDIR}/man8
 
 uninstall:
-	rm -f ${BINS:%=${DESTDIR}${PREFIX}/sbin/%}
-	rm -f ${MAN5:%=${DESTDIR}${MANDIR}/man5/%}
-	rm -f ${MAN8:%=${DESTDIR}${MANDIR}/man8/%}
+.for BIN in ${BINS}
+	rm -f ${DESTDIR}${PREFIX}/bin/${BIN}
+.endfor
+.for SBIN in ${SBINS}
+	rm -f ${DESTDIR}${PREFIX}/sbin/${SBIN}
+.endfor
 	rm -f ${DESTDIR}${ETCDIR}/rc.d/${RC_SCRIPT:T}
+.for MAN in ${MAN1}
+	rm -f ${DESTDIR}${MANDIR}/man1/${MAN}
+.endfor
+.for MAN in ${MAN5}
+	rm -f ${DESTDIR}${MANDIR}/man5/${MAN}
+.endfor
+.for MAN in ${MAN8}
+	rm -f ${DESTDIR}${MANDIR}/man8/${MAN}
+.endfor
diff --git a/catsit-watch.1 b/catsit-watch.1
new file mode 100644
index 0000000..b45f704
--- /dev/null
+++ b/catsit-watch.1
@@ -0,0 +1,52 @@
+.Dd February 25, 2021
+.Dt CATSIT-WATCH 1
+.Os
+.
+.Sh NAME
+.Nm catsit-watch
+.Nd run command when files are modified
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Op Fl f Ar file
+.Ar command ...
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility runs a command
+each time any of a set of files
+are modified.
+If any watched files are removed
+or if the command exits non-zero,
+.Nm
+exits.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar file
+Add
+.Ar file
+to the set of watched files.
+.It Fl i
+Run the command once initially,
+before watching files.
+.El
+.
+.Sh EXIT STATUS
+If any watched files are removed,
+.Nm
+exits with
+.Dv EX_TEMPFAIL
+(75).
+If the command exits non-zero,
+.Nm
+exits with the same status.
+.
+.Sh SEE ALSO
+.Xr catsitd 8
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/catsit-watch.c b/catsit-watch.c
new file mode 100644
index 0000000..4d0840c
--- /dev/null
+++ b/catsit-watch.c
@@ -0,0 +1,94 @@
+/* Copyright (C) 2021  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static void watch(int kq, char *path) {
+	int fd = open(path, O_RDONLY | O_CLOEXEC);
+	if (fd < 0) err(EX_NOINPUT, "%s", path);
+
+	struct kevent event;
+	EV_SET(
+		&event, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+		NOTE_WRITE | NOTE_DELETE, 0, path
+	);
+	int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
+	if (nevents < 0) err(EX_OSERR, "kevent");
+}
+
+static void run(char *argv[]) {
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+	if (!pid) {
+		execvp(argv[0], argv);
+		err(126, "%s", argv[0]);
+	}
+
+	int status;
+	pid = wait(&status);
+	if (pid < 0) err(EX_OSERR, "wait");
+	if (WIFEXITED(status)) {
+		status = WEXITSTATUS(status);
+		if (status) exit(status);
+	} else {
+		exit(status);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	int kq = kqueue();
+	if (kq < 0) err(EX_OSERR, "kqueue");
+	fcntl(kq, F_SETFD, FD_CLOEXEC);
+
+	int init = 0;
+	for (int opt; 0 < (opt = getopt(argc, argv, "f:i"));) {
+		switch (opt) {
+			break; case 'f': watch(kq, optarg);
+			break; case 'i': init = 1;
+			break; default: return EX_USAGE;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+	if (!argc) errx(EX_USAGE, "command required");
+
+#ifdef __OpenBSD__
+	int error = pledge("stdio proc exec", NULL);
+	if (error) err(EX_OSERR, "pledge");
+#endif
+
+	if (init) run(argv);
+	for (;;) {
+		struct kevent event;
+		int nevents = kevent(kq, NULL, 0, &event, 1, NULL);
+		if (nevents < 0) err(EX_OSERR, "kevent");
+
+		if (event.fflags & NOTE_DELETE) {
+			errx(EX_TEMPFAIL, "%s: file removed", (char *)event.udata);
+		}
+		run(argv);
+	}
+}
diff --git a/catsit.conf.5 b/catsit.conf.5
index c18f683..7469e74 100644
--- a/catsit.conf.5
+++ b/catsit.conf.5
@@ -80,6 +80,7 @@ pounce/tilde	pounce ${0#*/}.conf
 .Ed
 .
 .Sh SEE ALSO
+.Xr catsit-watch 1 ,
 .Xr catsitd 8
 .
 .Sh AUTHORS