diff --git a/Makefile-man.am b/Makefile-man.am index 14c2b7d57e..1ff85d7d2c 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -355,6 +355,7 @@ MANPAGES_ALIAS += \ man/sd_journal_set_data_threshold.3 \ man/sd_journal_test_cursor.3 \ man/sd_journal_wait.3 \ + man/sd_listen_fds_with_names.3 \ man/sd_machine_get_ifindices.3 \ man/sd_notifyf.3 \ man/sd_pid_notify.3 \ @@ -643,6 +644,7 @@ man/sd_journal_sendv.3: man/sd_journal_print.3 man/sd_journal_set_data_threshold.3: man/sd_journal_get_data.3 man/sd_journal_test_cursor.3: man/sd_journal_get_cursor.3 man/sd_journal_wait.3: man/sd_journal_get_fd.3 +man/sd_listen_fds_with_names.3: man/sd_listen_fds.3 man/sd_machine_get_ifindices.3: man/sd_machine_get_class.3 man/sd_notifyf.3: man/sd_notify.3 man/sd_pid_notify.3: man/sd_notify.3 @@ -1319,6 +1321,9 @@ man/sd_journal_test_cursor.html: man/sd_journal_get_cursor.html man/sd_journal_wait.html: man/sd_journal_get_fd.html $(html-alias) +man/sd_listen_fds_with_names.html: man/sd_listen_fds.html + $(html-alias) + man/sd_machine_get_ifindices.html: man/sd_machine_get_class.html $(html-alias) diff --git a/man/sd_listen_fds.xml b/man/sd_listen_fds.xml index 9b9705eb2e..ea55671e4f 100644 --- a/man/sd_listen_fds.xml +++ b/man/sd_listen_fds.xml @@ -1,4 +1,4 @@ - + @@ -45,6 +45,7 @@ sd_listen_fds + sd_listen_fds_with_names SD_LISTEN_FDS_START Check for file descriptors passed by the system manager @@ -59,23 +60,26 @@ int sd_listen_fds int unset_environment + + + int sd_listen_fds_with_names + int unset_environment + char*** names + Description - sd_listen_fds() shall be called by a - daemon to check for file descriptors passed by the init system as - part of the socket-based activation logic. - - If the unset_environment parameter is - non-zero, sd_listen_fds() will unset the - $LISTEN_FDS and $LISTEN_PID - environment variables before returning (regardless of whether the - function call itself succeeded or not). Further calls to - sd_listen_fds() will then fail, but the - variables are no longer inherited by child processes. + sd_listen_fds() may be invoked by a + daemon to check for file descriptors passed by the service manager as + part of the socket-based activation logic. It returns the number + of received file descriptors. If no file descriptors have been + received zero is returned. The first file descriptor may be found + at file descriptor number 3 + (i.e. SD_LISTEN_FDS_START), the remaining + descriptors follow at 4, 5, 6, ..., if any. If a daemon receives more than one file descriptor, they will be passed in the same order as configured in the systemd @@ -108,12 +112,86 @@ FDSTORE=1 messages, these file descriptors are passed last, in arbitrary order, and with duplicates removed. + + If the unset_environment parameter is + non-zero, sd_listen_fds() will unset the + $LISTEN_FDS, $LISTEN_PID and + $LISTEN_FDNAMES environment variables before + returning (regardless of whether the function call itself + succeeded or not). Further calls to + sd_listen_fds() will then return zero, but the + variables are no longer inherited by child processes. + + sd_listen_fds_with_names() is like + sd_listen_fds() but optionally also returns + an array of strings with identification names for the passed file + descriptors, if that is available, and the + names parameter is non-NULL. This + information is read from the $LISTEN_FDNAMES + variable, which may contain a colon-separated list of names. For + socket-activated services, these names may be configured with the + FileDescriptorName= setting in socket unit + files, see + systemd.socket5 + for details. For file descriptors pushed into the file descriptor + store (see above) the name is set via the + FDNAME= field transmitted via + sd_pid_notify_with_fds(). The primary usecase + for these names are services which accept a variety of file + descriptors which are not recognizable with functions like + sd_is_socket() alone, and thus require + identification via a name. It is recommended to rely on named file + descriptors only if identification via + sd_is_socket() and related calls is not + sufficient. Note that the names used are not unique in any + way. The returned array of strings has as many entries as file + descriptors has been received, plus a final NULL pointer + terminating the array. The caller needs to free the array itself + and each of its elements with libc's free() + call after use. If the names parameter is + NULL the call is entirely equivalent to + sd_listen_fds(). + + Under specific conditions the following automatic file + descriptor names are returned: + + + + <command>Special names</command> + + + + + + Name + Description + + + + + unknown + The process received no name for the specific file descriptor from the service manager. + + + + stored + The file descriptor originates in the service manager's per-service file descriptor store, and the FDNAME= field was absent when the file descriptor was submitted to the service manager. + + + + connection + The service was activated in per-connection style using Accept=yes in the socket unit file, and the file descriptor is the connection socket. + + + +
+
Return Value - On failure, this call returns a negative errno-style error + On failure, these calls returns a negative errno-style error code. If $LISTEN_FDS/$LISTEN_PID was not set or was not correctly set for this daemon and hence no file @@ -128,13 +206,16 @@ - Internally, this function checks whether the - $LISTEN_PID environment variable equals the - daemon PID. If not, it returns immediately. Otherwise, it parses - the number passed in the $LISTEN_FDS + Internally, sd_listen_fds() checks + whether the $LISTEN_PID environment variable + equals the daemon PID. If not, it returns immediately. Otherwise, + it parses the number passed in the $LISTEN_FDS environment variable, then sets the FD_CLOEXEC flag for the parsed number of file descriptors starting from SD_LISTEN_FDS_START. - Finally, it returns the parsed number. + Finally, it returns the parsed + number. sd_listen_fds_with_names() does the + same but also parses $LISTEN_FDNAMES if + set. @@ -144,15 +225,14 @@ $LISTEN_PID $LISTEN_FDS + $LISTEN_FDNAMES - Set by the init system - for supervised processes that use - socket-based activation. This - environment variable specifies the - data - sd_listen_fds() - parses. See above for - details. + Set by the service manager for supervised + processes that use socket-based activation. This environment + variable specifies the data + sd_listen_fds() and + sd_listen_fds_with_names() parses. See + above for details. @@ -167,6 +247,7 @@ sd_is_socket3, sd_is_socket_inet3, sd_is_socket_unix3, + sd_pid_notify_with_fds3, daemon7, systemd.service5, systemd.socket5 diff --git a/man/sd_notify.xml b/man/sd_notify.xml index 14030f56b1..2d73c27f62 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -1,4 +1,4 @@ - + @@ -229,6 +229,27 @@ below. + + FDNAME=... + + When used in combination with + FDSTORE=1 specifies a name for the + submitted file descriptors. This name is passed to the service + during activation, and may be queried using + sd_listen_fds_with_names3. File + descriptors submitted without this field set, will implicitly + get the name stored assigned. Note that if + multiple file descriptors are submitted at once the specified + name will be assigned to all of them. In order to assign + different names to submitted file descriptors, submit them in + seperate invocations of + sd_pid_notify_with_fds(). The name may + consist of any ASCII characters, but must not contain control + characters or :. It may not be longer than + 255 characters. If a submitted name does not follow these + restrictions it is ignored. + + It is recommended to prefix variable names that are not @@ -358,7 +379,7 @@ in order to continue operation after a service restart without losing state use FDSTORE=1: - sd_pid_notify_with_fds(0, 0, "FDSTORE=1", &fd, 1); + sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &fd, 1); @@ -367,9 +388,11 @@ systemd1, sd-daemon3, + sd_listen_fds3, + sd_listen_fds_with_names3, + sd_watchdog_enabled3, daemon7, - systemd.service5, - sd_watchdog_enabled3 + systemd.service5 diff --git a/man/systemd-activate.xml b/man/systemd-activate.xml index 3b854fd8ec..90e974c991 100644 --- a/man/systemd-activate.xml +++ b/man/systemd-activate.xml @@ -115,6 +115,16 @@ + + NAME + + Specify a name for the activation file + descriptors. This is equivalent to setting + FileDescriptorName= in socket unit files, and + enables use of + sd_listen_fds_with_names3. + + @@ -126,6 +136,7 @@ $LISTEN_FDS $LISTEN_PID + $LISTEN_FDNAMES See sd_listen_fds3. @@ -165,6 +176,8 @@ systemd1, systemd.socket5, systemd.service5, + sd_listen_fds3, + sd_listen_fds_with_names3, cat1 diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 212764075d..46a47b2d95 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -1,4 +1,4 @@ - + @@ -748,6 +748,22 @@ list. + + FileDescriptorName= + Assigns a name to all file descriptors this + socket unit encapsulates. This is useful to help activated + services to identify specific file descriptors, if multiple + are passed. Services may use the + sd_listen_fds_with_names3 + call to acquire the names configured for the received file + descriptors. Names may contain any ASCII character, but must + exclude control characters or :, and must + be at most 255 characters in length. If this setting is not + used the file descriptor name defaults to the name of the + socket unit, including its .socket + suffix. + + Check @@ -768,9 +784,10 @@ systemd.kill5, systemd.resource-control5, systemd.service5, - systemd.directives7 + systemd.directives7, + sd_listen_fds3, + sd_listen_fds_with_names3 - For more extensive descriptions see the "systemd for Developers" series: Socket Activation, diff --git a/man/systemd.xml b/man/systemd.xml index 9e927c3204..391333bfb4 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -835,6 +835,7 @@ $LISTEN_PID $LISTEN_FDS + $LISTEN_FDNAMES Set by systemd for supervised processes during socket-based activation. See diff --git a/src/activate/activate.c b/src/activate/activate.c index 2dfc4a0bc4..6a8432314e 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -26,7 +26,7 @@ #include #include -#include "systemd/sd-daemon.h" +#include "sd-daemon.h" #include "log.h" #include "macro.h" @@ -38,6 +38,7 @@ static char** arg_listen = NULL; static bool arg_accept = false; static char** arg_args = NULL; static char** arg_setenv = NULL; +static const char *arg_fdname = NULL; static int add_epoll(int epoll_fd, int fd) { struct epoll_event ev = { @@ -136,8 +137,8 @@ static int launch(char* name, char **argv, char **env, int fds) { length = strv_length(arg_setenv); - /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, NULL */ - envp = new0(char *, length + 7); + /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ + envp = new0(char *, length + 8); if (!envp) return log_oom(); @@ -145,7 +146,9 @@ static int launch(char* name, char **argv, char **env, int fds) { if (strchr(*s, '=')) envp[n_env++] = *s; else { - _cleanup_free_ char *p = strappend(*s, "="); + _cleanup_free_ char *p; + + p = strappend(*s, "="); if (!p) return log_oom(); envp[n_env] = strv_find_prefix(env, p); @@ -164,15 +167,37 @@ static int launch(char* name, char **argv, char **env, int fds) { (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0)) return log_oom(); + if (arg_fdname) { + char *e; + + e = strappend("LISTEN_FDNAMES=", arg_fdname); + if (!e) + return log_oom(); + + for (i = 1; i < (unsigned) fds; i++) { + char *c; + + c = strjoin(e, ":", arg_fdname, NULL); + if (!c) { + free(e); + return log_oom(); + } + + free(e); + e = c; + } + + envp[n_env++] = e; + } + tmp = strv_join(argv, " "); if (!tmp) return log_oom(); log_info("Execing %s (%s)", name, tmp); execvpe(name, argv, envp); - log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp); - return -errno; + return log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp); } static int launch1(const char* child, char** argv, char **env, int fd) { @@ -289,6 +314,7 @@ static void help(void) { static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, + ARG_FDNAME, }; static const struct option options[] = { @@ -297,11 +323,12 @@ static int parse_argv(int argc, char *argv[]) { { "listen", required_argument, NULL, 'l' }, { "accept", no_argument, NULL, 'a' }, { "setenv", required_argument, NULL, 'E' }, - { "environment", required_argument, NULL, 'E' }, /* alias */ + { "environment", required_argument, NULL, 'E' }, /* legacy alias */ + { "fdname", required_argument, NULL, ARG_FDNAME }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -315,25 +342,27 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); - case 'l': { - int r = strv_extend(&arg_listen, optarg); + case 'l': + r = strv_extend(&arg_listen, optarg); if (r < 0) - return r; + return log_oom(); break; - } case 'a': arg_accept = true; break; - case 'E': { - int r = strv_extend(&arg_setenv, optarg); + case 'E': + r = strv_extend(&arg_setenv, optarg); if (r < 0) - return r; + return log_oom(); break; - } + + case ARG_FDNAME: + arg_fdname = optarg; + break; case '?': return -EINVAL; diff --git a/src/basic/strv.c b/src/basic/strv.c index 9524e80a6f..90f0b8c741 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -277,8 +277,8 @@ char **strv_split_newlines(const char *s) { } int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) { - size_t n = 0, allocated = 0; _cleanup_strv_free_ char **l = NULL; + size_t n = 0, allocated = 0; int r; assert(t); @@ -302,13 +302,16 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract l[n] = NULL; } - if (!l) + if (!l) { l = new0(char*, 1); + if (!l) + return -ENOMEM; + } *t = l; l = NULL; - return 0; + return (int) n; } char *strv_join(char **l, const char *separator) { @@ -745,3 +748,41 @@ char **strv_skip(char **l, size_t n) { return l; } + +int strv_extend_n(char ***l, const char *value, size_t n) { + size_t i, j, k; + char **nl; + + assert(l); + + if (!value) + return 0; + if (n == 0) + return 0; + + /* Adds the value value n times to l */ + + k = strv_length(*l); + + nl = realloc(*l, sizeof(char*) * (k + n + 1)); + if (!nl) + return -ENOMEM; + + *l = nl; + + for (i = k; i < k + n; i++) { + nl[i] = strdup(value); + if (!nl[i]) + goto rollback; + } + + nl[i] = NULL; + return 0; + +rollback: + for (j = k; j < i; i++) + free(nl[j]); + + nl[k] = NULL; + return NULL; +} diff --git a/src/basic/strv.h b/src/basic/strv.h index 4c4b6526de..7c1f80230a 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -158,3 +158,5 @@ static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, i char ***strv_free_free(char ***l); char **strv_skip(char **l, size_t n); + +int strv_extend_n(char ***l, const char *value, size_t n); diff --git a/src/basic/util.c b/src/basic/util.c index c63ec0ceb0..054d45092e 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -6842,3 +6842,28 @@ int version(void) { SYSTEMD_FEATURES); return 0; } + +bool fdname_is_valid(const char *s) { + const char *p; + + /* Validates a name for $LISTEN_NAMES. We basically allow + * everything ASCII that's not a control character. Also, as + * special exception the ":" character is not allowed, as we + * use that as field separator in $LISTEN_NAMES. + * + * Note that the empty string is explicitly allowed here.*/ + + if (!s) + return false; + + for (p = s; *p; p++) { + if (*p < ' ') + return false; + if (*p >= 127) + return false; + if (*p == ':') + return false; + } + + return p - s < 256; +} diff --git a/src/basic/util.h b/src/basic/util.h index a4e3672130..034410b8a8 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -941,3 +941,5 @@ int receive_one_fd(int transport_fd, int flags); void nop_signal_handler(int sig); int version(void); + +bool fdname_is_valid(const char *s); diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index 4611ad5f86..7444649f8b 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -84,6 +84,25 @@ static int property_get_listen( return sd_bus_message_close_container(reply); } + +static int property_get_fdname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Socket *s = SOCKET(userdata); + + assert(bus); + assert(reply); + assert(s); + + return sd_bus_message_append(reply, "s", socket_fdname(s)); +} + const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), @@ -128,6 +147,7 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Socket, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("NConnections", "u", bus_property_get_unsigned, offsetof(Socket, n_connections), 0), SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0), + SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), diff --git a/src/core/execute.c b/src/core/execute.c index 137a176c18..4664af873f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -21,18 +21,18 @@ #include #include -#include -#include -#include -#include -#include -#include -#include +#include #include #include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include #ifdef HAVE_PAM #include @@ -50,37 +50,38 @@ #include #endif -#include "barrier.h" #include "sd-messages.h" -#include "rm-rf.h" -#include "strv.h" -#include "macro.h" -#include "capability.h" -#include "util.h" -#include "log.h" -#include "ioprio.h" -#include "securebits.h" -#include "namespace.h" -#include "exit-status.h" -#include "missing.h" -#include "utmp-wtmp.h" -#include "def.h" -#include "path-util.h" -#include "env-util.h" -#include "fileio.h" -#include "unit.h" -#include "async.h" -#include "selinux-util.h" -#include "errno-list.h" + #include "af-list.h" -#include "mkdir.h" -#include "smack-util.h" +#include "async.h" +#include "barrier.h" #include "bus-endpoint.h" #include "cap-list.h" +#include "capability.h" +#include "def.h" +#include "env-util.h" +#include "errno-list.h" +#include "exit-status.h" +#include "fileio.h" #include "formats-util.h" +#include "ioprio.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "namespace.h" +#include "path-util.h" #include "process-util.h" -#include "terminal-util.h" +#include "rm-rf.h" +#include "securebits.h" +#include "selinux-util.h" #include "signal-util.h" +#include "smack-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit.h" +#include "util.h" +#include "utmp-wtmp.h" #ifdef HAVE_APPARMOR #include "apparmor-util.h" @@ -1198,6 +1199,7 @@ static void do_idle_pipe_dance(int idle_pipe[4]) { static int build_environment( const ExecContext *c, unsigned n_fds, + char ** fd_names, usec_t watchdog_usec, const char *home, const char *username, @@ -1211,11 +1213,13 @@ static int build_environment( assert(c); assert(ret); - our_env = new0(char*, 10); + our_env = new0(char*, 11); if (!our_env) return -ENOMEM; if (n_fds > 0) { + _cleanup_free_ char *joined = NULL; + if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0) return -ENOMEM; our_env[n_env++] = x; @@ -1223,6 +1227,15 @@ static int build_environment( if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) return -ENOMEM; our_env[n_env++] = x; + + joined = strv_join(fd_names, ":"); + if (!joined) + return -ENOMEM; + + x = strjoin("LISTEN_FDNAMES=", joined, NULL); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; } if (watchdog_usec > 0) { @@ -1273,7 +1286,7 @@ static int build_environment( } our_env[n_env++] = NULL; - assert(n_env <= 10); + assert(n_env <= 11); *ret = our_env; our_env = NULL; @@ -1850,7 +1863,7 @@ static int exec_child( #endif } - r = build_environment(context, n_fds, params->watchdog_usec, home, username, shell, &our_env); + r = build_environment(context, n_fds, params->fd_names, params->watchdog_usec, home, username, shell, &our_env); if (r < 0) { *exit_status = EXIT_MEMORY; return r; diff --git a/src/core/execute.h b/src/core/execute.h index 2c93044748..f1c37116fd 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -208,19 +208,30 @@ struct ExecContext { struct ExecParameters { char **argv; - int *fds; unsigned n_fds; + + int *fds; + char **fd_names; + unsigned n_fds; + char **environment; + bool apply_permissions; bool apply_chroot; bool apply_tty_stdin; + bool confirm_spawn; bool selinux_context_net; + CGroupMask cgroup_supported; const char *cgroup_path; bool cgroup_delegate; + const char *runtime_prefix; + usec_t watchdog_usec; + int *idle_pipe; + char *bus_endpoint_path; int bus_endpoint_fd; }; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 2333926e7d..89e624b557 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -287,6 +287,7 @@ Socket.MessageQueueMaxMessages, config_parse_long, 0, Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop) Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) +Socket.FileDescriptorName, config_parse_fdname, 0, 0 Socket.Service, config_parse_socket_service, 0, 0 m4_ifdef(`HAVE_SMACK', `Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index fc2755cb92..b1d4c6b57d 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1475,10 +1475,10 @@ int config_parse_socket_service( void *userdata) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - Socket *s = data; - int r; - Unit *x; _cleanup_free_ char *p = NULL; + Socket *s = data; + Unit *x; + int r; assert(filename); assert(lvalue); @@ -1507,6 +1507,50 @@ int config_parse_socket_service( return 0; } +int config_parse_fdname( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *p = NULL; + Socket *s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + s->fdname = mfree(s->fdname); + return 0; + } + + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!fdname_is_valid(p)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p); + return 0; + } + + free(s->fdname); + s->fdname = p; + p = NULL; + + return 0; +} + int config_parse_service_sockets( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 6ee7c71bc4..8661cbfedc 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -107,6 +107,7 @@ int config_parse_protect_system(const char* unit, const char *filename, unsigned int config_parse_bus_name(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/manager.c b/src/core/manager.c index 9de9691a47..526d4d1cef 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -495,6 +495,7 @@ static void manager_clean_environment(Manager *m) { "MANAGERPID", "LISTEN_PID", "LISTEN_FDS", + "LISTEN_FDNAMES", "WATCHDOG_PID", "WATCHDOG_USEC", NULL); diff --git a/src/core/service.c b/src/core/service.c index 2c78cb96c2..c941689dd7 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -261,6 +261,7 @@ static void service_fd_store_unlink(ServiceFDStore *fs) { sd_event_source_unref(fs->event_source); } + free(fs->fdname); safe_close(fs->fd); free(fs); } @@ -334,7 +335,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us return 0; } -static int service_add_fd_store(Service *s, int fd) { +static int service_add_fd_store(Service *s, int fd, const char *name) { ServiceFDStore *fs; int r; @@ -361,9 +362,13 @@ static int service_add_fd_store(Service *s, int fd) { fs->fd = fd; fs->service = s; + fs->fdname = strdup(name ?: "stored"); + if (!fs->fdname) + return -ENOMEM; r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs); if (r < 0) { + free(fs->fdname); free(fs); return r; } @@ -376,7 +381,7 @@ static int service_add_fd_store(Service *s, int fd) { return 1; } -static int service_add_fd_store_set(Service *s, FDSet *fds) { +static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { int r; assert(s); @@ -391,7 +396,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds) { if (fd < 0) break; - r = service_add_fd_store(s, fd); + r = service_add_fd_store(s, fd, name); if (r < 0) return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); if (r > 0) { @@ -957,56 +962,79 @@ static int service_coldplug(Unit *u) { return 0; } -static int service_collect_fds(Service *s, int **fds) { +static int service_collect_fds(Service *s, int **fds, char ***fd_names) { + _cleanup_strv_free_ char **rfd_names = NULL; _cleanup_free_ int *rfds = NULL; - int rn_fds = 0; - Iterator i; - Unit *u; + int rn_fds = 0, r; assert(s); assert(fds); + assert(fd_names); - if (s->socket_fd >= 0) - return -EINVAL; + if (s->socket_fd >= 0) { - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { - _cleanup_free_ int *cfds = NULL; - Socket *sock; - int cn_fds; + /* Pass the per-connection socket */ - if (u->type != UNIT_SOCKET) - continue; + rfds = new(int, 1); + if (!rfds) + return -ENOMEM; + rfds[0] = s->socket_fd; - sock = SOCKET(u); + rfd_names = strv_new("connection", NULL); + if (!rfd_names) + return -ENOMEM; - cn_fds = socket_collect_fds(sock, &cfds); - if (cn_fds < 0) - return cn_fds; + rn_fds = 1; + } else { + Iterator i; + Unit *u; - if (cn_fds <= 0) - continue; + /* Pass all our configured sockets for singleton services */ - if (!rfds) { - rfds = cfds; - rn_fds = cn_fds; + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { + _cleanup_free_ int *cfds = NULL; + Socket *sock; + int cn_fds; - cfds = NULL; - } else { - int *t; + if (u->type != UNIT_SOCKET) + continue; - t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); - if (!t) - return -ENOMEM; + sock = SOCKET(u); - memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); + cn_fds = socket_collect_fds(sock, &cfds); + if (cn_fds < 0) + return cn_fds; - rfds = t; - rn_fds += cn_fds; + if (cn_fds <= 0) + continue; + + if (!rfds) { + rfds = cfds; + rn_fds = cn_fds; + + cfds = NULL; + } else { + int *t; + + t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); + if (!t) + return -ENOMEM; + + memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); + + rfds = t; + rn_fds += cn_fds; + } + + r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); + if (r < 0) + return r; } } if (s->n_fd_store > 0) { ServiceFDStore *fs; + char **nl; int *t; t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int)); @@ -1014,12 +1042,30 @@ static int service_collect_fds(Service *s, int **fds) { return -ENOMEM; rfds = t; - LIST_FOREACH(fd_store, fs, s->fd_store) - rfds[rn_fds++] = fs->fd; + + nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*)); + if (!nl) + return -ENOMEM; + + rfd_names = nl; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + rfds[rn_fds] = fs->fd; + rfd_names[rn_fds] = strdup(strempty(fs->fdname)); + if (!rfd_names[rn_fds]) + return -ENOMEM; + + rn_fds++; + } + + rfd_names[rn_fds] = NULL; } *fds = rfds; + *fd_names = rfd_names; + rfds = NULL; + rfd_names = NULL; return rn_fds; } @@ -1035,15 +1081,13 @@ static int service_spawn( bool is_control, pid_t *_pid) { - pid_t pid; - int r; - int *fds = NULL; - _cleanup_free_ int *fdsbuf = NULL; - unsigned n_fds = 0, n_env = 0; + _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; _cleanup_free_ char *bus_endpoint_path = NULL; - _cleanup_strv_free_ char - **argv = NULL, **final_env = NULL, **our_env = NULL; + _cleanup_free_ int *fds = NULL; + unsigned n_fds = 0, n_env = 0; const char *path; + pid_t pid; + ExecParameters exec_params = { .apply_permissions = apply_permissions, .apply_chroot = apply_chroot, @@ -1052,6 +1096,8 @@ static int service_spawn( .selinux_context_net = s->socket_fd_selinux_context_net }; + int r; + assert(s); assert(c); assert(_pid); @@ -1071,17 +1117,11 @@ static int service_spawn( s->exec_context.std_output == EXEC_OUTPUT_SOCKET || s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { - if (s->socket_fd >= 0) { - fds = &s->socket_fd; - n_fds = 1; - } else { - r = service_collect_fds(s, &fdsbuf); - if (r < 0) - goto fail; + r = service_collect_fds(s, &fds, &fd_names); + if (r < 0) + goto fail; - fds = fdsbuf; - n_fds = r; - } + n_fds = r; } if (timeout > 0) { @@ -1119,7 +1159,7 @@ static int service_spawn( goto fail; } - if (UNIT_DEREF(s->accept_socket)) { + if (s->socket_fd >= 0) { union sockaddr_union sa; socklen_t salen = sizeof(sa); @@ -1185,6 +1225,7 @@ static int service_spawn( exec_params.argv = argv; exec_params.fds = fds; + exec_params.fd_names = fd_names; exec_params.n_fds = n_fds; exec_params.environment = final_env; exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; @@ -2047,13 +2088,16 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { } LIST_FOREACH(fd_store, fs, s->fd_store) { + _cleanup_free_ char *c = NULL; int copy; copy = fdset_put_dup(fds, fs->fd); if (copy < 0) return copy; - unit_serialize_item_format(u, f, "fd-store-fd", "%i", copy); + c = cescape(fs->fdname); + + unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c)); } if (s->main_exec_status.pid > 0) { @@ -2183,12 +2227,24 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->bus_endpoint_fd = fdset_remove(fds, fd); } } else if (streq(key, "fd-store-fd")) { + const char *fdv; + size_t pf; int fd; - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + pf = strcspn(value, WHITESPACE); + fdv = strndupa(value, pf); + + if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value); else { - r = service_add_fd_store(s, fd); + _cleanup_free_ char *t = NULL; + const char *fdn; + + fdn = value + pf; + fdn += strspn(fdn, WHITESPACE); + (void) cunescape(fdn, 0, &t); + + r = service_add_fd_store(s, fd, t); if (r < 0) log_unit_error_errno(u, r, "Failed to add fd to store: %m"); else if (r > 0) @@ -2942,8 +2998,17 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) if (strv_find(tags, "WATCHDOG=1")) service_reset_watchdog(s); - if (strv_find(tags, "FDSTORE=1")) - service_add_fd_store_set(s, fds); + if (strv_find(tags, "FDSTORE=1")) { + const char *name; + + name = strv_find_startswith(tags, "FDNAME="); + if (name && !fdname_is_valid(name)) { + log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring."); + name = NULL; + } + + service_add_fd_store_set(s, fds, name); + } /* Notify clients about changed status or main pid */ if (notify_dbus) diff --git a/src/core/service.h b/src/core/service.h index a8d42706bd..61bb44fbcf 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -97,6 +97,7 @@ struct ServiceFDStore { Service *service; int fd; + char *fdname; sd_event_source *event_source; LIST_FIELDS(ServiceFDStore, fd_store); diff --git a/src/core/socket.c b/src/core/socket.c index 4462fbd72d..6300b20f3e 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -508,6 +508,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { "%sTCPCongestion: %s\n" "%sRemoveOnStop: %s\n" "%sWritable: %s\n" + "%sFDName: %s\n" "%sSELinuxContextFromNet: %s\n", prefix, socket_state_to_string(s->state), prefix, socket_result_to_string(s->result), @@ -525,6 +526,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(s->tcp_congestion), prefix, yes_no(s->remove_on_stop), prefix, yes_no(s->writable), + prefix, socket_fdname(s), prefix, yes_no(s->selinux_context_from_net)); if (s->control_pid > 0) @@ -2760,6 +2762,19 @@ static int socket_get_timeout(Unit *u, uint64_t *timeout) { return 1; } +char *socket_fdname(Socket *s) { + assert(s); + + /* Returns the name to use for $LISTEN_NAMES. If the user + * didn't specify anything specifically, use the socket unit's + * name as fallback. */ + + if (s->fdname) + return s->fdname; + + return UNIT(s)->id; +} + static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { [SOCKET_EXEC_START_PRE] = "StartPre", [SOCKET_EXEC_START_CHOWN] = "StartChown", diff --git a/src/core/socket.h b/src/core/socket.h index 96091b0609..94cda8a90d 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -154,6 +154,8 @@ struct Socket { char *user, *group; bool reset_cpu_usage:1; + + char *fdname; }; /* Called from the service code when collecting fds */ @@ -164,6 +166,10 @@ void socket_connection_unref(Socket *s); void socket_free_ports(Socket *s); +int socket_instantiate_service(Socket *s); + +char *socket_fdname(Socket *s); + extern const UnitVTable socket_vtable; const char* socket_exec_command_to_string(SocketExecCommand i) _const_; @@ -173,5 +179,3 @@ const char* socket_result_to_string(SocketResult i) _const_; SocketResult socket_result_from_string(const char *s) _pure_; const char* socket_port_type_to_string(SocketPort *p) _pure_; - -int socket_instantiate_service(Socket *s); diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 5fb0b73d02..437518119b 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -19,25 +19,37 @@ along with systemd; If not, see . ***/ -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "util.h" #include "path-util.h" #include "socket-util.h" +#include "strv.h" +#include "util.h" + #include "sd-daemon.h" +static void unsetenv_all(bool unset_environment) { + + if (!unset_environment) + return; + + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + unsetenv("LISTEN_FDNAMES"); +} + _public_ int sd_listen_fds(int unset_environment) { const char *e; unsigned n; @@ -79,12 +91,49 @@ _public_ int sd_listen_fds(int unset_environment) { r = (int) n; finish: - if (unset_environment) { - unsetenv("LISTEN_PID"); - unsetenv("LISTEN_FDS"); + unsetenv_all(unset_environment); + return r; +} + +_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) { + _cleanup_strv_free_ char **l = NULL; + bool have_names; + int n_names = 0, n_fds; + const char *e; + int r; + + if (!names) + return sd_listen_fds(unset_environment); + + e = getenv("LISTEN_FDNAMES"); + if (e) { + n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (n_names < 0) { + unsetenv_all(unset_environment); + return n_names; + } + + have_names = true; + } else + have_names = false; + + n_fds = sd_listen_fds(unset_environment); + if (n_fds <= 0) + return n_fds; + + if (have_names) { + if (n_names != n_fds) + return -EINVAL; + } else { + r = strv_extend_n(&l, "unknown", n_fds); + if (r < 0) + return r; } - return r; + *names = l; + l = NULL; + + return n_fds; } _public_ int sd_is_fifo(int fd, const char *path) { diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h index 861dc8f1f4..214e77cab1 100644 --- a/src/systemd/sd-daemon.h +++ b/src/systemd/sd-daemon.h @@ -76,6 +76,8 @@ _SD_BEGIN_DECLARATIONS; */ int sd_listen_fds(int unset_environment); +int sd_listen_fds_with_names(int unset_environment, char ***names); + /* Helper call for identifying a passed file descriptor. Returns 1 if the file descriptor is a FIFO in the file system stored under the diff --git a/src/test/test-daemon.c b/src/test/test-daemon.c index 7e0ac754d1..45fb554445 100644 --- a/src/test/test-daemon.c +++ b/src/test/test-daemon.c @@ -21,9 +21,22 @@ #include -#include "systemd/sd-daemon.h" +#include "sd-daemon.h" + +#include "strv.h" int main(int argc, char*argv[]) { + _cleanup_strv_free_ char **l = NULL; + int n, i; + + n = sd_listen_fds_with_names(false, &l); + if (n < 0) { + log_error_errno(n, "Failed to get listening fds: %m"); + return EXIT_FAILURE; + } + + for (i = 0; i < n; i++) + log_info("fd=%i name=%s\n", SD_LISTEN_FDS_START + i, l[i]); sd_notify(0, "STATUS=Starting up"); @@ -49,5 +62,5 @@ int main(int argc, char*argv[]) { "STOPPING=1"); sleep(5); - return 0; + return EXIT_SUCCESS; } diff --git a/src/test/test-strv.c b/src/test/test-strv.c index cc47fd4d34..64801f8e01 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -155,7 +155,7 @@ static void test_strv_join(void) { static void test_strv_quote_unquote(const char* const *split, const char *quoted) { _cleanup_free_ char *p; - _cleanup_strv_free_ char **s; + _cleanup_strv_free_ char **s = NULL; char **t; int r; @@ -166,7 +166,7 @@ static void test_strv_quote_unquote(const char* const *split, const char *quoted assert_se(streq(p, quoted)); r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES); - assert_se(r == 0); + assert_se(r == (int) strv_length(s)); assert_se(s); STRV_FOREACH(t, s) { assert_se(*t); @@ -183,7 +183,7 @@ static void test_strv_unquote(const char *quoted, char **list) { int r; r = strv_split_extract(&s, quoted, WHITESPACE, EXTRACT_QUOTES); - assert_se(r == 0); + assert_se(r == (int) strv_length(list)); assert_se(s); j = strv_join(s, " | "); assert_se(j); @@ -225,7 +225,7 @@ static void test_strv_split_extract(void) { int r; r = strv_split_extract(&l, str, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - assert_se(r == 0); + assert_se(r == (int) strv_length(l)); assert_se(streq_ptr(l[0], "")); assert_se(streq_ptr(l[1], "foo:bar")); assert_se(streq_ptr(l[2], "")); @@ -591,6 +591,33 @@ static void test_strv_skip(void) { test_strv_skip_one(STRV_MAKE(NULL), 55, STRV_MAKE(NULL)); } +static void test_strv_extend_n(void) { + _cleanup_strv_free_ char **v = NULL; + + v = strv_new("foo", "bar", NULL); + assert_se(v); + + assert_se(strv_extend_n(&v, "waldo", 3) >= 0); + assert_se(strv_extend_n(&v, "piep", 2) >= 0); + + assert_se(streq(v[0], "foo")); + assert_se(streq(v[1], "bar")); + assert_se(streq(v[2], "waldo")); + assert_se(streq(v[3], "waldo")); + assert_se(streq(v[4], "waldo")); + assert_se(streq(v[5], "piep")); + assert_se(streq(v[6], "piep")); + assert_se(v[7] == NULL); + + v = strv_free(v); + + assert_se(strv_extend_n(&v, "foo", 1) >= 0); + assert_se(strv_extend_n(&v, "bar", 0) >= 0); + + assert_se(streq(v[0], "foo")); + assert_se(v[1] == NULL); +} + int main(int argc, char *argv[]) { test_specifier_printf(); test_strv_foreach(); @@ -650,6 +677,7 @@ int main(int argc, char *argv[]) { test_strv_reverse(); test_strv_shell_escape(); test_strv_skip(); + test_strv_extend_n(); return 0; }