From e6b97161209a4a851e63f3cef49e2c156b609d33 Mon Sep 17 00:00:00 2001 From: Christian Neukirchen Date: Fri, 31 Jul 2015 14:19:32 +0200 Subject: Initial import of nq --- nq.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nq.sh | 67 +++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 nq.c create mode 100755 nq.sh diff --git a/nq.c b/nq.c new file mode 100644 index 0000000..ab2e1ee --- /dev/null +++ b/nq.c @@ -0,0 +1,175 @@ +/* +##% gcc -Wall -g -o $STEM $FILE +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static void +quit(int sig) +{ + exit(0); +} + +#include +/* +char * +timestamp() { + struct timeval tv; + gettimeofday(&tv, NULL); + static char buf[10]; + + int64_t ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + buf[9] = 0; + int i; + for (i = 8; i >= 0; i--) { + buf[i] = 'a' + (ms % 26); + ms /= 26; + } + + return buf; +} +*/ + +int +main(int argc, char *argv[]) +{ + pid_t child; + int lockfd; + char lockfile[128]; + int dirfd; + + struct timeval tv; + gettimeofday(&tv, NULL); + int64_t ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + char *path = getenv("NQDIR"); + if (!path) + path = "."; + + dirfd = open(path, O_RDONLY); + if (dirfd < 0) { + perror("dir open"); + exit(111); + } + + child = fork(); + if (child == -1) { + perror("fork"); + exit(111); + } + + if (child > 0) { + // background + signal(SIGINT, quit); + pause(); + exit(0); + } + + child = fork(); + if (child == -1) { + perror("fork"); + exit(111); + } + if (child > 0) { + int status; + sprintf(lockfile, ",%lx.%d", ms, child); + + puts(lockfile); + + // signal parent to exit + kill(getppid(), SIGINT); + + wait(&status); + int fd; + fd = openat(dirfd, lockfile, O_RDWR | O_APPEND); + fchmod(fd, 0600); + if (WIFEXITED(status)) + dprintf(fd, "\n[exited with status %d.]\n", WEXITSTATUS(status)); + else + dprintf(fd, "\n[killed by signal %d.]\n", WTERMSIG(status)); + + exit(0); + } + + sprintf(lockfile, ".,%lx.%d", ms, getpid()); + lockfd = openat(dirfd, lockfile, O_CREAT | O_EXCL | O_RDWR | O_APPEND, 0600); + + if (lockfd < 0) { + perror("open"); + exit(222); + } + + flock(lockfd, LOCK_EX); + + // drop leading . + renameat(dirfd, lockfile, dirfd, lockfile+1); + + write(lockfd, "exec", 4); + int i; + for (i = 0; i < argc; i++) { + int j, l = strlen(argv[i]); + write(lockfd, " '", 2); + for (j = 0; j < l; j++) { + if (argv[i][j] == '\'') + write(lockfd, "'\\''", 4); + else + write(lockfd, argv[i]+j, 1); + } + write(lockfd, "'", 1); + } + + DIR *dir = fdopendir(dirfd); + if (!dir) { + perror("fdopendir"); + exit(111); + } + + struct dirent *ent; + +again: + while ((ent = readdir(dir))) { + if (ent->d_name[0] == ',' && strcmp(ent->d_name, lockfile+1) < 0) { + int f = openat(dirfd, ent->d_name, O_RDWR); + + if (flock(f, LOCK_EX | LOCK_NB) == -1 && errno == EWOULDBLOCK) { + flock(f, LOCK_EX); // sit it out + + rewinddir(dir); + goto again; + } + + fchmod(f, 0600); + close(f); + } + } + + closedir(dir); + + // ready to run + + write(lockfd, "\n", 1); + + fchmod(lockfd, 0700); + + dup2(lockfd, 2); + dup2(lockfd, 1); + close(lockfd); + close(dirfd); + + execvp(argv[1], argv+1); + + return 111; +} diff --git a/nq.sh b/nq.sh new file mode 100755 index 0000000..2907be5 --- /dev/null +++ b/nq.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# nq CMD... - run CMD... in background and in order, saving output to ,* files +# +# - needs POSIX sh + util-linux flock(1) +# - when run from tmux, display output in a new window (needs +# GNU tail, C-c to abort the job.) +# - we try hard to make the currently running ,* file have +x bit +# - enforcing order works like this: +# - every job has a flock(2)ed file +# - every job starts only after all earlier flock(2)ed files finished +# - the lock is released when job terminates +# +# To the extent possible under law, Christian Neukirchen +# has waived all copyright and related or neighboring rights to this work. +# http://creativecommons.org/publicdomain/zero/1.0/ + +if [ -z "$NQ" ]; then + export NQ=$(date +%s) + "$0" "$@" & c=$! + ( + # wait for job to finish + flock -x .,$NQ.$c -c true + flock -x ,$NQ.$c -c true + chmod -x ,$NQ.$c + ) & + exit +fi + +us=",$NQ.$$" + +exec 9>>.$us +# first flock(2) the file, then make it known under the real name +flock -x 9 +mv .$us $us + +printf "## nq $*" 1>&9 + +if [ -n "$TMUX" ]; then + tmux new-window -a -d -n '<' -c '#{pane_current_path}' \ + "trap true INT QUIT TERM EXIT; + tail -F --pid=$$ $us || kill $$; + printf '\n[%d exited, ^D to exit.]\n' $$; + cat >/dev/null" +fi + +waiting=true +while $waiting; do + waiting=false + # this must traverse in lexical (= numerical) order: + # check all older locks are released + for f in ,*; do + # reached the current lock, good to go + [ $f = $us ] && break + + if ! flock -x -n $f -c "chmod -x $f"; then + # force retrying all locks again; + # an earlier lock could just now have really appeared + waiting=true + flock -x $f -c true + fi + done +done + +printf '\n' 1>&9 + +chmod +x $us +exec "$@" 2>&1 1>&9 -- cgit 1.4.1