aboutsummaryrefslogtreecommitdiff
path: root/iexec.c
diff options
context:
space:
mode:
Diffstat (limited to 'iexec.c')
-rw-r--r--iexec.c115
1 files changed, 76 insertions, 39 deletions
diff --git a/iexec.c b/iexec.c
index cd9dc97..562d49d 100644
--- a/iexec.c
+++ b/iexec.c
@@ -1,7 +1,7 @@
/*
* iexec.c: run a program and restart on file change
*
- * Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
+ * Copyright (c) 2017 - 2023, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -24,34 +24,54 @@
// This can also work on BSD if someone puts in the effort to support kqueue
#include <sys/inotify.h>
-static pid_t g_child;
-static bool g_restarting = false;
-static int g_inotify_fd, g_inotify_wd;
+static struct
+{
+ pid_t child; ///< Watched child or 0
+ bool exits; ///< Don't restart child when it exits
+ bool respawn; ///< Respawn child ASAP
+ bool killing; ///< Waiting for child to die
+ int inotify_fd, inotify_wd;
+}
+g;
+// Note that this program doesn't queue up file-based restarts
static void
-handle_file_change (const char *base)
+handle_inotify_event (const struct inotify_event *e, const char *base)
{
- char buf[4096]; ssize_t len; const struct inotify_event *e;
- while ((len = read (g_inotify_fd, buf, sizeof buf)) > 0)
- for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len)
- {
- e = (const struct inotify_event *) buf;
- if (e->wd != g_inotify_wd || strcmp (e->name, base))
- continue;
+ if (e->wd != g.inotify_wd || strcmp (e->name, base))
+ return;
+ if (g.child)
+ {
print_debug ("file changed, killing child");
- g_restarting = true;
- if (kill (g_child, SIGINT))
+ if (kill (g.child, SIGINT))
print_error ("kill: %s", strerror (errno));
+ g.killing = true;
+ }
+ else
+ {
+ print_debug ("file changed, respawning");
+ g.respawn = true;
}
}
static void
+handle_file_change (const char *base)
+{
+ char buf[4096];
+ ssize_t len = 0;
+ struct inotify_event *e = NULL;
+ while ((len = read (g.inotify_fd, buf, sizeof buf)) > 0)
+ for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len)
+ handle_inotify_event ((e = (struct inotify_event *) buf), base);
+}
+
+static void
spawn (char *argv[])
{
- if ((g_child = fork ()) == -1)
+ if ((g.child = fork ()) == -1)
exit_fatal ("fork: %s", strerror (errno));
- else if (g_child)
+ else if (g.child)
return;
// A linker can create spurious CLOSE_WRITEs, wait until it's executable
@@ -64,23 +84,22 @@ spawn (char *argv[])
}
static bool
-check_child_death (char *argv[])
+check_child_death (void)
{
- if (waitpid (g_child, NULL, WNOHANG) != g_child)
+ int status = 0;
+ if (waitpid (g.child, &status, WNOHANG) != g.child)
return true;
- if (!g_restarting)
+ g.child = 0;
+ if (!g.killing)
{
print_debug ("child died on its own, not respawning");
- return false;
- }
- else
- {
- print_debug ("child died on request, respawning");
- spawn (argv);
- g_restarting = false;
- return true;
+ return g.exits;
}
+
+ g.killing = false;
+ print_debug ("child died on request, respawning");
+ return g.respawn = true;
}
static void
@@ -93,8 +112,11 @@ sigchld_handler (int signum)
int
main (int argc, char *argv[])
{
+ const char *target = NULL;
static const struct opt opts[] =
{
+ { 'f', "file", "PATH", 0, "watch this path rather than the program" },
+ { 'e', "exits", NULL, 0, "allow the program to exit on its own" },
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
@@ -102,7 +124,7 @@ main (int argc, char *argv[])
};
struct opt_handler oh = opt_handler_make (argc, argv, opts,
- "PROGRAM [ARG...]", "Run a program and restart on file change.");
+ "PROGRAM [ARG...]", "Run a program and restart it when it changes.");
// We have to turn that off as it causes more trouble than what it's worth
cstr_set (&oh.opt_string, xstrdup_printf ("+%s", oh.opt_string));
@@ -111,6 +133,12 @@ main (int argc, char *argv[])
while ((c = opt_handler_get (&oh)) != -1)
switch (c)
{
+ case 'f':
+ target = optarg;
+ break;
+ case 'e':
+ g.exits = true;
+ break;
case 'd':
g_debug_mode = true;
break;
@@ -136,6 +164,9 @@ main (int argc, char *argv[])
argc -= optind;
argv += optind;
+ if (!target)
+ target = argv[0];
+
(void) signal (SIGPIPE, SIG_IGN);
struct sigaction sa = { .sa_handler = sigchld_handler };
sigemptyset (&sa.sa_mask);
@@ -148,27 +179,33 @@ main (int argc, char *argv[])
if (sigprocmask (SIG_BLOCK, &chld, &orig))
exit_fatal ("sigprocmask: %s", strerror (errno));
- char *path = xstrdup (argv[0]);
+ char *path = NULL;
+ char *dir = dirname ((path = xstrdup (target)));
- if ((g_inotify_fd = inotify_init1 (IN_NONBLOCK)) < 0)
+ if ((g.inotify_fd = inotify_init1 (IN_NONBLOCK)) < 0)
exit_fatal ("inotify_init1: %s", strerror (errno));
- if ((g_inotify_wd = inotify_add_watch (g_inotify_fd,
- dirname (path), IN_MOVED_TO | IN_CLOSE_WRITE)) < 0)
+ if ((g.inotify_wd = inotify_add_watch (g.inotify_fd,
+ dir, IN_MOVED_TO | IN_CLOSE_WRITE)) < 0)
exit_fatal ("inotify_add_watch: %s", strerror (errno));
free (path);
- char *base = basename ((path = xstrdup (argv[0])));
- spawn (argv);
-
+ char *base = basename ((path = xstrdup (target)));
+ g.respawn = true;
do
{
- fd_set r; FD_SET (g_inotify_fd, &r);
- (void) pselect (g_inotify_fd + 1, &r, NULL, NULL, NULL, &orig);
+ if (g.respawn)
+ {
+ spawn (argv);
+ g.respawn = false;
+ }
+
+ fd_set r; FD_SET (g.inotify_fd, &r);
+ (void) pselect (g.inotify_fd + 1, &r, NULL, NULL, NULL, &orig);
handle_file_change (base);
}
- while (check_child_death (argv));
+ while (check_child_death ());
free (path);
- close (g_inotify_fd);
+ xclose (g.inotify_fd);
return EXIT_SUCCESS;
}