mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-07 07:53:18 +02:00
package/execd: new local package
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
service log:prio:local1.err,tag:container \
|
||||
[2345] execd /run/containers/queue /run/containers/done -- Container job runner
|
||||
@@ -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))
|
||||
@@ -0,0 +1,2 @@
|
||||
d /run/containers/queue 0700 - -
|
||||
d /run/containers/done 0700 - -
|
||||
@@ -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.
|
||||
|
||||
@@ -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/
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user