From 92bb0ca3991c64d84b5e9e34cfcd36bcaf21ba90 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 17 Sep 2023 10:50:18 +0200 Subject: [PATCH] runbg: new helper function inspired by the Infix project The runbg() function is a delayed execvp() that has double-forked to the background (to not create zombies). It is useful for running commands in the background for APIs that need to return a reply, e.g. a NETCONF RPC to reboot the system. Compared to the original in Infix, this version double forks to reparent itself to PID 1 (init), closes all open files, including stdio. Signed-off-by: Joachim Wiberg --- doc/API.md | 13 +++++++ src/Makefile.am | 6 ++-- src/lite.h | 1 + src/runbg.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ test/.gitignore | 1 + test/Makefile.am | 1 + test/runbg.c | 23 ++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/runbg.c create mode 100644 test/runbg.c diff --git a/doc/API.md b/doc/API.md index 67eda58..883ca07 100644 --- a/doc/API.md +++ b/doc/API.md @@ -249,6 +249,19 @@ Generic Functions `delete`, it is now called `opt`. The APIs are 100% compatible if the value `1` was used. +- `runbg(cmd, delay)` + + Run a command in the background, after delay microseconds, essentially + a background `execvp()` with a delay. The function employs double-fork + to prevent zombies and closes all open files, including stdio. + +```C + char *cmd[] = { "sh", "-c", "echo 'hej' >/tmp/foo", NULL}; + + runbg(cmd, 200000); /* Create /tmp/foo after 200 millisec */ + /* Execution continues here immediately */ +``` + - `strmatch(str, list)`, `strnmatch(str, list, len)` Find matching string in an array of strings. Returns index in array on diff --git a/src/Makefile.am b/src/Makefile.am index b2180d2..632c6c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,12 +11,12 @@ libite_la_SOURCES = chomp.c copyfile.c \ ifconfig.c lfile.c \ makepath.c procval.c progress.c \ pidfile.c pidfilefn.c popenf.c \ - reallocarray.c rsync.c strtrim.c \ + reallocarray.c rsync.c runbg.c \ strlcpy.c strlcat.c strtonum.c \ strdupa.h strndupa.h strnlen.h \ - strmatch.c systemf.c touchf.c \ + strmatch.c systemf.c strtrim.c \ telnet.c tempfile.c truncatef.c \ - yorn.c which.c \ + touchf.c yorn.c which.c \ lite.h strlite.h \ queue.h tree.h diff --git a/src/lite.h b/src/lite.h index e0285c5..207c36b 100644 --- a/src/lite.h +++ b/src/lite.h @@ -128,6 +128,7 @@ void *reallocarray(void *optr, size_t nmemb, size_t size); void progress (int percent, int max_width); void progress_simple(int percent); +int runbg (char *const cmd[], int delay); int systemf (const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); sdbuf_t*telnet_open (int addr, short port); diff --git a/src/runbg.c b/src/runbg.c new file mode 100644 index 0000000..2f4543a --- /dev/null +++ b/src/runbg.c @@ -0,0 +1,93 @@ +/* Run a command in the background: fork() + usleep() + execvp() + * + * Copyright (c) 2023 Joachim Wiberg + * + * Permission to use, copy, modify, and/or 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. + */ + +/** + * @file runbg.c + * @author Joachim Wiberg + * @date 2023 + * @copyright ISC License + */ + +#include +#include +#include +#include + +/** + * Run command in the background + * @param cmd NULL terminated list of command and optional arguments + * @param delay Microseconds after fork() to wait before calling cmd + * + * This function forks to run the given command @p cmd in the background. + * To ensure there are no lingering zombies the function actuall forks + * twice, and also calls setsid(), before it calls usleep() with the @p + * delay argument. The command @p cmd is handed over to execvp(). + * + * Since it runs in the background it is not possible to get the return + * code of the command. + * + * @returns on successful (first) fork(), this function returns POSIX + * OK(0), otherwise -1 and @a errno is set to indicate the error. + */ +int runbg(char *const cmd[], int delay) +{ + int pid = fork(); + int rc; + + if (!pid) { + int fd, maxfd; + + if (setsid() == -1) + _exit(errno); + + /* reparent to init */ + pid = fork(); + if (pid == -1) + _exit(errno); + if (pid > 0) + _exit(0); + + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd == -1) + maxfd = 8192; + for (fd = 0; fd < maxfd; fd++) + close(fd); + + usleep(delay); + _exit(execvp(cmd[0], cmd)); + } + + if (pid == -1) + return -1; + + if (waitpid(pid, &rc, 0) == -1) + return -1; + + if (WIFEXITED(rc)) { + errno = WEXITSTATUS(rc); + if (errno) + rc = -1; + else + rc = 0; + } else if (WIFSIGNALED(rc)) { + errno = EINTR; + rc = -1; + } else + rc = -1; + + return rc; +} diff --git a/test/.gitignore b/test/.gitignore index 5aff61f..b2ed0a7 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -15,6 +15,7 @@ /printhdr /progress /rsync +/runbg /str /strmatch /systemf diff --git a/test/Makefile.am b/test/Makefile.am index 039046b..bfa461c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -30,6 +30,7 @@ TESTS += pidfile TESTS += printhdr TESTS += progress TESTS += rsync +TESTS += runbg TESTS += str TESTS += strmatch TESTS += systemf diff --git a/test/runbg.c b/test/runbg.c new file mode 100644 index 0000000..6bee005 --- /dev/null +++ b/test/runbg.c @@ -0,0 +1,23 @@ +#include "check.h" + +#define file "/tmp/runbg.txt" + +int main(void) +{ + char *cmd[] = { + "sh", "-c", "echo 'hello world' > " file, NULL + }; + + erase(file); + test(runbg(cmd, 200000), "Calling runbg"); + + if (test(fexist(file), "Verifying %s does not yet exist", file)) + return 1; + + test(usleep(300000), "Waiting for runbg"); + + if (test(!fexist(file), "Verifying %s has been created", file)) + return 1; + + return 0; +}