eradman-entr-5321b01c9dab/.hg_archival.txt0000644000000000000000000000017412633050166016451 0ustar 00000000000000repo: 49108c05f40cf0f2abceacff753c2df1cd22e1ec node: 5321b01c9dabf8cf6d5bacd10c56b126215a1fe3 branch: default tag: entr-3.4 eradman-entr-5321b01c9dab/.hgtags0000644000000000000000000001241612633050166014643 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 e2e9b0c6f3e1cb48d0aed184a4268aeb346ee877 entr-2.6 e2e9b0c6f3e1cb48d0aed184a4268aeb346ee877 entr-2.6 0000000000000000000000000000000000000000 entr-2.6 5005e9e06e36eabb7159e94f0572c0d68194d600 entr-2.5 f9f7002759700a8e81232bfaea6e3666e3894fcb 2.6 0000000000000000000000000000000000000000 entr-2.6 8f83875c37be1821efbcb68361b54a27cc6af5f0 entr-2.6 f9f7002759700a8e81232bfaea6e3666e3894fcb 2.6 0000000000000000000000000000000000000000 2.6 12578a4562117fb02f236ed5ccab8e06e4973d40 entr-2.7 12578a4562117fb02f236ed5ccab8e06e4973d40 entr-2.7 0000000000000000000000000000000000000000 entr-2.7 0000000000000000000000000000000000000000 entr-2.7 1a5f856f032a6859ce514e66ce1ad2a21966253e entr-2.7 9192ece7198c7f95c889d3a039428dad8790a46e entr-2.8 a0fcbd830bd9bced97bb4fa3c9eb4a69ed98bdbd entr-2.9 d69fc3f5af94c79543d8ff60be4272b1493f1471 entr-3.0 fb68d8f869d5199ac94bc701ae8ada9b9c4901e9 entr-3.1 c20e34ec153a989f11648e5269dd2970c843e097 entr-3.2 220131754e1cac16b013d63f6394c8fd6bef8036 entr-3.3 dd6be59e7c6139df69e0d510891e8a6649b72d02 entr-3.4 dd6be59e7c6139df69e0d510891e8a6649b72d02 entr-3.4 0000000000000000000000000000000000000000 entr-3.4 eradman-entr-5321b01c9dab/LICENSE0000644000000000000000000000650612633050166014375 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-5321b01c9dab/Makefile.bsd0000644000000000000000000000247212633050166015575 0ustar 00000000000000PREFIX ?= /usr/local MANPREFIX ?= ${PREFIX}/man RELEASE = 3.4 CPPFLAGS += -DRELEASE=\"${RELEASE}\" all: versioncheck entr env: @echo "CC = ${CC}" @echo "DESTDIR = ${DESTDIR}" @echo "EXTRA_SRC = ${EXTRA_SRC}" @echo "LDFLAGS = ${LDFLAGS}" @echo "MANPREFIX = ${MANPREFIX}" @echo "PREFIX = ${PREFIX}" @echo "SRC = ${SRC}" test: entr_spec entr @echo "Running unit tests" @./entr_spec gcc-lint: clean @CPPFLAGS="-std=c89 -pedantic -Wall -Wpointer-arith -Wbad-function-cast" make test regress: @echo "Running functional tests" @./system_test.sh entr: entr.c ${EXTRA_SRC} ${CC} ${CFLAGS} ${CPPFLAGS} ${EXTRA_SRC} entr.c -o $@ ${LDFLAGS} entr_spec: entr_spec.c entr.c ${EXTRA_SRC} ${CC} ${CFLAGS} ${CPPFLAGS} ${EXTRA_SRC} entr_spec.c -o $@ ${LDFLAGS} 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 -m 644 entr.1 ${DESTDIR}${MANPREFIX}/man1 uninstall: rm ${DESTDIR}${PREFIX}/bin/entr rm ${DESTDIR}${MANPREFIX}/man1/entr.1 versioncheck: @head -n3 NEWS | egrep -q "^= Next Release: ${RELEASE}|^== ${RELEASE}: " @grep -q "Version:.*${RELEASE}" contrib/entr.spec .PHONY: all env test gcc-lint clean distclean install uninstall versioncheck eradman-entr-5321b01c9dab/Makefile.linux0000644000000000000000000000024012633050166016153 0ustar 00000000000000CPPFLAGS += -D_GNU_SOURCE -D_LINUX_PORT -Imissing MANPREFIX ?= ${PREFIX}/share/man EXTRA_SRC = missing/strlcpy.c missing/kqueue_inotify.c include Makefile.bsd eradman-entr-5321b01c9dab/Makefile.macos0000644000000000000000000000016112633050166016120 0ustar 00000000000000CPPFLAGS += -D_MACOS_PORT MANPREFIX ?= ${PREFIX}/share/man EXTRA_SRC = missing/fmemopen.c include Makefile.bsd eradman-entr-5321b01c9dab/NEWS0000644000000000000000000001725112633050166014066 0ustar 00000000000000= Release History == 3.4: December 12, 2015 - Terminate the child process before exiting when the reload option is combined with the directory watch option - Direct users to http://entrproject.org/limits.html if the maximum number of open files is exceeded == 3.3: October 23, 2015 - Substitute '/_' only with regular files when using the directory watch option - Bugfix when tracking the first file to change - Display release number along with usage == 3.2: April 9, 2015 - Execute the utility immediately after registering watch events. Old behavior is restored using new postpone option ('-p') - Relocated 'reload-browser' to http://entrproject.org/scripts/reload-browser - Drop FIFO support to further simply code and documentation. FIFO mode was conceptually good, but EXEC mode is intelligent hand easily handles the vast majority of use cases. - Remove kFreeBSD as a target platform == 3.1: March 4, 2015 - Return error code indicating exit reason after handling signals - Restart mode sets process group to ensure that wrapper scripts do not prevent signals from reaching child processes - Removed DEBUG printf statements == 3.0: December 19, 2014 - Man page highlights the flexibility of executing a commands using a shell - 'reload-browser', a cross-platform script for reloading the current tab in one or more browsers - system_test.sh auto-upgrades to bash if the default shell does not support the ERR trap - Workaround implemented for Linux using fstat(2) to detect if a binary was removed if inotify issues IN_ATTRIB but not IN_DELETE_SELF - Use calloc(3) to take advantage of overflow detection == 2.9: June 10, 2014 - RPM package by @funollet - Moved alternate Makefiles to contrib/ - Trigger actions when a file is truncated on BSD - Improved format of DEBUG messages - Added missing extern for missing/fmemopen.c to avoid warning on MacOS 10.7+ - No longer register NOTE_EXTEND; this event is not always merged with NOTE_WRITE on MacOS - New directory-watch option ('-d'). In this mode a shell while-loop can be used to track the addition of new files. == 2.8: March 15, 2014 - Iteratively check for new file system events using a 1ms timeout after a file has been renamed or removed in order to prevent the utility from being executed before successive changes from a version control system have completed. - Regression tests are now system tests that depend on and use Vim and Mercurial - Timing on regression tests has been tightened to only support real hardware; they are not expected to pass in emulated environments. - More correctly identify the first file to trigger a file change notification - Regression tests no longer require ksh - Renamed regress.sh to system_test.sh == 2.7: February 12, 2014 - Changed function pointer prefixes from '_' to 'x' to avoid runtime symbol collisions in libc. Required to build on DragonFlyBSD - Added this NEWS file to provide detailed release history for port maintainers - Subsitute '/_' argument with the first file that changed - Man page formatted with more semantically correct markup - Multiple events on the same file are merged on Linux to prevent duplicate writes to a named pipe == 2.6: January 23, 2014 - Added -c option to clear the screen before calling execvp(3) - Changed command line option '{}' to '/_' - Renamed Makefile.linux-lbsd to Makefile.debian - Include CPPFLAGS since some build systems (namely Debian) use it to specify hardening options - Raise ARG_MAX from 4K to 2MB on Linux, since _SC_ARG_MAX returns an incorrect value on Linux - Handle RENAME events that are not followed by DELETE. This enables us to cope with the backup option in Vim. == 2.5: December 30, 2013 - Restore full 50ms period for consolidating events on Linux. Required for the Geany editor - Remove use of setproctitle(3), no plans to implement this on Linux and MacOS. This also eliminates use of the 3-clause license - New feature: the first occurrence of '{}' on the command line will be replaced with the name of the first file under watch - Set PAGER to an absolute path (/bin/cat) - Prevent interactive utilities from paging output == 2.4: December 18, 2013 - Reintroduce ${DESTDIR} for STAGEDIR installation on FreeBSD and pkgsrc - License file describes the copyright holders for the compatibility libraries == 2.3: December 12, 2013 - Wait for processes to terminate in restart mode - Set process title for blocking events - Correct multiple bugs in the inotify->kqueue translation layer - Print out detailed instructions if the user provides configure arguments - Remove ${DESTDIR} from Makefile which appears to interfere with OpenBSD ports - The handler for SIGSEGV prints file name and line number if a unit test fails - Reduce the number of attempts to re-open files to 10 (total of 1 second) - Define architecture in Makefile instead of testing for __linux or __APPLE__ == 2.2: August 07, 2013 - Process every delete or rename event to ensure files remain tracked - Always process DELETE or RENAME events to prevent a files from escaping watch - inotify(7) events are consolidated for a duration of 50ms as a workaround for an apparent bug in glib/gio where a file is written and then removed - Execute mode can run a file under watch by retrying if text file is busy - Exit non-zero if list of regular files exceeds the hard limit set by the current login class - Ignore directories and special files; exit with error code 1 if no regular files are provided as input. == 2.1: July 01, 2013 - Zero-dependency build on Linux using built-in compatibility layer - Add poll with optional timeout in order to prevent change events from accumulating while running a command - Invalidate file->fd after inotify event is closed. Do not respond to IN_MOVE_SELF since this event only occurs on Linux NFS mounts - Optional build on GNU/Linux using external compatibility libraries libkqueue and libbsd - Fix bug in finding the correct filename to write to a FIFO - Correct build on MacOS == 2.0: June 17, 2013 - Support for old architectures without C99 support - More aggressively remove duplicate events - install/uninstall uses DESTDIR to be compatible with pkgsrc == 1.9: April 13, 2013 - New auto-reload option ('-r'). Thanks for Kevin Cox for the idea - Add a simple configure script, BSD is the default - Do not install directories...they should already exist - Man page installation path can be set with MANPATH. MacOS expects it to be under /usr/local/share == 1.8: May 12, 2013 - Ensure that loosing a file under watch is results in an error == 1.7: November 20, 2012 - Allocate an memory based on _SC_OPEN_MAX, not rl.rlim_max - Stat deleted file until success before running a command - Handle SIGTERM and remove named pipe on exit == 1.6: August 10, 2012 - Separate makefile for Linux to make building easier - No need for pthreads on BSD - Respond to RENAME and LINK events to improve behavior on NFS == 1.5: July 29, 2012 - Support interactive applications by opening a TTY - Successfully stat deleted files before running a command == 1.4: May 22, 2013 - Added regression tests - Delete events instead of relying on implicit removal by close(2) for better compatibility with libkqueue on Linux == 1.3: May 17, 2012 - Added FIFO support. Thanks to James K. Lowden for the idea - Use min(OPEN_MAX, rl.rlim_max) to be more compatible of Mac OS. - More correct use of fgets(3) when reading STDIN == 1.2: April 26, 2012 - Added support for Linux via libkqueue == 1.1: April 17, 2012 - Added support for Mac OS == 1.0: April 12, 2012 - Initial release at https://bitbucket.org/eradman/entr - Builds on all major BSD platforms eradman-entr-5321b01c9dab/README.md0000644000000000000000000000342512633050166014644 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` was written 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`. Installation - Debian --------------------- apt-get install entr Examples from `man entr` ------------------------ Rebuild a project if source files change, limiting output to the first 20 lines: $ find src/ | entr sh -c 'make | head -n 20' Launch and auto-reload a node.js server: $ ls *.js | entr -r node app.js Clear the screen and run a query after the SQL script is updated: $ echo my.sql | entr -p psql -f /_ Rebuild project if a source file is modified or added to the src/ directory: $ while sleep 1; do ls src/*.rb | entr -d rake; done News ---- A release history as well as features in the upcoming release are covered in the [NEWS][NEWS] file. License ------- Source is under and ISC-style license. See the [LICENSE][LICENSE] file for more detailed information on the license used for compatibility libraries. [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 [NEWS]: http://www.bitbucket.org/eradman/entr/src/default/NEWS [LICENSE]: http://www.bitbucket.org/eradman/entr/src/default/LICENSE eradman-entr-5321b01c9dab/configure0000755000000000000000000000077312633050166015277 0ustar 00000000000000#!/bin/sh copy_mk() { cmd="cp $1/Makefile.$2 Makefile" echo "$cmd"; $cmd } case `uname` in Darwin) copy_mk . macos ;; Linux) copy_mk . linux ;; *) copy_mk . bsd ;; esac [ $# = 0 ] && exit 0 cat < - Modified description to match features of 3.2 * Sun Feb 9 2014 Jordi Funollet Pujol - Packaged 2.6-1 eradman-entr-5321b01c9dab/data.h0000644000000000000000000000171312633050166014445 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 /* data */ typedef struct { char fn[PATH_MAX]; int fd; int is_dir; int file_count; } WatchFile; /* declare as extern in source */ WatchFile **files; eradman-entr-5321b01c9dab/entr.10000644000000000000000000000631412633050166014417 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 March 13, 2015 .Dt ENTR 1 .Os .Sh NAME .Nm entr .Nd run arbitrary commands when files change .Sh SYNOPSIS .Nm .Op Fl cdpr .Ar utility .Op Ar argument ... .Op Ar /_ .Sh DESCRIPTION A list of files provided on the standard input and the .Ar utility is executed using the supplied arguments if any of them change. .Nm waits for the child process to finish before responding to subsequent file system events. A TTY is also opened before entering the watch loop in order to support interactive utilities. .Pp The arguments are as follows: .Bl -tag -width Ds .It Fl c Execute .Pa /usr/bin/clear before invoking the .Ar utility specified on the command line. .It Fl d Track the directories of regular files provided as input and exit if a new file is added. This option also enables directories to be specified explicitly. Files with names beginning with .Ql \&. are ignored. .It Fl p Postpone the first execution of the .Ar utility until a file is modified. .It Fl r Reload a persistent child process. .Dv SIGTERM is used to terminate the .Ar utility before it is restarted. A process group is created to prevent shell scripts from masking signals. .Nm waits for the .Ar utility to exit to ensure that resources such as sockets have been closed. .El .Pp The first occurrence of .Ar /_ on the command line will be replaced with the absolute path of the first file that was modified. If the restart option is used the first file under watch is treated as the default. .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 utility exits with one of the following values: .Pp .Bl -tag -width Ds -offset indent -compact .It Li 0 .Dv SIGINT or .Dv SIGTERM was received .It Li 1 No regular files were provided as input or an error occurred .It Li 2 A file was added to a directory while using the while using the directory watch option .El .Sh EXAMPLES Rebuild a project if source files change, limiting output to the first 20 lines: .Pp .Dl $ find src/ | entr sh -c 'make | head -n 20' .Pp Launch and auto-reload a node.js server: .Pp .Dl $ ls *.js | entr -r node app.js .Pp Clear the screen and run a query after the SQL script is updated: .Pp .Dl $ echo my.sql | entr -p psql -f /_ .Pp Rebuild project if a source file is modified or added to the src/ directory: .Pp .Dl $ while sleep 1; do ls src/*.rb | entr -d rake; done eradman-entr-5321b01c9dab/entr.c0000644000000000000000000003100612633050166014475 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 #include #include "missing/compat.h" #include "data.h" /* events to watch for */ #define NOTE_ALL NOTE_DELETE|NOTE_WRITE|NOTE_RENAME|NOTE_TRUNCATE /* shortcuts */ #define min(a, b) (((a) < (b)) ? (a) : (b)) #define MEMBER_SIZE(S, M) sizeof(((S *)0)->M) /* function pointers */ int (*test_runner_main)(int, char**); int (*xstat)(const char *, struct stat *); int (*xkillpg)(pid_t, int); int (*xexecvp)(const char *, char *const []); pid_t (*xwaitpid)(pid_t, int *, int); pid_t (*xfork)(); int (*xkevent)(int, const struct kevent *, int, struct kevent *, int , const struct timespec *); int (*xopen)(const char *path, int flags, ...); char * (*xrealpath)(const char *, char *); void (*xfree)(void *); void (*xerrx)(int, const char *, ...); int (*xlist_dir)(char *); /* globals */ extern int optind; extern WatchFile **files; WatchFile *leading_edge; int child_pid; int clear_opt; int dirwatch_opt; int restart_opt; int postpone_opt; /* forwards */ static void usage(); static void terminate_utility(); static void handle_exit(int sig); static int process_input(FILE *, WatchFile *[], int); static int set_options(char *[]); static int list_dir(char *); static void run_utility(char *[]); static void watch_file(int, WatchFile *); static int compare_dir_contents(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 */ xstat = stat; xkevent = kevent; xkillpg = killpg; xexecvp = execvp; xwaitpid = waitpid; xfork = fork; xopen = open; xrealpath = realpath; xfree = free; xerrx = errx; xlist_dir = list_dir; /* call usage() if no command is supplied */ if (argc < 2) usage(); argv_index = set_options(argv); /* normally a user will exit this utility by do_execting Ctrl-C */ act.sa_flags = 0; act.sa_flags = SA_RESETHAND; 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 = calloc(rl.rlim_cur+1, sizeof(WatchFile *)); 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(1, "No regular files to watch"); if (n_files == -1) errx(1, "Too many files listed; the hard limit for your login" " class is %d. Please consult" " http://entrproject.org/limits.html", (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, "release: %s\n", RELEASE); fprintf(stderr, "usage: %s [-cdpr] utility [args, [/_], ...] < filenames\n", __progname); exit(1); } void terminate_utility() { int status; if (child_pid > 0) { xkillpg(child_pid, SIGTERM); xwaitpid(child_pid, &status, 0); child_pid = 0; } } /* Callbacks */ void handle_exit(int sig) { terminate_utility(); raise(sig); } /* * 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, *path; int n_files = 0; struct stat sb; int i, matches; 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; if (xstat(buf, &sb) == -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)); files[n_files]->is_dir = 0; files[n_files]->file_count = 0; n_files++; } /* also watch the directory if it's not already in the list */ if (dirwatch_opt == 1) { if (S_ISDIR(sb.st_mode) != 0) path = &buf[0]; else if ((path = dirname(buf)) == 0) err(1, "dirname '%s' failed", buf); for (matches=0, i=0; ifn, path) == 0) matches++; if (matches == 0) { files[n_files] = malloc(sizeof(WatchFile)); strlcpy(files[n_files]->fn, path, MEMBER_SIZE(WatchFile, fn)); files[n_files]->is_dir = 1; files[n_files]->file_count = xlist_dir(path); n_files++; } } if (n_files+1 > max_files) return -1; } return n_files; } int list_dir(char *dir) { struct dirent *dp; DIR *dfd = opendir(dir); int count = 0; if (dfd == NULL) errx(1, "Unable to open directory: %s", dir); while((dp = readdir(dfd)) != NULL) if (dp->d_name[0] != '.') count++; closedir(dfd); return count; } /* * Evaluate command line arguments and return an offset to the command to * execute. */ int set_options(char *argv[]) { int ch; int argc; /* read arguments until we reach a command */ for (argc=1; argv[argc] != 0 && argv[argc][0] == '-'; argc++); while ((ch = getopt(argc, argv, "cdpr")) != -1) { switch (ch) { case 'c': clear_opt = 1; break; case 'd': dirwatch_opt = 1; break; case 'p': postpone_opt = 1; break; case 'r': restart_opt = 1; break; default: usage(); } } /* no command to run */ if (argv[optind] == '\0') usage(); return optind; } /* * Execute the program supplied on the command line. If restart was set * then send the child process SIGTERM and restart it. */ void run_utility(char *argv[]) { int pid; int i, m; int ret, status; struct timespec delay = { 0, 1000000 }; char **new_argv; char *p, *arg_buf; int argc; if (restart_opt == 1) terminate_utility(); /* clone argv on each invocation to make the implementation of more * complex subsitution rules possible and easy */ for (argc=0; argv[argc]; argc++); arg_buf = malloc(ARG_MAX); new_argv = calloc(argc+1, sizeof(char *)); for (m=0, i=0, p=arg_buf; ifn, p)); m++; } else p += strlcpy(p, argv[i], ARG_MAX - (p - arg_buf)); p++; } pid = xfork(); if (pid == -1) err(1, "can't fork"); if (pid == 0) { if (clear_opt == 1) system("/usr/bin/clear"); /* Set process group so subprocess can be signaled */ if (restart_opt == 1) setpgid(0, getpid()); /* wait up to 1 seconds for each file to become available */ for (i=0; i < 10; i++) { ret = xexecvp(new_argv[0], new_argv); if (errno == ETXTBSY) nanosleep(&delay, NULL); else break; } if (ret != 0) err(1, "exec %s", new_argv[0]); } child_pid = pid; if (restart_opt == 0) xwaitpid(pid, &status, 0); xfree(arg_buf); xfree(new_argv); } /* * 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 * 1000000 }; /* wait up to 1 second for file to become available */ for (i=0; i < 10; i++) { #ifdef O_EVTONLY file->fd = xopen(file->fn, O_RDONLY|O_EVTONLY); #else file->fd = xopen(file->fn, O_RDONLY); #endif if (file->fd == -1) nanosleep(&delay, NULL); else break; } if (file->fd == -1) err(1, "cannot open `%s'", file->fn); EV_SET(&evSet, file->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ALL, 0, file); if (xkevent(kq, &evSet, 1, NULL, 0, NULL) == -1) err(1, "failed to register VNODE event"); } /* * Wait for directory contents to stabilize */ int compare_dir_contents(WatchFile *file) { int i; struct timespec delay = { 0, 100 * 1000000 }; /* wait up to 0.5 seconds for file to become available */ for (i=0; i < 5; i++) { if (xlist_dir(file->fn) == file->file_count) return 0; nanosleep(&delay, NULL); } return 1; } /* * Wait for events to and execute a command. Four major concerns are in play: * leading_edge: Global reference to the first file to have changed * reopen_only : Unlink or rename events which require us to spin while * waiting for the file to reappear. These must always be * processed * collate_only: Changes that indicate that more events are likely to occur. * Watch for more events using a short timeout * do_exec : Delay execution until all events have been processed. Allow * the user to edit files while the utility is running without * any visible side-effects * dir_modified: The number of files changed for a directory under watch */ void watch_loop(int kq, char *argv[]) { struct kevent evSet; struct kevent evList[32]; int nev; WatchFile *file; int i; struct timespec evTimeout = { 0, 1000000 }; int reopen_only = 0; int collate_only = 0; int do_exec = 0; int dir_modified = 0; int leading_edge_set = 0; leading_edge = files[0]; /* default */ if (postpone_opt == 0) run_utility(argv); main: if ((reopen_only == 1) || (collate_only == 1)) nev = xkevent(kq, NULL, 0, evList, 32, &evTimeout); else { nev = xkevent(kq, NULL, 0, evList, 32, NULL); dir_modified = 0; } /* escape for test runner */ if ((nev == -2) && (collate_only == 0)) return; for (i=0; iis_dir == 1) dir_modified += compare_dir_contents(file); else if (leading_edge_set == 0) if ((reopen_only == 0) && (collate_only == 0)) { leading_edge = file; leading_edge_set = 1; } } collate_only = 0; for (i=0; ifd, EVFILT_VNODE, EV_DELETE, NOTE_ALL, 0, file); if (xkevent(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); collate_only = 1; } } if (reopen_only == 1) { reopen_only = 0; goto main; } for (i=0; iis_dir == 1) && (dir_modified == 0)) continue; if (evList[i].fflags & NOTE_DELETE || evList[i].fflags & NOTE_WRITE || evList[i].fflags & NOTE_RENAME || evList[i].fflags & NOTE_TRUNCATE) { if ((dir_modified > 0) && (restart_opt == 1)) continue; do_exec = 1; } } if (collate_only == 1) goto main; if (do_exec == 1) { do_exec = 0; run_utility(argv); reopen_only = 1; leading_edge_set = 0; } if (dir_modified > 0) { terminate_utility(); xerrx(2, "directory altered"); } goto main; } eradman-entr-5321b01c9dab/entr_spec.c0000644000000000000000000005060412633050166015514 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; } event; struct { int pid; int sig; int count; } signal; struct { int pid; int count; } wait; struct { int fd; const char *path; } open; struct { int count; } exit; } 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; /* getopt(3) keeps an external reference */ optind = 1; /* initialize global data */ clear_opt = 0; dirwatch_opt = 0; postpone_opt = 0; restart_opt = 0; leading_edge = 0; files = calloc(max_files, sizeof(WatchFile *)); 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) { ctx.wait.pid = wpid; ctx.wait.count++; return wpid; } char * fake_realpath(const char *pathname, char *resolved) { snprintf(resolved, PATH_MAX, "/home/user/%s", pathname); return resolved; } int fake_list_dir(char *path) { return 2; } pid_t fake_fork() { return child_pid; /* pretend to be the child */ } void fake_free(void *ptr) { } /* mock objects */ /* * kevent(2) is used to change and retrieve events. * This version always returns at most 2 events */ int fake_kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout) { int decrement; decrement = MIN(ctx.event.nlist, 2); /* 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 -= decrement; return decrement; } /* no more events, use bogus return code to cause the main loop to exit */ return -2; } /* spies */ int fake_killpg(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; } void fake_errx(int eval, const char *msg, ...) { ctx.exit.count++; } /* 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; } /* * Read a list of use supplied files and directories */ int process_input_03() { int n_files; FILE *fake; char input[] = "dir1\nfile1\nfile2\nfile3"; dirwatch_opt = 1; fake = fmemopen(input, strlen(input), "r"); n_files = process_input(fake, files, 32); ok(n_files == 5); ok(strcmp(files[0]->fn, "dir1") == 0); ok(strcmp(files[1]->fn, "file1") == 0); ok(strcmp(files[2]->fn, ".") == 0); ok(strcmp(files[3]->fn, "file2") == 0); ok(strcmp(files[4]->fn, "file3") == 0); ok(files[0]->is_dir == 1); /* dir1 */ ok(files[1]->is_dir == 0); /* file1 */ ok(files[2]->is_dir == 1); /* . */ ok(files[3]->is_dir == 0); /* file2 */ ok(files[4]->is_dir == 0); /* file3 */ return 0; } /* * Remove a file */ int watch_fd_exec_01() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, "arg1", 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_ALL)); 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_ALL)); 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_ALL)); 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); ok(ctx.exit.count == 0); return 0; } /* * Change a file attribute */ int watch_fd_exec_02() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, "main.py", 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_ALL)); ok(ctx.event.Set[0].udata == files[0]); ok(ctx.exec.count == 0); ok(ctx.exec.file == 0); ok(ctx.exit.count == 0); return 0; } /* * Write to three files at once */ int watch_fd_exec_03() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, "main.py", sizeof(files[0]->fn)); watch_file(kq, files[0]); strlcpy(files[1]->fn, "util.py", sizeof(files[1]->fn)); watch_file(kq, files[1]); strlcpy(files[2]->fn, "app.py", sizeof(files[2]->fn)); watch_file(kq, files[2]); ctx.event.nlist = 3; 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]); EV_SET(&ctx.event.List[2], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[2]); watch_loop(kq, argv); ok(strcmp(leading_edge->fn, "main.py") == 0); 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_ALL)); 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); ok(ctx.exit.count == 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 }; postpone_opt = 1; strlcpy(files[0]->fn, "arg1", sizeof(files[0]->fn)); watch_file(kq, files[0]); 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[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_ALL)); 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_ALL)); 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_ALL)); 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); ok(ctx.exit.count == 0); return 0; } /* * Rename a file without removing it (e.g. Vim's backup option) */ int watch_fd_exec_05() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, "arg1", 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_RENAME, 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_ALL)); 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_ALL)); 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_ALL)); 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); ok(ctx.exit.count == 0); return 0; } /* * Add a file to a directory and wait for child to exit */ int watch_fd_exec_06() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, ".", sizeof(files[0]->fn)); files[0]->is_dir = 1; files[0]->file_count = 1; strlcpy(files[1]->fn, "run.sh", sizeof(files[0]->fn)); watch_file(kq, files[0]); watch_file(kq, files[1]); child_pid = 222; dirwatch_opt = 1; restart_opt = 1; ctx.event.nlist = 1; EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]); 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_ALL)); 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_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[1].fflags == (NOTE_ALL)); ok(ctx.event.Set[1].udata == files[1]->fn); ok(ctx.exec.count == 0); ok(ctx.wait.count == 1); ok(ctx.wait.pid == 222); ok(ctx.exit.count == 1); return 0; } /* * Add a file to a directory and write to a file */ int watch_fd_exec_07() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; strlcpy(files[0]->fn, ".", sizeof(files[0]->fn)); files[0]->is_dir = 1; strlcpy(files[1]->fn, "run.sh", sizeof(files[0]->fn)); watch_file(kq, files[0]); watch_file(kq, files[1]); dirwatch_opt = 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_ALL)); 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_CLEAR|EV_ADD)); /* open */ ok(ctx.event.Set[1].fflags == (NOTE_ALL)); ok(ctx.event.Set[1].udata == files[1]->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); ok(ctx.exit.count == 1); return 0; } /* * Write to a file in directory watch mode */ int watch_fd_exec_08() { int kq = kqueue(); static char *argv[] = { "prog", "arg1", "arg2", NULL }; postpone_opt = 1; dirwatch_opt = 1; strlcpy(files[0]->fn, "src", sizeof(files[0]->fn)); files[0]->is_dir = 1; watch_file(kq, files[0]); strlcpy(files[1]->fn, "main.py", 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.exec.count == 1); ok(ctx.exec.file != 0); ok(ctx.exit.count == 1); ok(strcmp(leading_edge->fn, "main.py") == 0); 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", "test1.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 1); ok(restart_opt == 0); ok(clear_opt == 0); ok(dirwatch_opt == 0); return 0; } /* * Parse command line arguments for restart mode */ int set_options_02() { int argv_offset; char *argv[] = { "entr", "-r", "ruby", "test2.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 2); ok(restart_opt == 1); ok(clear_opt == 0); ok(dirwatch_opt == 0); ok(postpone_opt == 0); return 0; } /* * Parse command line arguments with the clear option */ int set_options_03() { int argv_offset; char *argv[] = { "entr", "-c", "ruby", "test3.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 2); ok(restart_opt == 0); ok(clear_opt == 1); ok(dirwatch_opt == 0); ok(postpone_opt == 0); return 0; } /* * Parse command line arguments with the directory watch option */ int set_options_04() { int argv_offset; char *argv[] = { "entr", "-d", "ruby", "test4.rb", NULL }; argv_offset = set_options(argv); ok(argv_offset == 2); ok(restart_opt == 0); ok(clear_opt == 0); ok(dirwatch_opt == 1); ok(postpone_opt == 0); return 0; } /* * Ensure that command line arguments are not confused with utility arguments */ int set_options_05() { int argv_offset; char *argv[] = { "entr", "ls", "-r", "-c", NULL }; argv_offset = set_options(argv); ok(argv_offset == 1); ok(restart_opt == 0); ok(clear_opt == 0); 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 }; restart_opt = 1; strlcpy(files[0]->fn, "main.rb", sizeof(files[0]->fn)); watch_file(kq, files[0]); ctx.event.nlist = 0; watch_loop(kq, argv); ok(strcmp(leading_edge->fn, "main.rb") == 0); 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_ALL)); 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 }; restart_opt = 1; strlcpy(files[0]->fn, "main.rb", 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_ALL)); 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; } /* * Substitue '/_' with the first file that leading_edge */ int run_utility_01() { static char *argv[] = { "psql", "-f", "/_", NULL }; char input[] = "one.sql\ntwo.sql"; FILE *fake; fake = fmemopen(input, strlen(input), "r"); (void) process_input(fake, files, 3); leading_edge = files[1]; run_utility(argv); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "psql") == 0); ok(strcmp(ctx.exec.argv[0], "psql") == 0); ok(strcmp(ctx.exec.argv[1], "-f") == 0); ok(strcmp(ctx.exec.argv[2], "/home/user/two.sql") == 0); return 0; } /* * Substitue only the first occurance of '/_' */ int run_utility_02() { static char *argv[] = { "/_", "/_", NULL }; char input[] = "one.sh\ntwo.sh"; FILE *fake; fake = fmemopen(input, strlen(input), "r"); (void) process_input(fake, files, 3); leading_edge = files[0]; run_utility(argv); ok(ctx.exec.count == 1); ok(ctx.exec.file != 0); ok(strcmp(ctx.exec.file, "/home/user/one.sh") == 0); ok(strcmp(ctx.exec.argv[0], "/home/user/one.sh") == 0); ok(strcmp(ctx.exec.argv[1], "/_") == 0); return 0; } /* * main */ int test_main(int argc, char *argv[]) { signal(SIGSEGV, sighandler); /* set up pointers to test doubles */ xstat = fake_stat; xkevent = fake_kevent; xkillpg = fake_killpg; xwaitpid = fake_waitpid; xexecvp = fake_execvp; xfork = fake_fork; xopen = fake_open; xrealpath = fake_realpath; xfree = fake_free; xerrx = fake_errx; xlist_dir = fake_list_dir; /* all tests */ run(process_input_01); run(process_input_02); run(process_input_03); run(watch_fd_exec_01); run(watch_fd_exec_02); run(watch_fd_exec_03); run(watch_fd_exec_04); run(watch_fd_exec_05); run(watch_fd_exec_06); run(watch_fd_exec_07); run(watch_fd_exec_08); run(set_options_01); run(set_options_02); run(set_options_03); run(set_options_04); run(set_options_05); run(watch_fd_restart_01); run(watch_fd_restart_02); run(run_utility_01); run(run_utility_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-5321b01c9dab/missing/compat.h0000644000000000000000000000053212633050166016466 0ustar 00000000000000/* compat.h */ #if !defined(NOTE_TRUNCATE) #define NOTE_TRUNCATE 0 #endif #if defined(_LINUX_PORT) #include size_t strlcpy(char *to, const char *from, int l); #endif #if defined(_MACOS_PORT) #include FILE *fmemopen(void *buf, size_t size, const char *mode); #endif #if !defined(ARG_MAX) #define ARG_MAX 2097152 #endif eradman-entr-5321b01c9dab/missing/fmemopen.c0000644000000000000000000000715712633050166017016 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 /* externs from from "local.h" */ FILE *__sfp(void); int __sflags(const char *, int *); 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-5321b01c9dab/missing/kqueue_inotify.c0000644000000000000000000000766712633050166020256 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 #include #include #include "compat.h" #include "../data.h" /* globals */ extern WatchFile **files; /* 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 */ } /* interface */ #define EVENT_SIZE (sizeof (struct inotify_event)) #define EVENT_BUF_LEN (32 * (EVENT_SIZE + 16)) #define IN_ALL IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MODIFY|IN_MOVE_SELF|IN_ATTRIB /* * 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 stat sb; 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_ALL); 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/1000000) == 0)) return 0; n = 0; do { pos = 0; len = read(kq /* ifd */, &buf, EVENT_BUF_LEN); if (len < 0) { /* SA_RESTART doesn't work for inotify fds */ if (errno == EINTR) continue; else perror("read"); } while ((pos < len) && (n < nevents)) { iev = (struct inotify_event *) &buf[pos]; pos += EVENT_SIZE + iev->len; /* 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 (iev->mask & IN_MOVE_SELF) fflags |= NOTE_RENAME; if (iev->mask & IN_ATTRIB) { if ((fstat(iev->wd, &sb) == -1) && errno == ENOENT) fflags |= NOTE_DELETE; } if (fflags == 0) continue; /* merge events if we're not acting on a new file descriptor */ if ((n > 0) && (eventlist[n-1].ident == iev->wd)) fflags |= eventlist[--n].fflags; 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); if (eventlist[n].udata) n++; } } while ((poll(&pfd, 1, 50) > 0)); return n; } eradman-entr-5321b01c9dab/missing/strlcpy.c0000644000000000000000000000303112633050166016673 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-5321b01c9dab/missing/sys/event.h0000644000000000000000000000560512633050166017150 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-5321b01c9dab/system_test.sh0000755000000000000000000002015412633050166016305 0ustar 00000000000000#!/bin/sh # # 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 trap 'printf "$0: exit code $? on line $LINENO\nFAIL: $this\n"; exit 1' ERR \ 2> /dev/null || exec bash $0 "$@" typeset -i tests=0 function try { let tests+=1; this="$1"; } function assert { [[ "$1" == "$2" ]] && { printf "."; return; } printf "\nFAIL: $this\n'$1' != '$2'\n"; exit 1 } function skip { printf "s"; } function zz { sleep 0.25; } function setup { rm -f $tmp/*; touch $tmp/file{1,2}; zz; } tmp=$(cd $(mktemp -dt entr_system_test.XXXXXXXXXX); pwd -P) # rebuild [ -f entr ] || { ./configure make clean make } # required utilities utils="hg vim" for util in $utils; do p=$(which $util 2> /dev/null) || { echo "ERROR: could not locate the '$util' utility" >&2 echo "System tests depend on the following: $utils" >&2 exit 1 } done # tests try "no arguments" ./entr 2> /dev/null || code=$? assert $code 1 try "reload and clear options with no utility to run" ./entr -r -c 2> /dev/null || code=$? assert $code 1 try "empty input" echo "" | ./entr echo 2> /dev/null || code=$? assert $code 1 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 "exec single shell utility and exit when a file is added to an implicit watch path" setup ls $tmp/file* | ./entr -dp sh -c 'echo ping' >$tmp/exec.out 2>$tmp/exec.err \ || true & bgpid=$! ; zz touch $tmp/newfile wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "ping" assert "$(cat $tmp/exec.err)" "entr: directory altered" try "exec single shell utility and exit when a file is added to a specific path" setup ls -d $tmp | ./entr -dp sh -c 'echo ping' >$tmp/exec.out 2>$tmp/exec.err \ || true & bgpid=$! ; zz touch $tmp/newfile wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "ping" assert "$(cat $tmp/exec.err)" "entr: directory altered" try "do nothing when a file not monitored is changed in directory watch mode" setup ls $tmp/file2 | ./entr -dp echo "changed" >$tmp/exec.out 2>$tmp/exec.err & bgpid=$! ; zz echo "123" > $tmp/file1 kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "" assert "$(cat $tmp/exec.err)" "" try "exec utility when a file is written by Vim in directory watch mode" setup ls $tmp/file* | ./entr -dp echo "changed" >$tmp/exec.out 2>$tmp/exec.err & bgpid=$! ; zz vim -e -s -u NONE -N \ -c ":r!date" \ -c ":wq" $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "changed" assert "$(cat $tmp/exec.err)" "" try "exec utility when a file is opened for write and then closed" setup echo "---" > $tmp/file1 ls $tmp/file* | ./entr -p echo "changed" > $tmp/exec.out & bgpid=$! ; zz : > $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" if [ $(uname | egrep 'Darwin|FreeBSD|DragonFly') ]; then skip "NOTE_TRUNCATE not supported" else assert "$(cat $tmp/exec.out)" "changed" fi try "exec single utility when an entire stash of files is reverted" setup cp /usr/include/*.h $tmp/ cd $tmp hg init hg add *.h hg commit -u "regression" -m "initial checkin" for f in `ls *.h`; do chmod 644 $f echo "" >> $f done cd - > /dev/null ; zz ls $tmp/*.h | ./entr -p echo "changed" > $tmp/exec.out & bgpid=$! ; zz cd $tmp hg revert *.h cd - > /dev/null ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "changed" try "exec utility when a file is written by Vim" setup ls $tmp/file* | ./entr -p echo "changed" > $tmp/exec.out & bgpid=$! ; zz vim -e -s -u NONE -N \ -c ":r!date" \ -c ":wq" $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "changed" try "exec shell utility when a file is written by Vim with 'backup'" setup ls $tmp/file* | ./entr -p echo "changed" > $tmp/exec.out & bgpid=$! ; zz vim -e -s -u NONE -N \ -c ":set backup" \ -c ":r!date" \ -c ":wq" $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "changed" try "exec shell utility when a file is written by Vim with 'nowritebackup'" setup ls $tmp/file* | ./entr -p echo "changed" > $tmp/exec.out & bgpid=$! ; zz vim -e -s -u NONE -N \ -c ":set nowritebackup" \ -c ":r!date" \ -c ":wq" $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "changed" 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=$! ; zz assert "$(cat $tmp/exec.out)" "started." echo 456 >> $tmp/file2 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "$(printf 'started.\nstarted.')" try "Ensure that all shell subprocesses are terminated in restart mode" setup cat <<-SCRIPT > $tmp/go.sh #!/bin/sh trap 'echo "caught signal"; exit' TERM echo "running"; sleep 10 SCRIPT chmod +x $tmp/go.sh ls $tmp/file2 | ./entr -r sh -c "$tmp/go.sh" 2> /dev/null > $tmp/exec.out & bgpid=$! ; zz kill -INT $bgpid ; zz assert "$(cat $tmp/exec.out)" "$(printf 'running\ncaught signal')" try "exit with no action when restart and dirwatch flags are combined" setup echo "started." > $tmp/file1 ls $tmp/file* | ./entr -rd tail -f $tmp/file1 2> /dev/null > $tmp/exec.out & bgpid=$! ; zz assert "$(cat $tmp/exec.out)" "started." touch $tmp/newfile kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "$(printf 'started.')" try "exec single shell utility when two files change simultaneously" setup ln $tmp/file1 $tmp/file3 ls $tmp/file* | ./entr -p sh -c 'echo ping' > $tmp/exec.out & bgpid=$! ; zz echo 456 >> $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "ping" try "exec shell utility on startup and after a file is changed" setup ls $tmp/file* | ./entr sh -c 'printf ping' > $tmp/exec.out & bgpid=$! ; zz echo 456 >> $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "pingping" try "exec a command using the first file to change" setup ls $tmp/file* | ./entr -p cat /_ > $tmp/exec.out & bgpid=$! ; zz echo 123 > $tmp/file2 ; zz echo 456 > $tmp/file1 echo 789 > $tmp/file2 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "$(printf '123\n456')" try "exec single shell utility using utility substitution" setup ls $tmp/file1 $tmp/file2 | ./entr -p file /_ > $tmp/exec.out & bgpid=$! ; zz echo 456 >> $tmp/file2; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "$tmp/file2: ASCII text" try "watch and exec a program that is overwritten" setup touch $tmp/script; chmod 755 $tmp/script echo $tmp/script | ./entr -p $tmp/script $tmp/file1 > $tmp/exec.out & bgpid=$! ; zz cat > $tmp/script <<-EOF #!/bin/sh echo vroom EOF zz ; kill -INT $bgpid wait $bgpid || assert "$?" "130" assert "$(cat $tmp/exec.out)" "vroom" try "exec an interactive utility when a file changes" setup ls $tmp/file* | ./entr -p sh -c 'tty | colrm 9' 2> /dev/null > $tmp/exec.out & bgpid=$! ; zz echo 456 >> $tmp/file2 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" if ! tty > /dev/null ; then skip "A TTY is not available" else assert "$(cat $tmp/exec.out | tr '/pts' '/tty')" "/dev/tty" fi # cleanup rm -r $tmp echo; echo "$tests tests PASSED" exit 0