diff options
author | Leah Neukirchen <leah@vuxu.org> | 2024-07-03 17:55:45 +0200 |
---|---|---|
committer | Leah Neukirchen <leah@vuxu.org> | 2024-07-03 17:58:18 +0200 |
commit | 2bb551efd8b3e50d715482a8f3b89f88bae74397 (patch) | |
tree | d380e8ffaba4e634b6e518480f91f2f37bad72be /nqtail.c | |
parent | e5861199e018fcc6537fbd4a522aea1f75ec0d4e (diff) | |
download | nq-2bb551efd8b3e50d715482a8f3b89f88bae74397.tar.gz nq-2bb551efd8b3e50d715482a8f3b89f88bae74397.tar.xz nq-2bb551efd8b3e50d715482a8f3b89f88bae74397.zip |
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.c | 270 |
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; +} |