Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package entr for openSUSE:Factory checked in at 2026-03-06 18:18:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/entr (Old) and /work/SRC/openSUSE:Factory/.entr.new.561 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "entr" Fri Mar 6 18:18:33 2026 rev:19 rq:1336809 version:5.8 Changes: -------- --- /work/SRC/openSUSE:Factory/entr/entr.changes 2025-10-04 18:53:44.628022747 +0200 +++ /work/SRC/openSUSE:Factory/.entr.new.561/entr.changes 2026-03-06 18:19:16.013833701 +0100 @@ -1,0 +2,12 @@ +Thu Mar 5 14:44:09 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 5.8 + * Add usage hint and '-h' flag to display option summary. + * Add return value checks for malloc(3) and pipe(2). + * Make signal number configurable with ENTR_RESTART_SIGNAL. + * Monitor symlinks on Linux. + * Avoid expanding filenames using realpath(3). + * Linux: return correct status if poll(2) is interrupted. + * Always accept directories as input, making '-d' flag optional. + +------------------------------------------------------------------- Old: ---- entr-5.7.tar.gz entr-5.7.tar.gz.sig New: ---- entr-5.8.tar.gz entr-5.8.tar.gz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ entr.spec ++++++ --- /var/tmp/diff_new_pack.Al8BtM/_old 2026-03-06 18:19:17.625900131 +0100 +++ /var/tmp/diff_new_pack.Al8BtM/_new 2026-03-06 18:19:17.637900625 +0100 @@ -1,7 +1,7 @@ # # spec file for package entr # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # Copyright (c) 2016 Daniel Lichtenberger # # All modifications and additions to the file contributed by third parties @@ -18,7 +18,7 @@ Name: entr -Version: 5.7 +Version: 5.8 Release: 0 Summary: A utility for running arbitrary commands when files change License: ISC ++++++ entr-5.7.tar.gz -> entr-5.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/Makefile.bsd new/entr-5.8/Makefile.bsd --- old/entr-5.7/Makefile.bsd 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/Makefile.bsd 2026-03-02 22:01:02.000000000 +0100 @@ -19,7 +19,7 @@ ls entr.1 | EV_TRACE=1 ./entr -zn wc -l entr.1 check: entr - ./system_test.sh + @./system_test.sh clean: rm -f *.o compat.c entr @@ -38,6 +38,6 @@ rm ${DESTDIR}${MANPREFIX}/man1/entr.1 format: - ${CLANG_FORMAT} -i *.c *.h + ${CLANG_FORMAT} -i *.c *.h missing/*.c missing/*.h .PHONY: all test check clean format distclean install uninstall diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/NEWS new/entr-5.8/NEWS --- old/entr-5.7/NEWS 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/NEWS 2026-03-02 22:01:02.000000000 +0100 @@ -1,5 +1,16 @@ = Release History +== 5.8: March 2, 2026 + + - MacOS: set open_max limit using MAXFILESPERPROC + - Add usage hint and '-h' flag to display option summary + - Add return value checks for malloc(3) and pipe(2) + - Make signal number configurable with ENTR_RESTART_SIGNAL + - Monitor symlinks on Linux and MacOS + - Avoid expanding filenames using realpath(3) + - Linux: return correct status if poll(2) is interrupted + - Always accept directories as input, making '-d' flag optional + == 5.7: February 6, 2025 - Update MANPREFIX for FreeBSD diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/README.md new/entr-5.8/README.md --- old/entr-5.7/README.md 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/README.md 2026-03-02 22:01:02.000000000 +0100 @@ -21,11 +21,11 @@ may cause `entr` to respond incorrectly. Setting the environment variable `ENTR_INOTIFY_WORKAROUND` enables `entr` to operate in these environments. -Linux Features --------------- +Platform Features +----------------- -Symlinks can be monitored for changes by setting the environment variable -`ENTR_INOTIFY_SYMLINK`. +On Mac OS and Linux, symlinks are not followed unless the environment variable +`ENTR_FOLLOW_SYMLINK` is set. Man Page Examples ----------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/data.h new/entr-5.8/data.h --- old/entr-5.7/data.h 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/data.h 2026-03-02 22:01:02.000000000 +0100 @@ -24,6 +24,7 @@ char fn[PATH_MAX]; int fd; int is_dir; + int is_symlink; int file_count; mode_t mode; ino_t ino; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/entr.1 new/entr-5.8/entr.1 --- old/entr-5.7/entr.1 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/entr.1 2026-03-02 22:01:02.000000000 +0100 @@ -13,7 +13,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd February 6, 2025 +.Dd March 2, 2026 .Dt ENTR 1 .Os .Sh NAME @@ -52,11 +52,9 @@ specified on the command line. Specify twice to erase the scrollback buffer. .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. -If specified twice, all new entries to a directory are recognized, -otherwise files with names beginning with +Track the parent directory of regular files provided as input. +If specified twice, all new entries to a directory are recognized, otherwise +files with names beginning with .Ql \&. are ignored. .It Fl n @@ -74,10 +72,6 @@ .Ar utility which terminates is not executed again until a file system or keyboard event is processed. -.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 @@ -144,6 +138,21 @@ By default .Pa $HOME/.entr/status.awk is evaluated. +.It Ev ENTR_RESTART_SIGNAL +Signal used to terminate the +.Ar utility . +Supported signal names are +.Dv HUP , +.Dv INT , +.Dv QUIT , +.Dv TERM , +.Dv USR1 +and +.Dv USR2 . +The default is +.Dv SIGTERM . +.It Ev EV_TRACE +Print file system event messages. .It Ev PAGER Set to .Pa /bin/cat @@ -156,8 +165,6 @@ flag. The default is .Pa /bin/sh . -.It Ev EV_TRACE -Print file system event messages. .El .Sh EXIT STATUS If the @@ -178,7 +185,7 @@ .It 1 No regular files were provided as input or an error occurred .It 2 -A file was added to a directory and the directory watch option was specified +Files were added or removed from a directory .El .Sh EXAMPLES Rebuild a project if source files change, limiting output to the first 20 lines: @@ -195,7 +202,7 @@ .Pp Rebuild project if a source file is modified or added to the src/ directory: .Pp -.Dl $ while sleep 0.1; do ls src/*.rb | entr -d make; done +.Dl $ while sleep 0.1; do ls -d src src/*.rb | entr make; done .Pp Auto-reload a web server, or terminate if the server exits .Pp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/entr.c new/entr-5.8/entr.c --- old/entr-5.7/entr.c 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/entr.c 2026-03-02 22:01:02.000000000 +0100 @@ -17,6 +17,7 @@ #include <sys/param.h> #include <sys/resource.h> #include <sys/stat.h> +#include <sys/sysctl.h> #include <sys/wait.h> #include <sys/event.h> @@ -29,6 +30,7 @@ #include <limits.h> #include <paths.h> #include <signal.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -62,6 +64,7 @@ int child_pid; int child_status; int terminating; +int restart_signal; int aggressive_opt; int clear_opt; @@ -79,10 +82,15 @@ static char *shell, *shell_base; static char *argv0, *argv0_base; +/* function pointers */ + +int (*xstat)(const char *path, struct stat *sb); + /* forwards */ -static void usage(); +static void usage(bool); static void terminate_utility(); +static void set_restart_signal(); static void handle_exit(int sig); static void proc_exit(int sig); static void print_child_status(int status); @@ -100,7 +108,6 @@ */ int main(int argc, char *argv[]) { - struct rlimit rl; int kq; struct sigaction act; int ttyfd; @@ -112,7 +119,7 @@ /* call usage() if no command is supplied */ if (argc < 2) - usage(); + usage(false); argv_index = set_options(argv); sigemptyset(&act.sa_mask); @@ -127,25 +134,41 @@ if (sigaction(SIGHUP, &act, NULL) != 0) err(1, "Failed to set SIGHUP handler"); + set_restart_signal(); + /* notification used to combine the one-shot and restart options */ act.sa_flags = 0; act.sa_handler = proc_exit; if (sigaction(SIGCHLD, &act, NULL) != 0) err(1, "Failed to set SIGCHLD handler"); + /* monitor symlinks if possible */ + xstat = stat; +#if defined(O_PATH) || defined(O_SYMLINK) + if (getenv("ENTR_FOLLOW_SYMLINK") == NULL) + xstat = lstat; +#endif + #if defined(_LINUX_PORT) /* attempt to read inotify limits */ open_max = (unsigned) fs_sysctl(INOTIFY_MAX_USER_WATCHES); if (open_max == 0) open_max = 65536; #elif defined(_MACOS_PORT) + struct rlimit rl; + int mib[2] = { CTL_KERN, KERN_MAXFILESPERPROC }; + size_t namelen = sizeof(open_max); + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) err(1, "getrlimit"); - open_max = min(OPEN_MAX, rl.rlim_max); + if (sysctl(mib, 2, &open_max, &namelen, NULL, 0) == -1) + open_max = OPEN_MAX; rl.rlim_cur = open_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) err(1, "setrlimit cannot set rlim_cur to %u", open_max); #else /* BSD */ + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) err(1, "getrlimit"); open_max = (unsigned) rl.rlim_max; @@ -161,12 +184,9 @@ setenv("PAGER", "/bin/cat", 0); /* ensure a shell is available to use */ - setenv("SHELL", "/bin/sh", 0); - - shell = getenv("SHELL"); + if ((shell = getenv("SHELL")) == NULL) + shell = "/bin/sh"; shell_base = strdup(shell); - if (shell_base == NULL) - err(1, "cannot duplicate string"); shell_base = basename(shell_base); /* initialize status filter */ @@ -184,13 +204,15 @@ /* sequential scan may depend on a 0 at the end */ files = calloc(open_max + 1, sizeof(WatchFile *)); + if (files == NULL) + err(1, "calloc"); if ((kq = kqueue()) == -1) err(1, "cannot create kqueue"); /* expect file list from a pipe */ if (isatty(fileno(stdin))) - usage(); + usage(false); /* read input and populate watch list, skipping non-regular files */ n_files = process_input(stdin, files, open_max); @@ -231,9 +253,28 @@ /* Utility functions */ void -usage() { +usage(bool summary) { fprintf(stderr, "release: %s\n", RELEASE); fprintf(stderr, "usage: entr [-acdnprsxz] utility [argument [/_] ...] < filenames\n"); + if (!summary) { + fprintf(stderr, "hint: use -h to display option summary\n"); + goto end; + } + + printf("summary:\n" + " -a Do not consolidate events\n" + " -c Clear screen before execution\n" + " -d Track files added or removed from directories\n" + " -n Non-interactive mode\n" + " -p Wait for first event\n" + " -r Run as a background process, use signal to restart\n" + " -s Evaluate using a shell\n" + " -x Format exit status\n" + " -z Exit after the utility completes\n"); + printf("docs:\n" + " man entr\n"); + +end: exit(1); } @@ -244,7 +285,7 @@ terminating = 1; if (child_pid > 0) { - killpg(child_pid, SIGTERM); + killpg(child_pid, restart_signal); waitpid(child_pid, &status, 0); child_pid = 0; } @@ -252,6 +293,30 @@ terminating = 0; } +void +set_restart_signal() { + const char *sig; + const int signum[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2 }; + const char *signame[] = { "HUP", "INT", "QUIT", "TERM", "USR1", "USR2" }; + int i; + + if ((sig = getenv("ENTR_RESTART_SIGNAL")) == NULL) { + restart_signal = SIGTERM; + return; + } + + if (strncmp(sig, "SIG", 3) == 0) + sig += 3; + + for (i = 0; signum[i] < 6; i++) { + if (strcmp(sig, signame[i]) == 0) + restart_signal = signum[i]; + } + + if (restart_signal == 0) + errx(1, "unrecognized signal: %s <> (HUP, INT, QUIT, TERM, USR1, USR2)", sig); +} + /* Callbacks */ void @@ -331,7 +396,7 @@ int process_input(FILE *file, WatchFile *files[], int max_files) { char buf[PATH_MAX]; - char *p, *path; + char *p, *path, *parent_path; int n_files = 0; struct stat sb; int i, matches; @@ -341,39 +406,52 @@ *p = '\0'; if (buf[0] == '\0') continue; + path = &buf[0]; - if (stat(buf, &sb) == -1) { - warnx("unable to stat '%s'", buf); + if (xstat(path, &sb) == -1) { + warnx("unable to stat '%s'", path); continue; } - if (S_ISREG(sb.st_mode) != 0) { + + if ((S_ISREG(sb.st_mode) | S_ISLNK(sb.st_mode)) != 0) { files[n_files] = malloc(sizeof(WatchFile)); - strlcpy(files[n_files]->fn, buf, MEMBER_SIZE(WatchFile, fn)); + if (files[n_files] == NULL) + err(1, "malloc"); + strlcpy(files[n_files]->fn, path, MEMBER_SIZE(WatchFile, fn)); files[n_files]->is_dir = 0; + files[n_files]->is_symlink = (S_ISLNK(sb.st_mode) != 0) ? 1 : 0; files[n_files]->file_count = 0; files[n_files]->mode = sb.st_mode; files[n_files]->ino = sb.st_ino; n_files++; - } - /* also watch the directory if it's not already in the list */ - if (dirwatch_opt > 0) { - 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; i < n_files; i++) - if (strcmp(files[i]->fn, 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 = list_dir(path); - files[n_files]->mode = sb.st_mode; - files[n_files]->ino = sb.st_ino; - n_files++; + + /* also watch the directory if it's not already in the list */ + if (dirwatch_opt > 0) { + if ((parent_path = dirname(path)) == 0) + err(1, "dirname '%s' failed", path); + for (matches = 0, i = 0; i < n_files; i++) { + if ((files[i]->is_dir == 1) && (strcmp(files[i]->fn, parent_path) == 0)) + matches++; + } + if (matches == 0) { + if (stat(parent_path, &sb) == -1) + warnx("unable to stat '%s'", parent_path); + path = parent_path; + } } } + if (S_ISDIR(sb.st_mode) != 0) { + files[n_files] = malloc(sizeof(WatchFile)); + if (files[n_files] == NULL) + err(1, "malloc"); + strlcpy(files[n_files]->fn, path, MEMBER_SIZE(WatchFile, fn)); + files[n_files]->is_dir = 1; + files[n_files]->is_symlink = 0; + files[n_files]->file_count = list_dir(path); + files[n_files]->mode = sb.st_mode; + files[n_files]->ino = sb.st_ino; + n_files++; + } if (n_files + 1 > max_files) return -1; } @@ -404,6 +482,9 @@ int ch; int argc; + if (argv[1] && strcmp(argv[1], "-h") == 0) + usage(true); + /* read arguments until we reach a command */ for (argc = 1; argv[argc] != 0 && argv[argc][0] == '-'; argc++) ; @@ -437,11 +518,11 @@ oneshot_opt = 1; break; default: - usage(); + usage(false); } } if (argv[optind] == 0) - usage(); + usage(false); if (status_filter_opt && restart_opt) errx(1, "-r and -x may not be combined"); @@ -464,35 +545,41 @@ char **new_argv; char *p, *arg_buf; int argc; + size_t remaining; if (restart_opt == 1) terminate_utility(); + arg_buf = malloc(ARG_MAX); + if (shell_opt == 1) { /* run argv[1] with a shell using the leading edge as $0 */ argc = 4; - arg_buf = malloc(ARG_MAX); new_argv = calloc(argc + 1, sizeof(char *)); - realpath(leading_edge->fn, arg_buf); + if (new_argv == NULL) + err(1, "calloc"); new_argv[0] = shell; new_argv[1] = "-c"; new_argv[2] = argv[0]; - new_argv[3] = arg_buf; + new_argv[3] = leading_edge->fn; } else { /* clone argv on each invocation to make the implementation of more * complex substitution rules possible and easy */ for (argc = 0; argv[argc]; argc++) ; - arg_buf = malloc(ARG_MAX); new_argv = calloc(argc + 1, sizeof(char *)); + if (new_argv == NULL) + err(1, "calloc"); + new_argv[0] = "/bin/false"; for (m = 0, i = 0, p = arg_buf; i < argc; i++) { + remaining = ARG_MAX - (p - arg_buf); new_argv[i] = p; if ((m < 1) && (strcmp(argv[i], "/_")) == 0) { - p += strlen(realpath(leading_edge->fn, p)); + p += strlcpy(p, leading_edge->fn, remaining); m++; } else - p += strlcpy(p, argv[i], ARG_MAX - (p - arg_buf)); + p += strlcpy(p, argv[i], remaining); p++; } } @@ -553,8 +640,10 @@ /* wait up to 1 second for file to become available */ for (;;) { -#ifdef O_EVTONLY - file->fd = open(file->fn, O_RDONLY | O_CLOEXEC | O_EVTONLY); +#if defined(O_EVTONLY) + file->fd = open(file->fn, O_RDONLY | O_CLOEXEC | O_EVTONLY | O_SYMLINK); +#elif defined(O_PATH) + file->fd = open(file->fn, O_RDONLY | O_CLOEXEC | O_PATH | O_NOFOLLOW); #else file->fd = open(file->fn, O_RDONLY | O_CLOEXEC); #endif @@ -656,10 +745,6 @@ if ((nev == -1) && (errno != EINTR)) warn("kevent failed"); - /* escape for test runner */ - if ((nev == -2) && (collate_only == 0)) - return; - for (i = 0; i < nev; i++) { if (!noninteractive_opt && evList[i].filter == EVFILT_READ) { if (read(STDIN_FILENO, &c, 1) < 1) { @@ -722,7 +807,7 @@ } if (evList[i].fflags & NOTE_ATTRIB && S_ISREG(file->mode) != 0 - && stat(file->fn, &sb) == 0) { + && xstat(file->fn, &sb) == 0) { if (file->mode != sb.st_mode) { do_exec = 1; file->mode = sb.st_mode; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/missing/kqueue_inotify.c new/entr-5.8/missing/kqueue_inotify.c --- old/entr-5.7/missing/kqueue_inotify.c 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/missing/kqueue_inotify.c 2026-03-02 22:01:02.000000000 +0100 @@ -13,8 +13,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/inotify.h> #include <sys/event.h> +#include <sys/inotify.h> #include <sys/types.h> #include <err.h> @@ -38,7 +38,7 @@ /* forwards */ -static WatchFile * file_by_descriptor(int fd); +static WatchFile *file_by_descriptor(int fd); /* utility functions */ @@ -46,7 +46,7 @@ file_by_descriptor(int wd) { int i; - for (i=0; files[i] != NULL; i++) { + for (i = 0; files[i] != NULL; i++) { if (files[i]->fd == wd) return files[i]; } @@ -59,15 +59,14 @@ char line[8]; int value = 0; - switch(name) { + switch (name) { case INOTIFY_MAX_USER_WATCHES: file = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); if (file == NULL || fgets(line, sizeof(line), file) == NULL) { /* failed to read max_user_watches; sometimes inaccessible on Android */ value = 0; - } - else + } else value = atoi(line); if (file) @@ -79,9 +78,10 @@ /* interface */ -#define EVENT_SIZE (sizeof (struct inotify_event)) +#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_MOVE_SELF|IN_MOVE|IN_ATTRIB|IN_CREATE|IN_DELETE +#define IN_ALL \ + IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB | IN_CREATE | IN_DELETE /* * inotify and kqueue ids both have the type `int` @@ -105,8 +105,8 @@ * 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) { +kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, + const struct timespec *timeout) { int n; int wd; WatchFile *file; @@ -118,18 +118,21 @@ const struct kevent *kev; int nfds; + int timeout_ms = -1; int ignored = 0; struct pollfd pfd[2]; pfd[0].fd = kq; pfd[0].events = POLLIN; + pfd[0].revents = 0; pfd[1].fd = STDIN_FILENO; pfd[1].events = POLLIN; + pfd[1].revents = 0; if (nchanges > 0) { - for (n=0; n<nchanges; n++) { - kev = changelist + (sizeof(struct kevent)*n); - file = (WatchFile *)kev->udata; + for (n = 0; n < nchanges; n++) { + kev = changelist + (sizeof(struct kevent) * n); + file = (WatchFile *) kev->udata; if (kev->filter == EVFILT_READ) { if (kev->flags & EV_ADD) @@ -144,20 +147,18 @@ if (kev->flags & EV_DELETE) { inotify_rm_watch(kq /* ifd */, kev->ident); file->fd = -1; /* invalidate */ - } - else if (kev->flags & EV_ADD) { + } else if (kev->flags & EV_ADD) { if (getenv("ENTR_INOTIFY_WORKAROUND")) - wd = inotify_add_watch(kq, file->fn, IN_ALL|IN_MODIFY); - else if (getenv("ENTR_INOTIFY_SYMLINK")) - wd = inotify_add_watch(kq, file->fn, IN_ALL|IN_DONT_FOLLOW); + wd = inotify_add_watch(kq, file->fn, IN_ALL | IN_MODIFY); + else if (file->is_symlink) + wd = inotify_add_watch(kq, file->fn, IN_ALL | IN_DONT_FOLLOW); else wd = inotify_add_watch(kq, file->fn, IN_ALL); if (wd < 0) return -1; close(file->fd); file->fd = wd; /* replace with watch descriptor */ - } - else + } else ignored++; } return nchanges - ignored; @@ -167,14 +168,15 @@ nfds = 2; /* inotify and stdin */ else nfds = 1; /* inotify */ - if (timeout == NULL) - poll(pfd, nfds, -1); - else - poll(pfd, nfds, timeout->tv_nsec/1000000); + + if (timeout) + timeout_ms = timeout->tv_nsec / 1000000; + if (poll(pfd, nfds, timeout_ms) == -1) + return -1; n = 0; do { - if (pfd[0].revents & (POLLERR|POLLNVAL)) + if (pfd[0].revents & (POLLERR | POLLNVAL)) errx(1, "bad fd %d", pfd[0].fd); if (pfd[0].revents & POLLIN) { pos = 0; @@ -192,20 +194,30 @@ /* 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_CREATE) fflags |= NOTE_WRITE; - if (iev->mask & IN_DELETE) fflags |= NOTE_WRITE; - if (iev->mask & IN_MOVE_SELF) fflags |= NOTE_RENAME; - if (iev->mask & IN_MOVED_TO) fflags |= NOTE_RENAME; - if (iev->mask & IN_MOVED_FROM) fflags |= NOTE_RENAME; - if (iev->mask & IN_ATTRIB) fflags |= NOTE_ATTRIB; + if (iev->mask & IN_DELETE_SELF) + fflags |= NOTE_DELETE; + if (iev->mask & IN_CLOSE_WRITE) + fflags |= NOTE_WRITE; + if (iev->mask & IN_CREATE) + fflags |= NOTE_WRITE; + if (iev->mask & IN_DELETE) + fflags |= NOTE_WRITE; + if (iev->mask & IN_MOVE_SELF) + fflags |= NOTE_RENAME; + if (iev->mask & IN_MOVED_TO) + fflags |= NOTE_RENAME; + if (iev->mask & IN_MOVED_FROM) + fflags |= NOTE_RENAME; + if (iev->mask & IN_ATTRIB) + fflags |= NOTE_ATTRIB; if (getenv("ENTR_INOTIFY_WORKAROUND")) - if (iev->mask & IN_MODIFY) fflags |= NOTE_WRITE; - if (fflags == 0) continue; + if (iev->mask & IN_MODIFY) + fflags |= NOTE_WRITE; + 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)) + if ((n > 0) && (eventlist[n - 1].ident == iev->wd)) fflags |= eventlist[--n].fflags; eventlist[n].ident = iev->wd; @@ -219,9 +231,9 @@ } } if (read_stdin == 1) { - if (pfd[1].revents & (POLLERR|POLLNVAL)) + if (pfd[1].revents & (POLLERR | POLLNVAL)) errx(1, "bad fd %d", pfd[1].fd); - else if (pfd[1].revents & (POLLHUP|POLLIN)) { + else if (pfd[1].revents & (POLLHUP | POLLIN)) { fflags = 0; eventlist[n].ident = pfd[1].fd; eventlist[n].filter = EVFILT_READ; @@ -233,8 +245,7 @@ break; } } - } - while ((poll(pfd, nfds, 50) > 0)); + } while ((poll(pfd, nfds, 50) > 0)); return n; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/missing/strlcpy.c new/entr-5.8/missing/strlcpy.c --- old/entr-5.7/missing/strlcpy.c 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/missing/strlcpy.c 2026-03-02 22:01:02.000000000 +0100 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/types.h> #include <string.h> +#include <sys/types.h> /* * Copy string src to buffer dst of size dsize. At most dsize-1 @@ -25,8 +25,7 @@ * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t -strlcpy(char *dst, const char *src, size_t dsize) -{ +strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; @@ -41,10 +40,10 @@ /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) - *dst = '\0'; /* NUL-terminate dst */ + *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } - return(src - osrc - 1); /* count does not include NUL */ + return (src - osrc - 1); /* count does not include NUL */ } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/missing/sys/sysctl.h new/entr-5.8/missing/sys/sysctl.h --- old/entr-5.7/missing/sys/sysctl.h 1970-01-01 01:00:00.000000000 +0100 +++ new/entr-5.8/missing/sys/sysctl.h 2026-03-02 22:01:02.000000000 +0100 @@ -0,0 +1 @@ +/* empty unit: sysctl(2) not called on Linux */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/status.c new/entr-5.8/status.c --- old/entr-5.7/status.c 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/status.c 2026-03-02 22:01:02.000000000 +0100 @@ -43,6 +43,8 @@ awk_script = getenv("ENTR_STATUS_SCRIPT"); if ((!awk_script) || (strlen(awk_script) == 0)) { pw = getpwuid(getuid()); + if (pw == NULL) + errx(1, "getpwuid"); asprintf(&awk_script, "%s/.entr/status.awk", pw->pw_dir); } @@ -66,7 +68,10 @@ if (safe == 2) argv[5] = NULL; - pipe(status_stdin_pipe); + int rc = pipe(status_stdin_pipe); + if (rc == -1) + err(1, "pipe"); + status_pid = fork(); if (status_pid == -1) err(1, "fork"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/entr-5.7/system_test.sh new/entr-5.8/system_test.sh --- old/entr-5.7/system_test.sh 2025-02-06 20:36:24.000000000 +0100 +++ new/entr-5.8/system_test.sh 2026-03-02 22:01:02.000000000 +0100 @@ -38,7 +38,7 @@ utils="file pgrep git vim tmux" for util in $utils; do - p=$(command -pv $util) || { + p=$(command -v $util) || { echo "ERROR: could not locate the '$util' utility" >&2 echo "System tests depend on the following: $utils" >&2 exit 1 @@ -62,11 +62,21 @@ # fast tests try "no arguments" - entr 2> /dev/null || code=$? - assert $code 1 + entr >$tmp/exec.out 2>$tmp/exec.err + assert $? 1 + grep -q "usage:" $tmp/exec.err + assert $? 0 + +try "display option summary" + entr_tty -h >$tmp/exec.out 2>$tmp/exec.err + assert $? 1 + grep -q "usage:" $tmp/exec.err + assert $? 0 + grep -q "summary:" $tmp/exec.out + assert $? 0 try "no input" - entr echo "vroom" 2> /dev/null || code=$? + echo | entr echo "vroom" 2> /dev/null || code=$? assert $code 1 try "reload and clear options with no utility to run" @@ -83,6 +93,14 @@ rmdir $tmp/dir1 assert $code 1 +try "invalid signal number set" + ls $tmp | ENTR_RESTART_SIGNAL="" entr echo 2> /dev/null || code=$? + assert $code 1 + ls $tmp | ENTR_RESTART_SIGNAL="0" entr echo 2> /dev/null || code=$? + assert $code 1 + ls $tmp | ENTR_RESTART_SIGNAL="KILL" entr echo 2> /dev/null || code=$? + assert $code 1 + # status message tests try "install default status script" @@ -286,7 +304,7 @@ 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 \ + ls -d $tmp | entr -p sh -c 'echo ping' >$tmp/exec.out 2>$tmp/exec.err \ || true & bgpid=$! ; zz touch $tmp/newfile @@ -294,6 +312,34 @@ assert "$(cat $tmp/exec.out)" "ping" assert "$(cat $tmp/exec.err)" "entr: directory altered" +try "exec utility when a symlink is changed" + setup + ln -sf $tmp/file1 $tmp/link + echo $tmp/link | entr -p echo "changed" > $tmp/exec.out & + bgpid=$! ; zz + ln -sf $tmp/file2 $tmp/link ; zz + kill -INT $bgpid + wait $bgpid; assert "$?" "0" + if [ $(uname | grep -E 'Darwin|Linux') ]; then + assert "$(cat $tmp/exec.out)" "changed" + else + skip "O_SYMLINK not supported" + fi + +try "exec utility when a broken symlink is changed" + if [ $(uname | grep -E 'Darwin|Linux') ]; then + setup + ln -sf $tmp/file9 $tmp/link + echo $tmp/link | entr -p echo "changed" > $tmp/exec.out & + bgpid=$! ; zz + ln -sf $tmp/file1 $tmp/link ; zz + kill -INT $bgpid + wait $bgpid; assert "$?" "0" + assert "$(cat $tmp/exec.out)" "changed" + else + skip "O_SYMLINK not supported" + fi + 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 & @@ -403,15 +449,15 @@ wait $bgpid; assert "$?" "0" assert "$(cat $tmp/exec.out)" "$(printf 'started.\nstarted.')" -try "ensure that all shell subprocesses are terminated in restart mode" +try "ensure that all shell subprocesses are terminated with custom signal in restart mode" setup cat <<-SCRIPT > $tmp/go.sh #!/bin/sh - trap 'echo "caught signal"; exit' TERM + trap 'echo "caught signal"; exit' INT 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 & + ls $tmp/file2 | ENTR_RESTART_SIGNAL=INT 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')" @@ -521,7 +567,7 @@ echo 456 >> $tmp/file2 ; zz kill -INT $bgpid wait $bgpid; assert "$?" "0" - assert "$(cat $tmp/exec.out | tr '/pts' '/tty')" "/dev/tty" + assert "$(awk '/dev.(tty|pts)/ { print "/dev/tty" }' $tmp/exec.out)" "/dev/tty" fi try "exec a command using shell option"
