about summary refs log tree commit diff
path: root/nqtail.c
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2024-07-03 17:55:45 +0200
committerLeah Neukirchen <leah@vuxu.org>2024-07-03 17:58:18 +0200
commit2bb551efd8b3e50d715482a8f3b89f88bae74397 (patch)
treed380e8ffaba4e634b6e518480f91f2f37bad72be /nqtail.c
parente5861199e018fcc6537fbd4a522aea1f75ec0d4e (diff)
downloadnq-2bb551efd8b3e50d715482a8f3b89f88bae74397.tar.gz
nq-2bb551efd8b3e50d715482a8f3b89f88bae74397.tar.xz
nq-2bb551efd8b3e50d715482a8f3b89f88bae74397.zip
Rename fq -> nqtail, tq -> nqterm HEAD v1.0 master
There's only so many two letter binaries that can exist at the same time.

A newer project, called fq, arrived and is clashing with nq in many
packaging systems, and nqtail is a fine name too.
Diffstat (limited to 'nqtail.c')
-rw-r--r--nqtail.c270
1 files changed, 270 insertions, 0 deletions
diff --git a/nqtail.c b/nqtail.c
new file mode 100644
index 0000000..5dba4b3
--- /dev/null
+++ b/nqtail.c
@@ -0,0 +1,270 @@
+/*
+ * nqtail [FILES...] - follow output of nq jobs, quitting when they are done
+ *
+ * To the extent possible under law, Leah Neukirchen <leah@vuxu.org>
+ * has waived all copyright and related or neighboring rights to this work.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DELAY 250000
+
+#ifdef __linux__
+#define USE_INOTIFY
+#endif
+
+#if defined(__FreeBSD__) || defined(__APPLE__)
+#define USE_KEVENT
+#endif
+
+#ifdef USE_INOTIFY
+#include <sys/inotify.h>
+char ibuf[8192];
+#endif
+
+#ifdef USE_KEVENT
+#include <sys/event.h>
+#endif
+
+char buf[8192];
+
+static int
+islocked(int fd)
+{
+	if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
+		return (errno == EWOULDBLOCK);
+	} else {
+		flock(fd, LOCK_UN);
+		return 0;
+	}
+}
+
+static int
+alphabetic(const void *a, const void *b)
+{
+	return strcmp(*(char **)a, *(char **)b);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int i, fd, dirfd;
+	off_t off, loff;
+	ssize_t rd;
+	int didsth = 0, seen_nl = 0;
+	int opt = 0, aflag = 0, nflag = 0, qflag = 0;
+	char *path;
+
+#ifdef USE_INOTIFY
+	int ifd, wd;
+#endif
+#ifdef USE_KEVENT
+	int kq, note;
+	struct kevent kev;
+#endif
+
+	close(0);
+
+	while ((opt = getopt(argc, argv, "+anq")) != -1) {
+		switch (opt) {
+		case 'a':
+			aflag = 1;
+			break;
+		case 'n':
+			nflag = 1;
+			break;
+		case 'q':
+			qflag = 1;
+			break;
+		default:
+			fputs("usage: nqtail [-anq] [JOBID...]\n", stderr);
+			exit(1);
+		}
+	}
+
+	path = getenv("NQDIR");
+	if (!path)
+		path = ".";
+
+#ifdef O_DIRECTORY
+	dirfd = open(path, O_RDONLY | O_DIRECTORY);
+#else
+	dirfd = open(path, O_RDONLY);
+#endif
+	if (dirfd < 0) {
+		perror("open dir");
+		exit(111);
+	}
+
+	if (optind == argc) {   /* behave as if $NQDIR/,* was passed. */
+		DIR *dir;
+		struct dirent *d;
+		int len = 0;
+
+		argc = 0;
+		argv = 0;
+		optind = 0;
+
+		dir = fdopendir(dirfd);
+		if (!dir) {
+			perror("fdopendir");
+			exit(111);
+		}
+
+		while ((d = readdir(dir))) {
+			if (d->d_name[0] != ',')
+				continue;
+			if (argc >= len) {
+				len = 2*len + 1;
+				argv = realloc(argv, len * sizeof (char *));
+				if (!argv)
+					exit(222);
+			}
+			argv[argc] = strdup(d->d_name);
+			if (!argv[argc])
+				exit(222);
+			argc++;
+		}
+
+		qsort(argv, argc, sizeof (char *), alphabetic);
+	}
+
+#ifdef USE_INOTIFY
+	ifd = inotify_init();
+	if (ifd < 0)
+		exit(111);
+#endif
+#ifdef USE_KEVENT
+	kq = kqueue();
+	if (kq < 0)
+		exit(111);
+#endif
+
+	for (i = optind; i < argc; i++) {
+		loff = 0;
+		seen_nl = 0;
+
+		fd = openat(dirfd, argv[i], O_RDONLY);
+		if (fd < 0)
+			continue;
+
+		/* skip not running jobs, unless -a was passed, or we did not
+		 * output anything yet and are at the last argument.  */
+		if (!aflag && !islocked(fd) && (didsth || i != argc - 1)) {
+			close(fd);
+			continue;
+		}
+
+		write(1, "==> ", 4);
+		write(1, argv[i], strlen(argv[i]));
+		write(1, qflag ? " " : "\n", 1);
+
+		didsth = 1;
+
+#ifdef USE_INOTIFY
+		char fullpath[PATH_MAX];
+		snprintf(fullpath, sizeof fullpath, "%s/%s", path, argv[i]);
+		wd = inotify_add_watch(ifd, fullpath, IN_MODIFY | IN_CLOSE_WRITE);
+		if (wd == -1) {
+			perror("inotify_add_watch");
+			exit(111);
+		}
+#endif
+#ifdef USE_KEVENT
+		note = NOTE_WRITE;
+#ifdef __APPLE__
+		note |= NOTE_FUNLOCK;
+#endif
+#ifdef __FreeBSD__
+		note |= NOTE_CLOSE_WRITE;
+#endif
+		EV_SET(&kev, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note, 0, NULL);
+		if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) {
+			perror("kevent");
+			exit(111);
+		}
+#endif
+
+		while (1) {
+			off = lseek(fd, 0, SEEK_END);
+
+			if (off < loff)
+				loff = off;               /* file truncated */
+
+			if (off == loff) {
+				if (nflag && islocked(fd))
+					break;
+
+				if (flock(fd, LOCK_SH | LOCK_NB) == -1 &&
+				    errno == EWOULDBLOCK) {
+#if defined(USE_INOTIFY)
+					/* any inotify event is good */
+					read(ifd, ibuf, sizeof ibuf);
+#elif defined(USE_KEVENT)
+					kevent(kq, NULL, 0, &kev, 1, NULL);
+#else
+					/* poll for size change */
+					while (off == lseek(fd, 0, SEEK_END))
+						usleep(DELAY);
+#endif
+					continue;
+				} else {
+					flock(fd, LOCK_UN);
+					break;
+				}
+			}
+
+			if (off - loff > sizeof buf)
+				off = loff + sizeof buf;
+
+			rd = pread(fd, &buf, off - loff, loff);
+			if (qflag) {
+				if (!seen_nl) {
+					char *s;
+					if ((s = memchr(buf, '\n', rd))) {
+						write(1, buf, s+1-buf);
+						seen_nl = 1;
+					} else {
+						write(1, buf, rd);
+					}
+				}
+			} else {
+				write(1, buf, rd);
+			}
+
+			loff += rd;
+		}
+
+		if (qflag && !seen_nl)
+			write(1, "\n", 1);
+
+#ifdef USE_INOTIFY
+		inotify_rm_watch(ifd, wd);
+#endif
+#ifdef USE_KEVENT
+		EV_SET(&kev, fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
+		kevent(kq, &kev, 1, NULL, 0, NULL);
+#endif
+		close(fd);
+	}
+
+#ifdef USE_INOTIFY
+	close(ifd);
+#endif
+#ifdef USE_KEVENT
+	close(kq);
+#endif
+	return 0;
+}