From dfbda58c3373828d7770ab6c6cfa4bfb77fcc3df Mon Sep 17 00:00:00 2001 From: Daniel Foster Date: Tue, 27 May 2025 00:29:13 +1000 Subject: [PATCH 1/3] socket-activate: validate more argument combinations earlier in runtime Check user configuration errors and warnings (e.g. more than one socket passed with --inetd) earlier in runtime. There's no reason not to do this, and it means invalid configuration will be reported to the user earlier. Also let the user know that --fdname= has no effect with --inetd. --- src/socket-activate/socket-activate.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index ada289ded3..e62b762c95 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -102,6 +102,21 @@ static int open_sockets(int *ret_epoll_fd) { log_set_open_when_needed(false); } + if (count > 1 && !arg_accept && arg_inetd) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--inetd only supported with a single file descriptor, or with --accept."); + + if (arg_fdnames && !arg_inetd) { + size_t n_fdnames = strv_length(arg_fdnames); + + if (!arg_accept && n_fdnames != (size_t) count) + log_warning("The number of fd names is different from the number of fds: %zu vs %i", + n_fdnames, count); + + if (arg_accept && n_fdnames > 1) + log_warning("More than one fd name specified with --accept."); + } + epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd < 0) return log_error_errno(errno, "Failed to create epoll object: %m"); @@ -129,10 +144,6 @@ static int exec_process(char * const *argv, int start_fd, size_t n_fds) { assert(start_fd >= 0); assert(n_fds > 0); - if (arg_inetd && n_fds != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--inetd only supported for single file descriptors."); - FOREACH_STRING(var, "TERM", "COLORTERM", "NO_COLOR", "PATH", "USER", "HOME") { const char *n; @@ -181,8 +192,6 @@ static int exec_process(char * const *argv, int start_fd, size_t n_fds) { if (r < 0) return log_oom(); } - else if (len != n_fds) - log_warning("The number of fd names is different than number of fds: %zu vs %zu", len, n_fds); names = strv_join(arg_fdnames, ":"); if (!names) @@ -440,6 +449,9 @@ static int parse_argv(int argc, char *argv[]) { "Datagram sockets do not accept connections. " "The --datagram and --accept options may not be combined."); + if (arg_fdnames && arg_inetd) + log_warning("--fdname= has no effect with --inetd present."); + return 1 /* work to do */; } From 9e940164e9343aec156d0ae97c9358f468caae77 Mon Sep 17 00:00:00 2001 From: Daniel Foster Date: Tue, 27 May 2025 01:08:38 +1000 Subject: [PATCH 2/3] socket-activate: add --now option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a --now option that starts the program instantly, instead of waiting for a connection on the socket. This is useful, for instance, when developing: Say I have a Rust, socket-activated web service. I can test it with: systemd-socket-activate -l 8080 --fdname=http cargo run However this delays running the service (which potentially involves compilation) to when I try to connect to it, which wastes time. Since it delays compilation, I also don't see any warning or errors in the code until it gets a connection either. The name's now a bit of a misnomer, since starting the service immediately isn't really socket activation, but oh well ¯\_(ツ)_/⁻ --- man/systemd-socket-activate.xml | 12 +++++++- src/socket-activate/socket-activate.c | 42 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/man/systemd-socket-activate.xml b/man/systemd-socket-activate.xml index 4c29fa2c66..09f0d5cad6 100644 --- a/man/systemd-socket-activate.xml +++ b/man/systemd-socket-activate.xml @@ -72,7 +72,7 @@ Launch an instance of the service program for each connection and pass the connection - socket. + socket. May not be combined with . @@ -135,6 +135,16 @@ + + + + Start the service program instantly, instead of waiting for a connection on the + socket(s). May not be combined with . + + + + + diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index e62b762c95..a80080b9cd 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -30,6 +30,7 @@ static int arg_socket_type = SOCK_STREAM; static char **arg_setenv = NULL; static char **arg_fdnames = NULL; static bool arg_inetd = false; +static bool arg_now = false; static int add_epoll(int epoll_fd, int fd) { struct epoll_event ev = { @@ -117,9 +118,11 @@ static int open_sockets(int *ret_epoll_fd) { log_warning("More than one fd name specified with --accept."); } - epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if (epoll_fd < 0) - return log_error_errno(errno, "Failed to create epoll object: %m"); + if (!arg_now) { + epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (epoll_fd < 0) + return log_error_errno(errno, "Failed to create epoll object: %m"); + } for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { _cleanup_free_ char *name = NULL; @@ -127,9 +130,11 @@ static int open_sockets(int *ret_epoll_fd) { getsockname_pretty(fd, &name); log_info("Listening on %s as %i.", strna(name), fd); - r = add_epoll(epoll_fd, fd); - if (r < 0) - return r; + if (epoll_fd >= 0) { + r = add_epoll(epoll_fd, fd); + if (r < 0) + return r; + } } *ret_epoll_fd = TAKE_FD(epoll_fd); @@ -323,6 +328,7 @@ static int help(void) { " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" " --fdname=NAME[:NAME...] Specify names for file descriptors\n" " --inetd Enable inetd file descriptor passing protocol\n" + " --now Start instantly instead of waiting for connection\n" "\nNote: file descriptors from sd_listen_fds() will be passed through.\n" "\nSee the %s for details.\n", program_invocation_short_name, @@ -339,6 +345,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_FDNAME, ARG_SEQPACKET, ARG_INETD, + ARG_NOW, }; static const struct option options[] = { @@ -352,6 +359,7 @@ static int parse_argv(int argc, char *argv[]) { { "environment", required_argument, NULL, 'E' }, /* legacy alias */ { "fdname", required_argument, NULL, ARG_FDNAME }, { "inetd", no_argument, NULL, ARG_INETD }, + { "now", no_argument, NULL, ARG_NOW }, {} }; @@ -432,6 +440,10 @@ static int parse_argv(int argc, char *argv[]) { arg_inetd = true; break; + case ARG_NOW: + arg_now = true; + break; + case '?': return -EINVAL; @@ -449,6 +461,10 @@ static int parse_argv(int argc, char *argv[]) { "Datagram sockets do not accept connections. " "The --datagram and --accept options may not be combined."); + if (arg_accept && arg_now) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--now cannot be used in conjunction with --accept."); + if (arg_fdnames && arg_inetd) log_warning("--fdname= has no effect with --inetd present."); @@ -493,15 +509,17 @@ static int run(int argc, char **argv) { for (;;) { struct epoll_event event; - if (epoll_wait(epoll_fd, &event, 1, -1) < 0) { - if (errno == EINTR) - continue; + if (epoll_fd >= 0) { + if (epoll_wait(epoll_fd, &event, 1, -1) < 0) { + if (errno == EINTR) + continue; - return log_error_errno(errno, "epoll_wait() failed: %m"); + return log_error_errno(errno, "epoll_wait() failed: %m"); + } + + log_info("Communication attempt on fd %i.", event.data.fd); } - log_info("Communication attempt on fd %i.", event.data.fd); - if (!arg_accept) return exec_process(exec_argv, SD_LISTEN_FDS_START, (size_t) n); From 9e0d0c3fdfe5043d71a8d54f1e6fcc152fbc3e58 Mon Sep 17 00:00:00 2001 From: Daniel Foster Date: Tue, 27 May 2025 13:43:39 +1000 Subject: [PATCH 3/3] test: add simple tests for systemd-socket-activate tool --- mkosi/mkosi.sanitizers/mkosi.postinst | 1 + .../TEST-74-AUX-UTILS.socket-activate.sh | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100755 test/units/TEST-74-AUX-UTILS.socket-activate.sh diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 582a7cb99d..ee26021c32 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -77,6 +77,7 @@ wrap=( ps setfacl setpriv + socat sshd stat su diff --git a/test/units/TEST-74-AUX-UTILS.socket-activate.sh b/test/units/TEST-74-AUX-UTILS.socket-activate.sh new file mode 100755 index 0000000000..b714ae46c9 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.socket-activate.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +CAT_PID="$(systemd-notify --fork -- systemd-socket-activate -l 1234 --accept --inetd cat)" +assert_eq "$(echo -n hello | socat - 'TCP:localhost:1234')" hello +kill "$CAT_PID" + +# Check whether socat's ACCEPT-FD is available (introduced in v1.8) +systemd-socket-activate -l 1234 --now socat ACCEPT-FD:3 PIPE & +sleep 1 +jobs >/dev/null +if kill %% &>/dev/null; then + systemd-socket-activate -l 1234 --now socat ACCEPT-FD:3 PIPE & + SOCAT_PID="$!" + + # unfortunately we need to sleep since socket-activate only sends sd_notify when --accept is passed, + # so we can't rely on that to avoid a race. + sleep 1 + + assert_in socat "$(