package/execd: new local package

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-02-25 19:49:27 +01:00
parent ad0463e96c
commit 0b6f7a6b93
11 changed files with 392 additions and 2 deletions
+2 -1
View File
@@ -91,8 +91,8 @@ BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_IRQBALANCE=y
BR2_PACKAGE_KMOD_TOOLS=y
BR2_PACKAGE_PWGEN=y
@@ -122,6 +122,7 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_EXECD=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
+2 -1
View File
@@ -86,8 +86,8 @@ BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_IRQBALANCE=y
BR2_PACKAGE_KMOD_TOOLS=y
BR2_PACKAGE_PWGEN=y
@@ -125,6 +125,7 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_EXECD=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
+1
View File
@@ -1,4 +1,5 @@
source "$BR2_EXTERNAL_INFIX_PATH/package/confd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/execd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/statd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/conmon/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/factory/Config.in"
+7
View File
@@ -0,0 +1,7 @@
config BR2_PACKAGE_EXECD
bool "execd"
select BR2_PACKAGE_LIBUEV
select BR2_PACKAGE_LIBITE
help
Generic job queue executor with retry on route changes.
+2
View File
@@ -0,0 +1,2 @@
service log:prio:local1.err,tag:container \
[2345] execd /run/containers/queue /run/containers/done -- Container job runner
+32
View File
@@ -0,0 +1,32 @@
################################################################################
#
# execd
#
################################################################################
EXECD_VERSION = 1.0
EXECD_SITE_METHOD = local
EXECD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/execd
EXECD_LICENSE = ISC
EXECD_LICENSE_FILES = LICENSE
EXECD_REDISTRIBUTE = NO
EXECD_DEPENDENCIES = libuev libite
define EXECD_BUILD_CMDS
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
LDFLAGS="$(TARGET_LDFLAGS)"
endef
define EXECD_INSTALL_TARGET_CMDS
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
DESTDIR="$(TARGET_DIR)" install
endef
define EXECD_INSTALL_EXTRA
cp $(EXECD_PKGDIR)/execd.conf $(FINIT_D)/available/
ln -sf ../available/execd.conf $(FINIT_D)/enabled/execd.conf
cp $(EXECD_PKGDIR)/tmpfiles.conf $(TARGET_DIR)/etc/tmpfiles.d/execd.conf
endef
EXECD_TARGET_FINALIZE_HOOKS += EXECD_INSTALL_EXTRA
$(eval $(generic-package))
+2
View File
@@ -0,0 +1,2 @@
d /run/containers/queue 0700 - -
d /run/containers/done 0700 - -
+14
View File
@@ -0,0 +1,14 @@
Copyright (c) 2024 The KernelKit Authors
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.
+26
View File
@@ -0,0 +1,26 @@
CFLAGS += -Wall -Wextra -Werror -Wno-unused
CPPFLAGS += -D_GNU_SOURCE
LDLIBS += -luev -lite
TARGET = execd
SRC = execd.c
OBJ = $(SRC:.c=.o)
all: $(TARGET)
.c.o:
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
$(OBJ): $(SRC) $(HEADERS)
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
clean:
-rm -f $(TARGET) $(OBJ)
distclean: clean
-rm *~
install: $(TARGET)
install -D $(TARGET) $(DESTDIR)/sbin/
+9
View File
@@ -0,0 +1,9 @@
Execute jobs on route changes
=============================
This is a generic job queue executor for work that needs network access.
For example creating a Docker container by downloading an image from the
network -- if the download fails `execd` retries the job whenever there
is a route change.
+295
View File
@@ -0,0 +1,295 @@
/* SPDX-License-Identifier: ISC */
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SYSLOG_NAMES
#include <syslog.h>
#include <unistd.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <uev/uev.h>
#include <libite/lite.h>
#define err(fmt, args...) syslog(LOG_ERR, fmt ": %s", ##args, strerror(errno))
#define errx(fmt, args...) syslog(LOG_ERR, fmt, ##args)
#define warn(fmt, args...) syslog(LOG_WARNING, fmt, ": %s", ##args, strerror(errno))
#define warnx(fmt, args...) syslog(LOG_WARNING, fmt, ##args)
#define log(fmt, args...) syslog(LOG_NOTICE, fmt, ##args)
#define dbg(fmt, args...) syslog(LOG_DEBUG, fmt, ##args)
static int logmask = LOG_UPTO(LOG_NOTICE);
static char buffer[BUFSIZ];
static char *done;
static void run_job(char *path, char *file)
{
char cmd[strlen(path) + strlen(file) + 2];
int rc;
/*
* Unfortunately, on some systems (x86_64), execd reacts too
* quickly to route and inotify events. So we have this load
* bearing sleep here to guard against "text file busy" and
* "destination unreachable" errors.
*/
usleep(500000);
snprintf(cmd, sizeof(cmd), "%s/%s", path, file);
if (access(cmd, X_OK)) {
errx("skipping %s, not executable", cmd);
return;
}
dbg("running job %s", cmd);
if ((rc = systemf(cmd))) {
errx("failed %s: rc %d", cmd, rc);
return;
}
dbg("job %s in %s done", file, path);
if (done)
movefile(cmd, done);
else
erase(cmd);
}
static void run_queue(char *path)
{
struct dirent *d;
DIR *dir;
dir = opendir(path);
if (!dir) {
err("opendir");
return;
}
while ((d = readdir(dir))) {
dbg("Running queue %s entry %s", path, d->d_name);
if (d->d_name[0] == '.')
continue;
run_job(path, d->d_name);
}
closedir(dir);
}
static void signal_cb(uev_t *w, void *arg, int _)
{
dbg("Got signal, calling job queue");
run_queue(arg);
}
static void toggle_debug(uev_t *w, void *arg, int _)
{
int current = setlogmask(0);
if (current == logmask)
setlogmask(LOG_UPTO(LOG_DEBUG));
else
setlogmask(logmask);
}
static void inotify_cb(uev_t *w, void *arg, int _)
{
ssize_t bytes;
bytes = read(w->fd, buffer, sizeof(buffer));
if (bytes == -1) {
err("read");
return;
}
for (char *p = buffer; p < buffer + bytes;) {
struct inotify_event *event = (struct inotify_event *)p;
if (event->mask & (IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVED_TO)) {
dbg("Got inotify event %s 0x%04x", event->name, event->mask);
run_job(arg, event->name);
}
p += sizeof(struct inotify_event) + event->len;
}
}
static void netlink_cb(uev_t *w, void *arg, int _)
{
struct iovec iov = { buffer, sizeof(buffer) };
struct sockaddr_nl addr;
struct msghdr msg = {
&addr, sizeof(addr),
&iov, 1,
NULL, 0,
0
};
ssize_t bytes;
int sd = w->fd;
dbg("Got netlink event");
/* Empty netlink queue, we just want the event */
while ((bytes = recvmsg(sd, &msg, 0)) > 0)
dbg("Read %ld netlink bytes", bytes);
dbg("Calling run queue");
run_queue(arg);
}
int logmask_from_str(const char *str)
{
const CODE *code;
for (code = prioritynames; code->c_name; code++)
if (!strcmp(str, code->c_name))
return LOG_UPTO(code->c_val);
return -1;
}
static int usage(char *arg0, int rc)
{
printf("Usage:\n"
" %s [-dh] [-l LVL] JOBDIR\n"
"Options:\n"
" -d Log to stderr as well\n"
" -h This help text\n"
" -l LVL Set log level: none, err, warn, notice*, info, debug\n"
"\n"
"Runs jobs from JOBDIR, re-runs failing jobs on route changes or SIGHUP.\n"
"Use SIGUSR1 to toggle debug messages at runtime.\n", arg0);
return rc;
}
int main(int argc, char *argv[])
{
struct sockaddr_nl sa = { 0 };
uev_t inotify_watcher;
uev_t netlink_watcher;
uev_t sigusr1_watcher;
uev_t sighup_watcher;
int logopt = LOG_PID;
int wd, sd, fd, c;
char *jobdir;
uev_ctx_t ctx;
int rc = 0;
while ((c = getopt(argc, argv, "dhl:")) != EOF) {
switch (c) {
case 'd':
logopt |= LOG_PERROR;
break;
case 'h':
return usage(argv[0], 0);
case 'l':
logmask = logmask_from_str(optarg);
if (logmask < 0) {
fprintf(stderr, "Invalid loglevel '%s'\n\n", optarg);
return usage(argv[0], 1);
}
break;
default:
return usage(argv[0], 1);
}
}
if (optind >= argc)
return usage(argv[0], 1);
jobdir = argv[optind++];
if (optind < argc)
done = argv[optind];
if (access(jobdir, X_OK)) {
fprintf(stderr, "Cannot find job directory %s, errno %d: %s\n",
jobdir, errno, strerror(errno));
return 1;
}
/*
* We close stdin, while leaving stdout et stderr open so a user
* can redirect output from us to a logger process, or
* similar.
*/
close(STDIN_FILENO);
/* The logs of this program go to syslog w/ regular daemon facility */
openlog(NULL, logopt, LOG_DAEMON);
setlogmask(logmask);
fd = inotify_init1(IN_NONBLOCK);
if (fd == -1) {
err("inotify_init");
return 1;
}
wd = inotify_add_watch(fd, jobdir, IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVED_TO);
if (wd == -1) {
err("inotify_add_watch");
close(fd);
return 1;
}
/* Set up netlink socket for route monitoring */
sd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE);
if (sd == -1) {
err("socket");
close(fd);
return 1;
}
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
if (bind(sd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
err("bind");
rc = 1;
goto done;
}
uev_init(&ctx);
if (uev_signal_init(&ctx, &sighup_watcher, signal_cb, jobdir, SIGHUP) == -1) {
err("uev_signal_init (sighup)");
rc = 1;
goto done;
}
if (uev_signal_init(&ctx, &sigusr1_watcher, toggle_debug, NULL, SIGUSR1) == -1) {
err("uev_signal_init (sigusr1)");
rc = 1;
goto done;
}
if (uev_io_init(&ctx, &inotify_watcher, inotify_cb, jobdir, fd, UEV_READ) == -1) {
err("uev_io_init (inotify)");
rc = 1;
goto done;
}
if (uev_io_init(&ctx, &netlink_watcher, netlink_cb, jobdir, sd, UEV_READ) == -1) {
err("uev_io_init (netlink)");
rc = 1;
goto done;
}
run_queue(jobdir);
if (uev_run(&ctx, 0) == -1) {
err("uev_run");
rc = 1;
}
done:
close(fd);
close(sd);
return rc;
}