about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2017-05-19 18:29:19 +0200
committerLeah Neukirchen <leah@vuxu.org>2017-05-19 18:29:19 +0200
commitf23df6009e987747832cf0adc73c0a3d0b476717 (patch)
tree7b180f48dba8ff6da49152005fb3ec8656b26e15
downloadrwc-f23df6009e987747832cf0adc73c0a3d0b476717.tar.gz
rwc-f23df6009e987747832cf0adc73c0a3d0b476717.tar.xz
rwc-f23df6009e987747832cf0adc73c0a3d0b476717.zip
Initial import of rwc
-rw-r--r--Makefile23
-rw-r--r--README54
-rw-r--r--rwc.174
-rw-r--r--rwc.c130
4 files changed, 281 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3a5b75e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+ALL=rwc
+
+CFLAGS=-g -O2 -Wall -Wno-switch -Wextra -Wwrite-strings
+
+DESTDIR=
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+MANDIR=$(PREFIX)/share/man
+
+all: $(ALL)
+
+README: rwc.1
+	mandoc -Tutf8 $< | col -bx >$@
+
+clean: FRC
+	rm -f $(ALL)
+
+install: FRC all
+	mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
+	install -m0755 $(ALL) $(DESTDIR)$(BINDIR)
+	install -m0644 $(ALL:=.1) $(DESTDIR)$(MANDIR)/man1
+
+FRC:
diff --git a/README b/README
new file mode 100644
index 0000000..c537bc8
--- /dev/null
+++ b/README
@@ -0,0 +1,54 @@
+RWC(1)                      General Commands Manual                     RWC(1)
+
+NAME
+     rwc – report when changed
+
+SYNOPSIS
+     rwc [-0d] [path ...]
+
+DESCRIPTION
+     rwc takes a list of files or directories, watches them using inotify(7),
+     and prints each file name when it changed.  If path is a single dash
+     (‘-’) or absent, rwc reads file names from the standard input.
+
+     Watching a directory will result in watching all changes to files which
+     resides directly in that directory.
+
+     The options are as follows:
+
+     -0      Read input filenames seperated by NUL bytes.  Likewise, output
+             filenames seperated by NUL bytes.
+
+     -d      Also detect file deletion.  In this case, deleted files are
+             prefixed by ‘- ’ (that is, a dash and a space).
+
+EXIT STATUS
+     The rwc utility exits 0 on success, and >0 if an error occurs.
+
+SEE ALSO
+     entr(1), inotifywatch(1), wendy(1)
+
+CAVEATS
+     rwc is limited by some restrictions of inotify(7).  You can only watch
+     files and directories you can read, and the amount of inotify descriptors
+     is limited.  Watching directories is not recursive.
+
+     rwc only uses one watch descriptor per directory, and filters file names
+     itself.  This allows tracking files which get safely written by unlink(2)
+     and rename(2), and also watching files which don't exist yet.
+
+     Many tools like to create temporary files in their working directory,
+     which may distort the output.
+
+AUTHORS
+     Leah Neukirchen <leah@vuxu.org>
+
+LICENSE
+     rwc is in the public domain.
+
+     To the extent possible under law, the creator of this work has waived all
+     copyright and related or neighboring rights to this work.
+
+     http://creativecommons.org/publicdomain/zero/1.0/
+
+Void Linux                       May 19, 2017                       Void Linux
diff --git a/rwc.1 b/rwc.1
new file mode 100644
index 0000000..b34a43a
--- /dev/null
+++ b/rwc.1
@@ -0,0 +1,74 @@
+.Dd May 19, 2017
+.Dt RWC 1
+.Os
+.Sh NAME
+.Nm rwc
+.Nd report when changed
+.Sh SYNOPSIS
+.Nm
+.Op Fl 0d
+.Op Ar path\ ...
+.Sh DESCRIPTION
+.Nm
+takes a list of files or directories, watches them using
+.Xr inotify 7 ,
+and prints each file name when it changed.
+If
+.Ar path
+is a single dash
+.Pq Sq -
+or absent,
+.Nm
+reads file names from the standard input.
+.Pp
+Watching a directory will result in watching all changes to files
+which resides directly in that directory.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 0
+Read input filenames seperated by NUL bytes.
+Likewise, output filenames seperated by NUL bytes.
+.It Fl d
+Also detect file deletion.
+In this case, deleted files are prefixed by
+.Sq Li "- "
+(that is, a dash and a space).
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr entr 1 ,
+.Xr inotifywatch 1 ,
+.Xr wendy 1
+.Sh CAVEATS
+.Nm
+is limited by some restrictions of
+.Xr inotify 7 .
+You can only watch files and directories you can read,
+and the amount of inotify descriptors is limited.
+Watching directories is not recursive.
+.Pp
+.Nm
+only uses one watch descriptor per directory,
+and filters file names itself.
+This allows tracking files which get safely written by
+.Xr unlink 2
+and
+.Xr rename 2 ,
+and also watching files which don't exist yet.
+.Pp
+Many tools like to create temporary files in their working directory,
+which may distort the output.
+.Sh AUTHORS
+.An Leah Neukirchen Aq Mt leah@vuxu.org
+.Sh LICENSE
+.Nm
+is in the public domain.
+.Pp
+To the extent possible under law,
+the creator of this work
+has waived all copyright and related or
+neighboring rights to this work.
+.Pp
+.Lk http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/rwc.c b/rwc.c
new file mode 100644
index 0000000..3b00d9b
--- /dev/null
+++ b/rwc.c
@@ -0,0 +1,130 @@
+// rwc [-0d] [PATH...] - report when changed
+//   -0  use NUL instead of newline for input/output separator
+//   -d  detect deletions too (prefixed with "- ")
+
+#include <sys/inotify.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+char *argv0;
+char ibuf[8192];
+int ifd;
+
+int dflag;
+char input_delim = '\n';
+
+static void *root = 0; // tree
+
+int
+order(const void *a, const void *b)
+{
+	return strcmp((char *)a, (char*)b);
+}
+
+void
+add(char *file)
+{
+        struct stat st;
+
+	char *dir = file;
+
+	tsearch(strdup(file), &root, order);
+
+	// assume non-existing files are regular files
+	if (lstat(file, &st) < 0 || !S_ISDIR(st.st_mode))
+		dir = dirname(file);
+
+	if (inotify_add_watch(ifd, dir, IN_MOVED_TO | IN_CLOSE_WRITE | dflag)<0)
+		fprintf(stderr, "%s: inotify_add_watch: %s: %s\n",
+		    argv0, dir, strerror(errno));
+}
+
+int
+main(int argc, char *argv[])
+{
+	int c, i;
+
+	argv0 = argv[0];
+
+        while ((c = getopt(argc, argv, "0d")) != -1)
+		switch(c) {
+		case '0': input_delim = 0; break;
+		case 'd': dflag = IN_DELETE | IN_DELETE_SELF; break;
+		default:
+                        fprintf(stderr, "Usage: %s [-0d] [PATH...]\n", argv0);
+                        exit(2);
+                }
+
+        ifd = inotify_init();
+        if (ifd < 0) {
+		fprintf(stderr, "%s: inotify_init: %s\n",
+		    argv0, strerror(errno));
+                exit(111);
+	}
+
+	i = optind;
+	if (optind == argc)
+		goto from_stdin;
+	for (; i < argc; i++) {
+		if (strcmp(argv[i], "-") != 0) {
+			add(argv[i]);
+			continue;
+		}
+from_stdin:
+		while (1) {
+			char *line = 0;
+			size_t linelen = 0;
+			ssize_t rd;
+
+			errno = 0;
+			rd = getdelim(&line, &linelen, input_delim, stdin);
+			if (rd == -1) {
+				if (errno != 0)
+					return -1;
+				break;
+			}
+			
+			if (rd > 0 && line[rd-1] == input_delim)
+				line[rd-1] = 0;  // strip delimiter
+
+			add(line);
+		}
+	}
+
+	while (1) {
+		ssize_t len, i;
+		struct inotify_event *ev;
+
+		len = read(ifd, ibuf, sizeof ibuf);
+		if (len <= 0) {
+			fprintf(stderr, "%s: error reading inotify buffer: %s",
+			    argv0, strerror(errno));
+			exit(1);
+		}
+	
+		for (i = 0; i < len; i += sizeof (*ev) + ev->len) {
+			ev = (struct inotify_event *) (ibuf + i);
+
+			if (ev->mask & IN_IGNORED)
+				continue;
+
+			if (tfind(ev->name, &root, order) ||
+			    tfind(dirname(ev->name), &root, order)) {
+				printf("%s%s%c",
+				    (ev->mask & IN_DELETE ? "- " : ""),
+				    ev->name,
+				    input_delim);
+				fflush(stdout);
+			}
+		}
+	}
+
+        return 0;
+}