eradman-entr-7821588c309c/.hg_archival.txt0000644000000000000000000000017412260303503016251 0ustar 00000000000000repo: 49108c05f40cf0f2abceacff753c2df1cd22e1ec node: 7821588c309ccb7eb9e35e2144800bf7ab4356c7 branch: default tag: entr-2.5 eradman-entr-7821588c309c/.hgtags0000644000000000000000000001023712260303503014442 0ustar 00000000000000a93f110dc101dc1fb0dfc8fb51aeccfe174deb42 1.0 a93f110dc101dc1fb0dfc8fb51aeccfe174deb42 1.0 0000000000000000000000000000000000000000 1.0 441b7a5efb34e7b27fd657bf133f1182e0ecc14d entr-1.0 441b7a5efb34e7b27fd657bf133f1182e0ecc14d entr-1.0 0000000000000000000000000000000000000000 entr-1.0 0000000000000000000000000000000000000000 entr-1.0 ed33bb691738c34109b52a0f0b31779753dfa492 entr-1.0 ed33bb691738c34109b52a0f0b31779753dfa492 entr-1.0 0000000000000000000000000000000000000000 entr-1.0 0000000000000000000000000000000000000000 entr-1.0 d0f7f616c4b1bea4ec26c3c50a1746f6d11be625 entr-1.0 210e11310365da7e273c4bdf1ec85bb70baf2754 entr-1.1 d60d9882b51c906a5bad084778051bf416fa5cd5 1.2 d60d9882b51c906a5bad084778051bf416fa5cd5 1.2 0000000000000000000000000000000000000000 1.2 ba7521e12164b7955851841d1d9e3540d66e711b entr-1.2 d8f34b7f6b3ff5442e729ea849a8c64b723f31d9 entr-1.3 d8f34b7f6b3ff5442e729ea849a8c64b723f31d9 entr-1.3 0000000000000000000000000000000000000000 entr-1.3 0000000000000000000000000000000000000000 entr-1.3 c9b0265a2dc0c911c39f1b220b33c436ad5ebdd1 entr-1.3 10adef1047c3e47c04e8a6b4ee38d0036d262447 entr-1.4 10adef1047c3e47c04e8a6b4ee38d0036d262447 entr-1.4 0000000000000000000000000000000000000000 entr-1.4 0000000000000000000000000000000000000000 entr-1.4 b90825a157a4d466dfe7a121bc796c13f15217b0 entr-1.4 ec7cdd3475cc81dd178fdaf164beab339ecbf670 entr-1.5 ec7cdd3475cc81dd178fdaf164beab339ecbf670 entr-1.5 0000000000000000000000000000000000000000 entr-1.5 0000000000000000000000000000000000000000 entr-1.5 03003fdbeab84cc00f9e04cfa33f2aa9185155e8 entr-1.5 12570105b6fa9693b6227866212569dce09c382c entr-1.6 12570105b6fa9693b6227866212569dce09c382c entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 37eb771d3dfc1bc6a8ee18d91fa725b41f7c0233 entr-1.6 37eb771d3dfc1bc6a8ee18d91fa725b41f7c0233 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 ae12c4f08b93c05ef4afa9c1025aca5a1e2a2c21 entr-1.6 ae12c4f08b93c05ef4afa9c1025aca5a1e2a2c21 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 62f60cc6a2b0187090496f21af20cc08a77f1207 entr-1.6 62f60cc6a2b0187090496f21af20cc08a77f1207 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 60ea9a753c859a2cac19cdc4dd5554e7f81fbc92 entr-1.6 60ea9a753c859a2cac19cdc4dd5554e7f81fbc92 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 0000000000000000000000000000000000000000 entr-1.6 d7d5d59112b30a54b877ed8a9d1a4f00f65260c6 entr-1.6 79e0f40300a9dc3ba8f0e01845430e53231ad4da add 79e0f40300a9dc3ba8f0e01845430e53231ad4da entr-1.7 79e0f40300a9dc3ba8f0e01845430e53231ad4da add 0000000000000000000000000000000000000000 add 79e0f40300a9dc3ba8f0e01845430e53231ad4da entr-1.7 0000000000000000000000000000000000000000 entr-1.7 0000000000000000000000000000000000000000 entr-1.7 4fc0ba787d8729b4140c5705fafb5d01c481806d entr-1.7 4fc0ba787d8729b4140c5705fafb5d01c481806d entr-1.7 0000000000000000000000000000000000000000 entr-1.7 0000000000000000000000000000000000000000 entr-1.7 90f2e2fb8d05724ac250a16664c60c1a8618c137 entr-1.7 102d933841a148a16c1d494887bc63a08b05a983 entr-1.8 102d933841a148a16c1d494887bc63a08b05a983 entr-1.8 0000000000000000000000000000000000000000 entr-1.8 0000000000000000000000000000000000000000 entr-1.8 f508b5eb02601f292dd638337b1063a13f4df13e entr-1.8 aa566aed8d71a0781b2d330a17bc6c08758041b1 entr-1.9 aa566aed8d71a0781b2d330a17bc6c08758041b1 entr-1.9 0000000000000000000000000000000000000000 entr-1.9 0000000000000000000000000000000000000000 entr-1.9 2ce330cb6b44e1057add6d9cda1971fd4d6990fe entr-1.9 6b1f1d24277567a427db721947e6fa02df54ffa1 entr-2.0 cfe6ab9d9f301cfef059cc9931e5ce66483d89b3 entr-2.1 cfe6ab9d9f301cfef059cc9931e5ce66483d89b3 entr-2.1 0000000000000000000000000000000000000000 entr-2.1 0000000000000000000000000000000000000000 entr-2.1 b621ace9ecba9171a40753f350dc634e2c03d51c entr-2.1 963b395019be991e4d62d915583c546fdb6616ae entr-2.2 5bd7523558dd8a8492a528dfc0bfbb2c9809572b entr-2.3 c0009123230bd7647779c786e6e6668277a67c8a entr-2.4 eradman-entr-7821588c309c/LICENSE0000644000000000000000000000650612260303503014175 0ustar 000000000000001) Project Source Source code for `entr` is licensed under an ISC-style license, to the following copyright holders: Eric Radman * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 2) Compatibility Libraries (MacOS and Linux only) Some code under the /missing subdirectory is licensed under a 2-term BSD license, to the following copyright holders: Jonathan Lemon * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. Some code under the /missing subdirectory is licensed under an ISC-style license, to the following copyright holders: Todd C. Miller Martin Pieuchot Ted Unangst * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. eradman-entr-7821588c309c/Makefile.bsd0000644000000000000000000000216712260303503015376 0ustar 00000000000000PREFIX ?= /usr/local MANPREFIX ?= ${PREFIX}/man all: entr test: entr_spec entr @/bin/echo "Running unit tests" @./entr_spec gcc-lint: clean @CFLAGS="-std=c89 -pedantic -Wall -Wpointer-arith -Wbad-function-cast" make test regress: @/bin/echo -n "Running functional tests" @./regress.sh debug: entr_spec gdb -q entr_spec entr: entr.c ${EXTRA_SRC} ${CC} ${CFLAGS} ${EXTRA_SRC} entr.c -o $@ ${LDFLAGS} @chmod +x $@ entr_spec: entr_spec.c entr.c ${EXTRA_SRC} ${CC} ${CFLAGS} ${EXTRA_SRC} entr_spec.c -o $@ ${LDFLAGS} @chmod +x $@ clean: rm -f entr entr_spec *.o distclean: clean rm -f Makefile install: entr @mkdir -p ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${MANPREFIX}/man1 install entr ${DESTDIR}${PREFIX}/bin install entr.1 ${DESTDIR}${MANPREFIX}/man1 uninstall: rm ${DESTDIR}${PREFIX}/bin/entr rm ${DESTDIR}${MANPREFIX}/man1/entr.1 env: @echo "CC = ${CC}" @echo "DESTDIR = ${DESTDIR}" @echo "EXTRA_SRC = ${EXTRA_SRC}" @echo "LDFLAGS = ${LDFLAGS}" @echo "MANPREFIX = ${MANPREFIX}" @echo "PREFIX = ${PREFIX}" @echo "SRC = ${SRC}" .PHONY: clean distclean install uninstall env eradman-entr-7821588c309c/Makefile.linux0000644000000000000000000000025512260303503015761 0ustar 00000000000000CFLAGS += -D_GNU_SOURCE -D_LINUX_PORT -Imissing MANPREFIX ?= ${PREFIX}/share/man EXTRA_SRC = missing/strlcpy.c missing/kqueue.c missing/setproctitle.c include Makefile.bsd eradman-entr-7821588c309c/Makefile.linux-lbsd0000644000000000000000000000066612260303503016711 0ustar 00000000000000# `entr` provides a built-in compability layer for Linux. To use compatibility # libraries instead insteall libkqueue and libbsd # # Debian: # sudo apt-get install libkqueue0 libkqueue-dev # sudo libbsd0 libbsd-dev # make test -f Makefile.linux-lbsd # sudo make install CFLAGS += -D_GNU_SOURCE -D_LINUX_PORT -I/usr/include/kqueue LDFLAGS += -lpthread -lkqueue -lbsd MANPREFIX ?= ${PREFIX}/share/man include Makefile.bsd eradman-entr-7821588c309c/Makefile.macos0000644000000000000000000000020612260303503015720 0ustar 00000000000000CFLAGS += -D_MACOS_PORT MANPREFIX ?= ${PREFIX}/share/man EXTRA_SRC = missing/fmemopen.c missing/setproctitle.c include Makefile.bsd eradman-entr-7821588c309c/README.md0000644000000000000000000000466512260303503014453 0ustar 00000000000000Event Notify Test Runner ======================== A utility for running arbitrary commands when files change. Uses [kqueue(2)][kqueue_2] or [inotify(7)][inotify_7] to avoid polling. `entr` responds to file system events by executing command line arguments or by writing to a FIFO. `entr` was written to provide to make rapid feedback and automated testing natural and completely ordinary. Installation - BSD, Mac OS, and Linux ------------------------------------- ./configure make test make install To see available build options run `./configure -h` Installation - Mac OS/Homebrew ------------------------------ brew install entr Installation - Ports -------------------- Available in OpenBSD ports, FreeBSD ports, and pkgsrc under `sysutils/entr`. Examples -------- Rebuild project when source files change $ find src | entr make Clear the screen and run tests $ ls *.py | entr sh -c 'clear; ./test.py' Launch and auto-reload a node.js server $ ls *.js | entr -r node index.js Convert Markdown files to HTML using a FIFO. Only files that change will be processed. $ ls *.md | entr +notify & $ while read F > do > markdown2html $F > done < notify Releases History ---------------- 2.5 prevent interactive utilities from paging output _2013-12-30_ 2.4 License file describes the copyright for the compatibility libraries _2013-12-18_ 2.3 Wait for processes to terminate in restart mode _2013-12-16_ 2.2 Process every delete or rename event to ensure files remain tracked _2013-08-07_ 2.1 Zero-dependency build on Linux using built-in compatibility layer _2013-07-01_ 2.0 More portable build; runs on old architectures without C99 support _2013-06-17_ 1.9 New auto-reload option _2013-04-13_ 1.8 Loosing a file under watch is always fatal _2012-12-05_ 1.7 Successfully stat deleted files before running a command _2012-11-20_ 1.6 Works with NFS mounts on Linux, no need for pthreads on BSD _2012-08-10_ 1.5 Support interactive applications by opening a TTY _2012-07-29_ 1.4 New regression tests and better Linux support _2012-05-22_ 1.3 New FIFO mode and better support of Mac OS _2012-05-17_ 1.2 Support for Linux via libkqueue _2012-04-26_ 1.1 Support for Mac OS added. _2012-04-17_ 1.0 Tested on all the major BSDs _2012-04-12_ [kqueue_2]: http://www.openbsd.org/cgi-bin/man.cgi?query=kqueue&manpath=OpenBSD+Current&format=html [inotify_7]: http://man.he.net/?section=all&topic=inotify eradman-entr-7821588c309c/configure0000755000000000000000000000071012260303503015066 0ustar 00000000000000#!/bin/sh copy_mk() { cmd="cp Makefile.$1 Makefile" echo "$cmd"; $cmd } case `uname` in Darwin) copy_mk macos ;; Linux) copy_mk linux ;; *) copy_mk bsd ;; esac [ $# = 0 ] && exit 0 cat < * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include /* data */ typedef struct { char fn[PATH_MAX]; int fd; } WatchFile; /* declare as extern in source */ WatchFile **files; eradman-entr-7821588c309c/entr.10000755000000000000000000000542212260303503014221 0ustar 00000000000000.\" .\" Copyright (c) 2012 Eric Radman .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate: December 30 2013 $ .Dt ENTR 1 .Os .Sh NAME .Nm entr .Nd run arbitrary commands when files change .Sh SYNOPSIS .Nm .Op Ar -r .Ar utility .Op Ar arguments ... .Nm entr .Ar +fifo .Sh DESCRIPTION .Nm entr has two modes of operation; the first use reads a list of files provided on STDIN and executes the supplied .Ar utility if any of them change. .Nm waits for the child process to finish before responding to subsequent file system events. A TTY is opened before entering the watch loop in order to support the invocation of interactive utilities. .Pp .Ar -r modifies the exec mode by launching the .Ar utility at startup and reloading it if one of the source files change. This features requires the application to exit if it receives SIGTERM. .Nm always waits for the .Ar utility to exit to ensure that resources such as sockets have been closed. .Pp The second mode also reads a list of filenames provided on STDIN and is enabled by specifying '+' and the name of a fifo. In this mode .Nm enables more sophisticated scripting by writing filenames to a named pipe when they are modified. .Sh ENVIRONMENT If .Ev PAGER is undefined, .Nm entr will assign .Pa /bin/cat to prevent interactive utilities from waiting for keyboard input if output does not fit on the screen. .Sh EXIT STATUS The .Nm entr utility does not normally return, but it will exit with a value of 0 if the signal .Dv SIGINT or .Dv SIGTERM was received. An exit status of 1 indicates that no regular files were provided as input. .Sh EXAMPLES Rebuild project when source files change .Pp .Dl $ find src | entr make .Pp Clear the screen and run tests .Pp .Dl $ ls *.py | entr sh -c 'clear; ./test.py' .Pp Launch and auto-reload a node.js server .Pp .Dl $ ls *.js | entr -r node index.js .Pp Convert individual Markdown files to HTML if they're modified .Pp .Dl $ ls *.md | entr +notify & .Dl $ while read F; do .Dl > markdown2html $F .Dl > done < notify .Sh CAVEATS Only regular files are monitored, directories and special files are ignored. eradman-entr-7821588c309c/entr.c0000644000000000000000000002403612260303503014302 0ustar 00000000000000/* * Copyright (c) 2012 Eric Radman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "missing/compat.h" #include "data.h" /* events to watch for */ #define NOTE_ALL NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND /* shortcuts */ #define min(a, b) (((a) < (b)) ? (a) : (b)) #define MEMBER_SIZE(S, M) sizeof(((S *)0)->M) #define MILLISECOND 1000000 /* function pointers */ int (*test_runner_main)(int, char**); int (*_stat)(const char *, struct stat *); int (*_kill)(pid_t, int); int (*_execvp)(const char *, char *const []); pid_t (*_waitpid)(pid_t, int *, int); pid_t (*_fork)(); int (*_kevent)(int, const struct kevent *, int, struct kevent *, int , const struct timespec *); int (*_mkfifo)(const char *path, mode_t mode); int (*_open)(const char *path, int flags, ...); /* globals */ extern int optind; extern WatchFile **files; WatchFile fifo; int restart_mode; int child_pid; /* forwards */ static void usage(); static void terminate_utility(); static void handle_exit(int sig); static int process_input(FILE *, WatchFile *[], int); static int set_fifo(char *[]); static int set_options(char *[]); static void run_script(char *[]); static void watch_file(int, WatchFile *); static void watch_loop(int, char *[]); /* * The Event Notify Test Runner * run arbitrary commands when files change */ int main(int argc, char *argv[]) { struct rlimit rl; int kq; struct sigaction act; int ttyfd; short argv_index; int n_files; int i; if ((*test_runner_main)) return(test_runner_main(argc, argv)); /* set up pointers to real functions */ _stat = stat; _kevent = kevent; _kill = kill; _execvp = execvp; _waitpid = waitpid; _fork = fork; _mkfifo = mkfifo; _open = open; /* call usage() if no command is supplied */ if (argc < 2) usage(); argv_index = set_options(argv); /* normally a user will exit this utility by hitting Ctrl-C */ act.sa_flags = 0; act.sa_handler = handle_exit; if (sigemptyset(&act.sa_mask) & (sigaction(SIGINT, &act, NULL) != 0)) err(1, "Failed to set SIGINT handler"); if (sigemptyset(&act.sa_mask) & (sigaction(SIGTERM, &act, NULL) != 0)) err(1, "Failed to set TERM handler"); /* raise soft limit */ getrlimit(RLIMIT_NOFILE, &rl); rl.rlim_cur = min((rlim_t)sysconf(_SC_OPEN_MAX), rl.rlim_max); if (setrlimit(RLIMIT_NOFILE, &rl) != 0) err(1, "setrlimit cannot set rlim_cur to %d", (int)rl.rlim_cur); /* prevent interactive utilities from paging output */ setenv("PAGER", "/bin/cat", 0); /* sequential scan may depend on a 0 at the end */ files = malloc(sizeof(char *) * rl.rlim_cur+1); memset(files, 0, sizeof(char *) * rl.rlim_cur+1); if ((kq = kqueue()) == -1) err(1, "cannot create kqueue"); /* read input and populate watch list, skipping non-regular files */ n_files = process_input(stdin, files, rl.rlim_cur); if (n_files == 0) errx(2, "No regular files to watch"); if (n_files == -1) errx(1, "Too many files listed; the hard limit for your login" " class is %d", (int)rl.rlim_cur); for (i=0; i STDIN_FILENO) { if (dup2(ttyfd, STDIN_FILENO) != 0) warn("can't dup2 to stdin"); close(ttyfd); } } watch_loop(kq, argv+argv_index); return 1; } /* Utility functions */ void usage() { extern char *__progname; fprintf(stderr, "usage: %s [-r] utility [args, ...] < filenames\n", __progname); fprintf(stderr, " %s +fifo < filenames\n", __progname); exit(2); } void terminate_utility() { int status; if (child_pid > 0) { setproctitle("waiting for child PID %d", child_pid); #ifdef DEBUG fprintf(stderr, "signal %d sent to pid %d\n", SIGTERM, child_pid); #endif _kill(child_pid, SIGTERM); _waitpid(child_pid, &status, 0); child_pid = 0; setproctitle(NULL); } } /* Callbacks */ void handle_exit(int sig) { if (fifo.fd) { close(fifo.fd); unlink(fifo.fn); } terminate_utility(); exit(0); } /* * Read lines from a file stream (normally STDIN) * Returns the number of regular files to be watched or -1 if max_files is * exceeded */ int process_input(FILE *file, WatchFile *files[], int max_files) { char buf[PATH_MAX]; char *p; int n_files = 0; struct stat sb; int ret; setproctitle("reading file list from STDIN"); while (fgets(buf, sizeof(buf), file) != NULL) { buf[PATH_MAX-1] = '\0'; if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; if (buf[0] == '\0') continue; ret = _stat(buf, &sb); if (ret == -1) err(1, "cannot stat '%s'", buf); if (S_ISREG(sb.st_mode) != 0) { files[n_files] = malloc(sizeof(WatchFile)); strlcpy(files[n_files]->fn, buf, MEMBER_SIZE(WatchFile, fn)); n_files++; } if (n_files+1 > max_files) return -1; } setproctitle(NULL); return n_files; } /* * Determine if the user is specifying FIFO mode by supplying a pathname * prefixed with '+' and set the global mode flag accordingly */ int set_fifo(char *argv[]) { if (argv[0][0] == (int)'+') { strlcpy(fifo.fn, argv[0]+1, MEMBER_SIZE(WatchFile, fn)); if (_mkfifo(fifo.fn, S_IRUSR| S_IWUSR) == -1) err(1, "mkfifo '%s' failed", fifo.fn); setproctitle("waiting for connection to fifo"); if ((fifo.fd = _open(fifo.fn, O_WRONLY, 0)) == -1) err(1, "open fifo '%s' failed", fifo.fn); setproctitle(NULL); return 1; } memset(&fifo, 0, sizeof(fifo)); return 0; } /* * Evaluate command line arguments and return an offset to the command to * execute. */ int set_options(char *argv[]) { int ch; int argc; argc = 1; while (argv[argc] != '\0') { if (argv[argc][0] == '-') argc++; else break; } /* no command to run */ if (argv[argc] == '\0') usage(); while ((ch = getopt(argc, argv, "r")) != -1) { switch (ch) { case 'r': restart_mode = 1; break; } } return optind; } /* * Execute the program supplied on the command line. If restart_mode was set * then send the child process SIGTERM and restart it. */ void run_script(char *argv[]) { int pid; int i; int ret; struct timespec delay = { 0, 100 * MILLISECOND }; int status; if (restart_mode == 1) terminate_utility(); pid = _fork(); if (pid == -1) err(errno, "can't fork"); if (pid == 0) { /* wait up to 1 second for each file to become available */ for (i=0; i < 10; i++) { ret = _execvp(argv[0], argv); if (errno == ETXTBSY) nanosleep(&delay, NULL); else break; } if (ret != 0) err(1, "exec %s", argv[0]); } child_pid = pid; if (restart_mode == 0) _waitpid(pid, &status, 0); } /* * Wait for file to become accessible and register a kevent to watch it */ void watch_file(int kq, WatchFile *file) { struct kevent evSet; int i; struct timespec delay = { 0, 100 * MILLISECOND }; /* wait up to 1 second for file to become available */ for (i=0; i < 10; i++) { #ifdef O_EVTONLY file->fd = _open(file->fn, O_RDONLY|O_EVTONLY); #else file->fd = _open(file->fn, O_RDONLY); #endif if (file->fd == -1) nanosleep(&delay, NULL); else break; } if (file->fd == -1) err(errno, "cannot open `%s'", file->fn); EV_SET(&evSet, file->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ALL, 0, file); if (_kevent(kq, &evSet, 1, NULL, 0, NULL) == -1) err(1, "failed to register VNODE event"); } /* * Wait for events to fire and execute a command or write filename to a FIFO. If * a file dissapears we'll spin waiting for it to reappear. */ void watch_loop(int kq, char *argv[]) { struct kevent evSet; struct kevent evList[32]; int nev; WatchFile *file; int i; struct timespec evTimeout = { 0, MILLISECOND }; int reopen_only = 0; if (restart_mode) run_script(argv); main: if (reopen_only == 1) nev = _kevent(kq, NULL, 0, evList, 32, &evTimeout); else nev = _kevent(kq, NULL, 0, evList, 32, NULL); if (nev == -2) /* test runner */ return; /* reopen all files that were removed */ for (i=0; ifd, EVFILT_VNODE, EV_DELETE, NOTE_ALL, 0, file); if (_kevent(kq, &evSet, 1, NULL, 0, NULL) == -1) err(1, "failed to remove VNODE event"); if ((file->fd != -1) && (close(file->fd) == -1)) err(1, "unable to close file"); watch_file(kq, file); } } if (reopen_only == 1) { reopen_only = 0; goto main; } /* respond to all events */ for (i=0; ifn, strlen(file->fn)); write(fifo.fd, "\n", 1); fsync(fifo.fd); } } } goto main; } eradman-entr-7821588c309c/entr_spec.c0000644000000000000000000003112612260303503015312 0ustar 00000000000000/* * Copyright (c) 2012 Eric Radman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "entr.c" /* globals */ extern WatchFile **files; /* test context */ struct { struct { int count; char *file; char **argv; } exec; struct { struct kevent Set[32]; struct kevent List[32]; int nset; int nlist; int decrement; } event; struct { int pid; int sig; int count; } signal; struct { int fd; const char *path; } open; } ctx; /* test runner */ int tests_run, failures; const char* func; int line; static void reset_state(); static void fail(); #define _() func=__func__; line=__LINE__; #define ok(test) do { _(); if (!(test)) { fail(); return 1; } } while(0) #define run(test) do { reset_state(); tests_run++; test(); } while(0) void fail() { failures++; fprintf(stderr, "test failure in %s() line %d\n", func, line); } void reset_state() { int i; int max_files = 4; /* initialize external global data */ memset(&fifo, 0, sizeof(fifo)); restart_mode = 0; child_pid = 0; files = malloc(sizeof(WatchFile *) * max_files); for (i=0; ist_mode = S_IFDIR; else sb->st_mode = S_IFREG; return 0; } pid_t fake_waitpid(pid_t wpid, int *status, int options) { return wpid; } pid_t fake_fork() { return 0; /* pretend to be the child */ } int fake_mkfifo(const char *path, mode_t mode) { return 0; /* success */ } /* mock objects */ int fake_kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout) { /* record each event that the application sets */ if (nchanges > 0) { memcpy(&ctx.event.Set[ctx.event.nset], changelist, sizeof(struct kevent) * nchanges); ctx.event.nset += nchanges; return nchanges; } /* return list of events that each test sets up */ if ((nevents > 0) && (ctx.event.nlist > 0)) { memcpy(eventlist, &ctx.event.List, sizeof(struct kevent) * ctx.event.nlist); ctx.event.nlist -= ctx.event.decrement; return ctx.event.decrement; } /* no more events, use bogus return code to cause the main loop to exit */ return -2; } /* spies */ int fake_kill(pid_t pid, int sig) { ctx.signal.pid = pid; ctx.signal.sig = sig; ctx.signal.count++; return 0; } int fake_execvp(const char *file, char *const argv[]) { ctx.exec.count++; ctx.exec.file = (char *)file; ctx.exec.argv = (char **)argv; return 0; } int fake_open(const char *path, int flags, ...) { ctx.open.path = path; ctx.open.fd++; return ctx.open.fd; } /* tests */ /* * Read a list of use supplied files where input exceeds available watch * descriptors */ int process_input_01() { char input[] = "file1\nfile2\nfile3"; FILE *fake; int n_files; fake = fmemopen(input, strlen(input), "r"); n_files = process_input(fake, files, 3); ok(n_files == -1); ok(strcmp(files[0]->fn, "file1") == 0); ok(strcmp(files[1]->fn, "file2") == 0); ok(strcmp(files[2]->fn, "file3") == 0); return 0; } /* * Read a list of use supplied files and populate files array */ int process_input_02() { int n_files; FILE *fake; char input[] = "dir1\nfile1\nfile2\nfile3"; fake = fmemopen(input, strlen(input), "r"); n_files = process_input(fake, files, 16); ok(n_files == 3); ok(strcmp(files[0]->fn, "file1") == 0); ok(strcmp(files[1]->fn, "file2") == 0); ok(strcmp(files[2]->fn, "file3") == 0); return 0; } /* * Remove a file */ int watch_fd_exec_01() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; static char fn[] = "/dev/null"; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); /* event 1/1: 4 (-4) 0x21 0x1 0 0x84d5e800 */ ctx.event.nlist = 1; EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_DELETE, 0, files[0]); watch_loop(kq, argv); ok(ctx.event.nset == 3); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].udata == files[0]); ok(ctx.event.Set[1].ident); ok(ctx.event.Set[1].filter == EVFILT_VNODE); ok(ctx.event.Set[1].flags == EV_DELETE); /* remove */ ok(ctx.event.Set[1].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[1].udata == files[0]); ok(ctx.event.Set[2].ident); ok(ctx.event.Set[2].filter == EVFILT_VNODE); ok(ctx.event.Set[2].flags == (EV_CLEAR|EV_ADD)); /* reopen */ ok(ctx.event.Set[2].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[2].udata == files[0]); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "prog") == 0); ok(strcmp(ctx.exec.argv[0], "prog") == 0); ok(strcmp(ctx.exec.argv[1], "arg1") == 0); ok(strcmp(ctx.exec.argv[2], "arg2") == 0); return 0; } /* * Change a file attribute */ int watch_fd_exec_02() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; static char fn[] = "/dev/null"; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); ctx.event.nlist = 1; EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_ATTRIB, 0, files[0]); watch_loop(kq, argv); ok(ctx.event.nset == 1); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].udata == files[0]); ok(ctx.exec.count == 0); ok(ctx.exec.file == 0); return 0; } /* * Write to two files at once */ int watch_fd_exec_03() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; static char fn[] = "/dev/null"; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); strlcpy(files[1]->fn, fn, sizeof(files[1]->fn)); watch_file(kq, files[1]); ctx.event.nlist = 2; EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]); EV_SET(&ctx.event.List[1], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[1]); watch_loop(kq, argv); ok(ctx.event.nset == 2); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].data == 0); ok(ctx.event.Set[0].udata == files[0]->fn); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "prog") == 0); ok(strcmp(ctx.exec.argv[0], "prog") == 0); ok(strcmp(ctx.exec.argv[1], "arg1") == 0); ok(strcmp(ctx.exec.argv[2], "arg2") == 0); return 0; } /* * Write to a file and then remove it */ int watch_fd_exec_04() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; static char fn[] = "/dev/null"; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); ctx.event.nlist = 2; ctx.event.decrement = 2; EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]); EV_SET(&ctx.event.List[1], files[0]->fd, EVFILT_VNODE, 0, NOTE_DELETE, 0, files[0]); watch_loop(kq, argv); ok(ctx.event.nset == 3); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].udata == files[0]->fn); ok(ctx.event.Set[1].ident); ok(ctx.event.Set[1].filter == EVFILT_VNODE); ok(ctx.event.Set[1].flags == EV_DELETE); /* remove */ ok(ctx.event.Set[1].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[1].udata == files[0]->fn); ok(ctx.event.Set[2].ident); ok(ctx.event.Set[2].filter == EVFILT_VNODE); ok(ctx.event.Set[2].flags == (EV_CLEAR|EV_ADD)); /* reopen */ ok(ctx.event.Set[2].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[2].udata == files[0]->fn); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "prog") == 0); ok(strcmp(ctx.exec.argv[0], "prog") == 0); ok(strcmp(ctx.exec.argv[1], "arg1") == 0); ok(strcmp(ctx.exec.argv[2], "arg2") == 0); return 0; } /* * FIFO mode; triggerd by a leading '+' on the filename */ int set_fifo_01() { static char *argv[] = { "entr", "+notify", NULL }; ok(set_fifo(argv+1)); ok(ctx.open.fd > 0); ok(strcmp(fifo.fn, "notify") == 0); ok(fifo.fd == ctx.open.fd); return 0; } /* * Parse command line arguments up to but not including the utility to execute */ int set_options_01() { int argv_offset; char *argv[] = { "entr", "ruby", "main.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 1); ok(restart_mode == 0); return 0; } /* * Parse command line arguments for restart mode */ int set_options_02() { int argv_offset; char *argv[] = { "entr", "-r", "ruby", "main.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 2); ok(restart_mode == 1); return 0; } /* * In restart mode the first action should be to start the server */ int watch_fd_restart_01() { int kq = kqueue(); char *argv[] = { "ruby", "main.rb", NULL }; static char fn[] = "/dev/null"; restart_mode = 1; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); ctx.event.nlist = 0; watch_loop(kq, argv); ok(ctx.event.nset == 1); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].udata == files[0]); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "ruby") == 0); ok(strcmp(ctx.exec.argv[0], "ruby") == 0); ok(strcmp(ctx.exec.argv[1], "main.rb") == 0); return 0; } /* * Extending a file while in restart mode should result in start-kill-restart */ int watch_fd_restart_02() { int kq = kqueue(); char *argv[] = { "ruby", "main.rb", NULL }; static char fn[] = "/dev/null"; restart_mode = 1; strlcpy(files[0]->fn, fn, sizeof(files[0]->fn)); watch_file(kq, files[0]); child_pid = 222; ctx.event.nlist = 0; watch_loop(kq, argv); ok(ctx.event.nset == 1); ok(ctx.event.Set[0].ident); ok(ctx.event.Set[0].filter == EVFILT_VNODE); ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[0].fflags == (NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND)); ok(ctx.event.Set[0].udata == files[0]); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "ruby") == 0); ok(strcmp(ctx.exec.argv[0], "ruby") == 0); ok(strcmp(ctx.exec.argv[1], "main.rb") == 0); EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_EXTEND, 0, files[0]); ctx.event.nlist = 0; watch_loop(kq, argv); ok(ctx.signal.count == 1); ok(ctx.signal.pid == 222); ok(ctx.signal.sig == 15); ok(ctx.exec.count == 2); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "ruby") == 0); ok(strcmp(ctx.exec.argv[0], "ruby") == 0); ok(strcmp(ctx.exec.argv[1], "main.rb") == 0); return 0; } /* * main */ int test_main(int argc, char *argv[]) { signal(SIGSEGV, sighandler); /* set up pointers to test doubles */ _stat = fake_stat; _kevent = fake_kevent; _kill = fake_kill; _waitpid = fake_waitpid; _execvp = fake_execvp; _fork = fake_fork; _mkfifo = fake_mkfifo; _open = fake_open; /* all tests */ run(process_input_01); run(process_input_02); run(watch_fd_exec_01); run(watch_fd_exec_02); run(watch_fd_exec_03); run(watch_fd_exec_04); run(set_fifo_01); run(set_options_01); run(set_options_02); run(watch_fd_restart_01); run(watch_fd_restart_02); /* TODO: find out how we broke stdout */ fprintf(stderr, "%d of %d tests PASSED\n", tests_run-failures, tests_run); return failures; } int (*test_runner_main)(int argc, char **argv) = test_main; eradman-entr-7821588c309c/missing/compat.h0000644000000000000000000000056412260303503016273 0ustar 00000000000000/* compat.h */ #if defined(_LINUX_PORT) #include size_t strlcpy(char *to, const char *from, int l); void setproctitle(const char *fmt, ...); void compat_init_setproctitle(int argc, char *argv[]); #endif #if defined(_MACOS_PORT) #include void setproctitle(const char *fmt, ...); FILE *fmemopen(void *buf, size_t size, const char *mode); #endif eradman-entr-7821588c309c/missing/fmemopen.c0000644000000000000000000000705212260303503016610 0ustar 00000000000000/* $OpenBSD: fmemopen.c,v 1.2 2013/03/27 15:06:25 mpi Exp $ */ /* * Copyright (c) 2011 Martin Pieuchot * Copyright (c) 2009 Ted Unangst * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include FILE *__sfp(void); struct state { char *string; /* actual stream */ size_t pos; /* current position */ size_t size; /* allocated size */ size_t len; /* length of the data */ int update; /* open for update */ }; static int fmemopen_read(void *v, char *b, int l) { struct state *st = v; int i; for (i = 0; i < l && i + st->pos < st->len; i++) b[i] = st->string[st->pos + i]; st->pos += i; return (i); } static int fmemopen_write(void *v, const char *b, int l) { struct state *st = v; int i; for (i = 0; i < l && i + st->pos < st->size; i++) st->string[st->pos + i] = b[i]; st->pos += i; if (st->pos >= st->len) { st->len = st->pos; if (st->len < st->size) st->string[st->len] = '\0'; else if (!st->update) st->string[st->size - 1] = '\0'; } return (i); } static fpos_t fmemopen_seek(void *v, fpos_t off, int whence) { struct state *st = v; ssize_t base = 0; switch (whence) { case SEEK_SET: break; case SEEK_CUR: base = st->pos; break; case SEEK_END: base = st->len; break; } if (off > st->size - base || off < -base) { errno = EOVERFLOW; return (-1); } st->pos = base + off; return (st->pos); } static int fmemopen_close(void *v) { free(v); return (0); } static int fmemopen_close_free(void *v) { struct state *st = v; free(st->string); free(st); return (0); } FILE * fmemopen(void *buf, size_t size, const char *mode) { struct state *st; FILE *fp; int flags, oflags; if (size == 0) { errno = EINVAL; return (NULL); } if ((flags = __sflags(mode, &oflags)) == 0) { errno = EINVAL; return (NULL); } if (buf == NULL && ((oflags & O_RDWR) == 0)) { errno = EINVAL; return (NULL); } if ((st = malloc(sizeof(*st))) == NULL) return (NULL); if ((fp = __sfp()) == NULL) { free(st); return (NULL); } st->pos = 0; st->len = (oflags & O_WRONLY) ? 0 : size; st->size = size; st->update = oflags & O_RDWR; if (buf == NULL) { if ((st->string = malloc(size)) == NULL) { free(st); fp->_flags = 0; return (NULL); } *st->string = '\0'; } else { st->string = (char *)buf; if (oflags & O_TRUNC) *st->string = '\0'; if (oflags & O_APPEND) { char *p; if ((p = memchr(st->string, '\0', size)) != NULL) st->pos = st->len = (p - st->string); else st->pos = st->len = size; } } fp->_flags = (short)flags; fp->_file = -1; fp->_cookie = (void *)st; fp->_read = (flags & __SWR) ? NULL : fmemopen_read; fp->_write = (flags & __SRD) ? NULL : fmemopen_write; fp->_seek = fmemopen_seek; fp->_close = (buf == NULL) ? fmemopen_close_free : fmemopen_close; return (fp); } eradman-entr-7821588c309c/missing/kqueue.c0000644000000000000000000000710712260303503016302 0ustar 00000000000000/* * Copyright (c) 2013 Eric Radman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "compat.h" #include "../data.h" /* globals */ extern WatchFile **files; /* utility forwards */ static WatchFile * file_by_descriptor(int fd); /* utility functions */ static WatchFile * file_by_descriptor(int wd) { int i; for (i=0; files[i] != NULL; i++) { if (files[i]->fd == wd) return files[i]; } return NULL; /* lookup failed */ } /* shortcuts */ #define MILLISECOND 1000000 /* interface */ #define EVENT_SIZE (sizeof (struct inotify_event)) #define EVENT_BUF_LEN (32 * (EVENT_SIZE + 16)) /* * Conveniently inotify and kqueue ids both have the type `int` */ int kqueue(void) { return inotify_init(); } /* * Emulate kqueue(2). Only the flags used in entr.c are considered * Returns the number of eventlist structs filled by this call */ int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout) { int n; int wd; WatchFile *file; char buf[EVENT_BUF_LEN]; ssize_t len; int pos; struct inotify_event *iev; u_int fflags; const struct kevent *kev; int ignored; struct pollfd pfd; struct timespec delay = { 0, 30 * MILLISECOND }; if (nchanges > 0) { ignored = 0; for (n=0; nudata; if (kev->flags & EV_DELETE) { inotify_rm_watch(kq /* ifd */, kev->ident); file->fd = -1; /* invalidate */ } else if (kev->flags & EV_ADD) { wd = inotify_add_watch(kq /* ifd */, file->fn, IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MODIFY); if (wd < 0) return -1; close(file->fd); file->fd = wd; /* replace with watch descriptor */ } else ignored++; } return nchanges - ignored; } pfd.fd = kq; pfd.events = POLLIN; if ((timeout != 0 && (poll(&pfd, 1, timeout->tv_nsec/1000) == 0))) return 0; /* Consolidate events within 50ms */ n = 0; do { pos = 0; len = read(kq /* ifd */, &buf, EVENT_BUF_LEN); while ((pos < len) && (n < nevents)) { iev = (struct inotify_event *) &buf[pos]; pos += EVENT_SIZE + iev->len; #ifdef DEBUG fprintf(stderr, "wd: %d mask: 0x%x\n", iev->wd, iev->mask); #endif /* convert iev->mask; to comparable kqueue flags */ fflags = 0; if (iev->mask & IN_DELETE_SELF) fflags |= NOTE_DELETE; if (iev->mask & IN_CLOSE_WRITE) fflags |= NOTE_WRITE; if (fflags == 0) continue; eventlist[n].ident = iev->wd; eventlist[n].filter = EVFILT_VNODE; eventlist[n].flags = 0; eventlist[n].fflags = fflags; eventlist[n].data = 0; eventlist[n].udata = file_by_descriptor(iev->wd); n++; } nanosleep(&delay, NULL); } while ((poll(&pfd, 1, 30) > 0)); return n; } eradman-entr-7821588c309c/missing/setproctitle.c0000644000000000000000000000020512260303503017514 0ustar 00000000000000#ifndef HAVE_SETPROCTITLE void setproctitle(const char *fmt, ...) { /* not implemented */ } #endif /* HAVE_SETPROCTITLE */ eradman-entr-7821588c309c/missing/strlcpy.c0000644000000000000000000000303112260303503016473 0ustar 00000000000000/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } eradman-entr-7821588c309c/missing/sys/event.h0000644000000000000000000000560512260303503016750 0ustar 00000000000000/*- * Copyright (c) 1999,2000,2001 Jonathan Lemon * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #define EVFILT_VNODE (-4) /* attached to vnodes */ /* actions */ #define EV_ADD 0x0001 /* add event to kq (implies enable) */ #define EV_DELETE 0x0002 /* delete event from kq */ #define EV_ENABLE 0x0004 /* enable event */ #define EV_DISABLE 0x0008 /* disable event (not reported) */ /* flags */ #define EV_ONESHOT 0x0010 /* only report one occurrence */ #define EV_CLEAR 0x0020 /* clear event state after reporting */ /* * data/hint flags for EVFILT_VNODE, shared with userspace */ #define NOTE_DELETE 0x0001 /* vnode was removed */ #define NOTE_WRITE 0x0002 /* data contents changed */ #define NOTE_EXTEND 0x0004 /* size increased */ #define NOTE_ATTRIB 0x0008 /* attributes changed */ #define NOTE_LINK 0x0010 /* link count changed */ #define NOTE_RENAME 0x0020 /* vnode was renamed */ #define NOTE_REVOKE 0x0040 /* vnode access was revoked */ #define NOTE_TRUNCATE 0x0080 /* vnode was truncated */ #define EV_SET(kevp, a, b, c, d, e, f) do { \ (kevp)->ident = (a); \ (kevp)->filter = (b); \ (kevp)->flags = (c); \ (kevp)->fflags = (d); \ (kevp)->data = (e); \ (kevp)->udata = (f); \ } while(0) struct kevent { u_int ident; /* identifier for this event */ short filter; /* filter for event */ u_short flags; u_int fflags; int data; void *udata; /* opaque user data identifier */ }; int kqueue(void); int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout); eradman-entr-7821588c309c/regress.sh0000755000000000000000000000770712260303503015205 0ustar 00000000000000#!/bin/ksh # # Copyright (c) 2012 Eric Radman # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # test runner typeset -i tests=0 typeset -i assertions=0 function try { let tests+=1; this="$1"; } trap 'printf "$0: exit code $? on line $LINENO\nFAIL: $this\n"; exit 1' ERR function assert { let assertions+=1 [[ "$1" == "$2" ]] && { printf "."; return; } printf "\nFAIL: $this\n'$1' != '$2'\n"; exit 1 } function pause { sleep 0.4; } function setup { rm -f $tmp/*.out $tmp/file?; touch $tmp/file{1,2}; sleep 0.2; } tmp=$(mktemp -d /tmp/entr_regress.XXXXXXXXXX) # rebuild [ -f entr ] || { ./configure make clean make } # tests try "no arguments" ./entr 2> /dev/null || code=$? assert $code 2 try "reload option with no command to run" ./entr -r 2> /dev/null || code=$? assert $code 2 try "empty input" echo "" | ./entr echo 2> /dev/null || code=$? assert $code 2 try "no regular files provided as input" mkdir $tmp/dir1 ls $tmp | ./entr echo 2> /dev/null || code=$? rmdir $tmp/dir1 assert $code 1 try "watch and exec a program that is overwritten" setup cp $(which ls) $tmp/ls chmod 755 $tmp/ls echo $tmp/ls | ./entr $tmp/ls $tmp/file1 > $tmp/exec.out & bgpid=$! pause cp $(which ls) $tmp/ls pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/exec.out)" "$(ls $tmp/file1)" try "exec single shell command when a file is removed and replaced" setup ls $tmp/file* | ./entr file $tmp/file2 > $tmp/exec.out & bgpid=$! pause rm $tmp/file2 pause touch $tmp/file2 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/exec.out)" "$tmp/file2: empty" try "restart a server when a file is modified" setup echo "started." > $tmp/file1 ls $tmp/file2 | ./entr -r tail -f $tmp/file1 2> /dev/null > $tmp/exec.out & bgpid=$! pause assert "$(cat $tmp/exec.out)" "started." echo 456 >> $tmp/file2 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/exec.out)" "$(printf 'started.\nstarted.')" try "exec single shell command when two files change simultaneously" setup ln $tmp/file1 $tmp/file3 ls $tmp/file* | ./entr sh -c 'echo ping' > $tmp/exec.out & bgpid=$! pause echo 456 >> $tmp/file1 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/exec.out)" "ping" try "read each filename from a named pipe as they're modified" setup ls $tmp/file* | ./entr +$tmp/notify & bgpid=$! pause cat $tmp/notify > $tmp/namedpipe.out & pause echo 123 >> $tmp/file1 pause echo 789 >> $tmp/file2 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/namedpipe.out | sed 's/.*\///')" "$(printf 'file1\nfile2')" try "read each filename from a named pipe until a file is removed" setup ls $tmp/file* | ./entr +$tmp/notify 2> /dev/null || code=$? & bgpid=$! pause cat $tmp/notify > $tmp/namedpipe.out & pause echo 123 >> $tmp/file1 pause rm $tmp/file2 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/namedpipe.out | sed 's/.*\///')" "$(printf 'file1')" assert $code 1 tty > /dev/null && { try "exec an interactive utility when a file changes" setup ls $tmp/file* | ./entr sh -c 'tty | colrm 9; sleep 0.3' > $tmp/exec.out & bgpid=$! pause echo 456 >> $tmp/file2 pause kill -INT $bgpid wait $bgpid assert "$(cat $tmp/exec.out | tr '/pts' '/tty')" "/dev/tty" } # cleanup rm -r $tmp echo; echo "$tests tests and $assertions assertions PASSED" exit 0