diff --git a/NEWS b/NEWS index 537c4b6131..0d5b95a42a 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,32 @@ systemd System and Service Manager +CHANGES WITH 240 in spe: + + * A new service type has been added: Type=exec. It's very similar to + Type=simple and ensures the service manager will wait for both fork() + and execve() of the main service binary to complete before proceeding + with follow-up units. This is primarily useful so that the manager + propagates any errors in the preparation phase of service execution + back to the job that requested the unit to be started. For example, + consider a service that has ExecStart= set to a file system binary + that doesn't exist. With Type=simple starting the unit would + typically succeed instantly, as only fork() has to complete + successfully and execve() is not waited for, and hence its failure is + seen "too late". With the new Type=exec service type starting the + unit will fail, as the execve() will be waited for and will fail, + which is then propagated back to the start job. + + NOTE: with the next release 241 of systemd we intend to change the + systemd-run tool to default to Type=exec for transient services + started by it. This should be mostly safe, but in specific corner + cases might result in problems, as the systemd-run tool will then + block on NSS calls (such as user name lookups due to User=) done + between the fork() and execve(), which under specific circumstances + might cause problems. It is recommended to specify "-p Type=simple" + explicitly in the few cases where this applies. For regular, + non-transient services (i.e. those defined with unit files on disk) + we will continue to default to Type=simple. + CHANGES WITH 239: * NETWORK INTERFACE DEVICE NAMING CHANGES: systemd-udevd's "net_id" diff --git a/TODO b/TODO index 72b1dc7609..9bb01fd574 100644 --- a/TODO +++ b/TODO @@ -35,6 +35,8 @@ Features: course, and watching things asynchronously even more so, but it should be good enough for the idle detection logic at least. +* maybe extend .path units to expose fanotify() per-mount change events + * Add a "systemctl list-units --by-slice" mode or so, which rearranges the output of "systemctl list-units" slightly by showing the tree structure of the slices, and the units attached to them. @@ -69,10 +71,6 @@ Features: that our log messages could contain clickable links for example for unit files and suchlike we operate on. -* introduce a new SystemCallFilters= group called "@system-service" with a - sensible default set for system services, then make use of them in portable - profiles - * add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) * add attach --enable and attach --now (for attach+enable+start) diff --git a/man/systemd-run.xml b/man/systemd-run.xml index 149228ee02..6c15d2d837 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -83,6 +83,16 @@ COMMAND may be omitted. In this case, systemd-run creates only a .path, .socket, or .timer unit that triggers the specified unit. + + By default, services created with systemd-run default to the type, + see the description of Type= in + systemd.service5 for + details. Note that when this type is used the service manager (and thus the systemd-run command) + considers service start-up successful as soon as the fork() for the main service process + succeeded, i.e. before the execve() is invoked, and thus even if the specified command cannot + be started. Consider using the service type (i.e. ) to + ensure that systemd-run returns successfully only if the specified command line has been + successfully started. diff --git a/man/systemd.service.xml b/man/systemd.service.xml index f14a057280..0cd5385f9b 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -153,77 +153,93 @@ Type= - Configures the process start-up type for this - service unit. One of - , - , - , - , - or - . + + Configures the process start-up type for this service unit. One of , + , , , , + or : - If set to (the default if - neither Type= nor - BusName=, but ExecStart= - are specified), it is expected that the process configured - with ExecStart= is the main process of the - service. In this mode, if the process offers functionality to - other processes on the system, its communication channels - should be installed before the daemon is started up (e.g. - sockets set up by systemd, via socket activation), as systemd - will immediately proceed starting follow-up units. + + If set to (the default if ExecStart= is + specified but neither Type= nor BusName= are), the service manager + will consider the unit started immediately after the main service process has been forked off. It is + expected that the process configured with ExecStart= is the main process of the + service. In this mode, if the process offers functionality to other processes on the system, its + communication channels should be installed before the service is started up (e.g. sockets set up by + systemd, via socket activation), as the service manager will immediately proceed starting follow-up units, + right after creating the main service process, and before executing the service's binary. Note that this + means systemctl start command lines for services will report + success even if the service's binary cannot be invoked successfully (for example because the selected + User= doesn't exist, or the service binary is missing). - If set to , it is expected that - the process configured with ExecStart= will - call fork() as part of its start-up. The - parent process is expected to exit when start-up is complete - and all communication channels are set up. The child continues - to run as the main daemon process. This is the behavior of - traditional UNIX daemons. If this setting is used, it is - recommended to also use the PIDFile= - option, so that systemd can identify the main process of the - daemon. systemd will proceed with starting follow-up units as - soon as the parent process exits. + The type is similar to , but the service + manager will consider the unit started immediately after the main service binary has been executed. The service + manager will delay starting of follow-up units until that point. (Or in other words: + proceeds with further jobs right after fork() returns, while + will not proceed before both fork() and + execve() in the service process succeeded.) Note that this means systemctl + start command lines for services will report failure when the service's + binary cannot be invoked successfully (for example because the selected User= doesn't + exist, or the service binary is missing). - Behavior of is similar to - ; however, it is expected that the - process has to exit before systemd starts follow-up units. - RemainAfterExit= is particularly useful for - this type of service. This is the implied default if neither - Type= nor ExecStart= are - specified. + If set to , it is expected that the process configured with + ExecStart= will call fork() as part of its start-up. The parent + process is expected to exit when start-up is complete and all communication channels are set up. The child + continues to run as the main service process, and the service manager will consider the unit started when + the parent process exits. This is the behavior of traditional UNIX services. If this setting is used, it is + recommended to also use the PIDFile= option, so that systemd can reliably identify the + main process of the service. systemd will proceed with starting follow-up units as soon as the parent + process exits. - Behavior of is similar to - ; however, it is expected that the - daemon acquires a name on the D-Bus bus, as configured by - BusName=. systemd will proceed with - starting follow-up units after the D-Bus bus name has been - acquired. Service units with this option configured implicitly - gain dependencies on the dbus.socket - unit. This type is the default if BusName= - is specified. + Behavior of is similar to ; however, the + service manager will consider the unit started after the main process exits. It will then start follow-up + units. RemainAfterExit= is particularly useful for this type of + service. Type= is the implied default if neither + Type= nor ExecStart= are specified. - Behavior of is similar to - ; however, it is expected that the - daemon sends a notification message via - sd_notify3 - or an equivalent call when it has finished starting up. - systemd will proceed with starting follow-up units after this - notification message has been sent. If this option is used, - NotifyAccess= (see below) should be set to - open access to the notification socket provided by systemd. If - NotifyAccess= is missing or set to - , it will be forcibly set to - . Note that currently - Type= will not work - if used in combination with - PrivateNetwork=. + Behavior of is similar to ; however, it is + expected that the service acquires a name on the D-Bus bus, as configured by + BusName=. systemd will proceed with starting follow-up units after the D-Bus bus name + has been acquired. Service units with this option configured implicitly gain dependencies on the + dbus.socket unit. This type is the default if BusName= is + specified. - Behavior of is very similar to ; however, actual execution - of the service program is delayed until all active jobs are dispatched. This may be used to avoid interleaving - of output of shell services with the status output on the console. Note that this type is useful only to - improve console output, it is not useful as a general unit ordering tool, and the effect of this service type - is subject to a 5s time-out, after which the service program is invoked anyway. + Behavior of is similar to ; however, it is + expected that the service sends a notification message via + sd_notify3 or an + equivalent call when it has finished starting up. systemd will proceed with starting follow-up units after + this notification message has been sent. If this option is used, NotifyAccess= (see + below) should be set to open access to the notification socket provided by systemd. If + NotifyAccess= is missing or set to , it will be forcibly set to + . Note that currently Type= will not work if + used in combination with PrivateNetwork=. + + Behavior of is very similar to ; however, + actual execution of the service program is delayed until all active jobs are dispatched. This may be used + to avoid interleaving of output of shell services with the status output on the console. Note that this + type is useful only to improve console output, it is not useful as a general unit ordering tool, and the + effect of this service type is subject to a 5s time-out, after which the service program is invoked + anyway. + + + It is generally recommended to use Type= for long-running + services whenever possible, as it is the simplest and fastest option. However, as this service type won't + propagate service start-up failures and doesn't allow ordering of other units against completion of + initialization of the service (which for example is useful if clients need to connect to the service through + some form of IPC, and the IPC channel is only established by the service itself — in contrast to doing this + ahead of time through socket or bus activation or similar), it might not be sufficient for many cases. If so, + or (the latter only in case the service provides a D-Bus + interface) are the preferred options as they allow service program code to precisely schedule when to + consider the service started up successfully and when to proceed with follow-up units. The + service type requires explicit support in the service codebase (as + sd_notify() or an equivalent API needs to be invoked by the service at the appropriate + time) — if it's not supported, then is an alternative: it supports the traditional + UNIX service start-up protocol. Finally, might be an option for cases where it is + enough to ensure the service binary is invoked, and where the service binary itself executes no or little + initialization on its own (and its initialization is unlikely to fail). Note that using any type other than + possibly delays the boot process, as the service manager needs to wait for service + initialization to complete. It is hence recommended not to needlessly use any types other than + . (Also note it is generally not recommended to use or + for long-running services.) diff --git a/src/basic/util.c b/src/basic/util.c index 2206c1b4ad..f951d641d7 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -230,6 +230,11 @@ int container_get_leader(const char *machine, pid_t *pid) { assert(machine); assert(pid); + if (streq(machine, ".host")) { + *pid = 1; + return 0; + } + if (!machine_name_is_valid(machine)) return -EINVAL; diff --git a/src/core/execute.c b/src/core/execute.c index a08e3105bf..0f36f6c825 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -148,11 +148,11 @@ static int shift_fds(int fds[], size_t n_fds) { return 0; } -static int flags_fds(const int fds[], size_t n_storage_fds, size_t n_socket_fds, bool nonblock) { +static int flags_fds(const int fds[], size_t n_socket_fds, size_t n_storage_fds, bool nonblock) { size_t i, n_fds; int r; - n_fds = n_storage_fds + n_socket_fds; + n_fds = n_socket_fds + n_storage_fds; if (n_fds <= 0) return 0; @@ -2573,6 +2573,7 @@ static int close_remaining_fds( const DynamicCreds *dcreds, int user_lookup_fd, int socket_fd, + int exec_fd, int *fds, size_t n_fds) { size_t n_dont_close = 0; @@ -2589,6 +2590,8 @@ static int close_remaining_fds( if (socket_fd >= 0) dont_close[n_dont_close++] = socket_fd; + if (exec_fd >= 0) + dont_close[n_dont_close++] = exec_fd; if (n_fds > 0) { memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds); n_dont_close += n_fds; @@ -2725,16 +2728,17 @@ static int exec_child( int socket_fd, int named_iofds[3], int *fds, - size_t n_storage_fds, size_t n_socket_fds, + size_t n_storage_fds, char **files_env, int user_lookup_fd, int *exit_status) { _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL; - _cleanup_free_ char *home_buffer = NULL; + int *fds_with_exec_fd, n_fds_with_exec_fd, r, ngids = 0, exec_fd = -1; _cleanup_free_ gid_t *supplementary_gids = NULL; const char *username = NULL, *groupname = NULL; + _cleanup_free_ char *home_buffer = NULL; const char *home = NULL, *shell = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; @@ -2754,7 +2758,6 @@ static int exec_child( #endif uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; - int r, ngids = 0; size_t n_fds; ExecDirectoryType dt; int secure_bits; @@ -2798,8 +2801,8 @@ static int exec_child( /* In case anything used libc syslog(), close this here, too */ closelog(); - n_fds = n_storage_fds + n_socket_fds; - r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds); + n_fds = n_socket_fds + n_storage_fds; + r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, params->exec_fd, fds, n_fds); if (r < 0) { *exit_status = EXIT_FDS; return log_unit_error_errno(unit, r, "Failed to close unwanted file descriptors: %m"); @@ -3172,18 +3175,59 @@ static int exec_child( } /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that we are - * more aggressive this time since socket_fd and the netns fds we don't need anymore. The custom endpoint fd - * was needed to upload the policy and can now be closed as well. */ - r = close_all_fds(fds, n_fds); + * more aggressive this time since socket_fd and the netns fds we don't need anymore. We do keep the exec_fd + * however if we have it as we want to keep it open until the final execve(). */ + + if (params->exec_fd >= 0) { + exec_fd = params->exec_fd; + + if (exec_fd < 3 + (int) n_fds) { + int moved_fd; + + /* Let's move the exec fd far up, so that it's outside of the fd range we want to pass to the + * process we are about to execute. */ + + moved_fd = fcntl(exec_fd, F_DUPFD_CLOEXEC, 3 + (int) n_fds); + if (moved_fd < 0) { + *exit_status = EXIT_FDS; + return log_unit_error_errno(unit, errno, "Couldn't move exec fd up: %m"); + } + + safe_close(exec_fd); + exec_fd = moved_fd; + } else { + /* This fd should be FD_CLOEXEC already, but let's make sure. */ + r = fd_cloexec(exec_fd, true); + if (r < 0) { + *exit_status = EXIT_FDS; + return log_unit_error_errno(unit, r, "Failed to make exec fd FD_CLOEXEC: %m"); + } + } + + fds_with_exec_fd = newa(int, n_fds + 1); + memcpy(fds_with_exec_fd, fds, n_fds * sizeof(int)); + fds_with_exec_fd[n_fds] = exec_fd; + n_fds_with_exec_fd = n_fds + 1; + } else { + fds_with_exec_fd = fds; + n_fds_with_exec_fd = n_fds; + } + + r = close_all_fds(fds_with_exec_fd, n_fds_with_exec_fd); if (r >= 0) r = shift_fds(fds, n_fds); if (r >= 0) - r = flags_fds(fds, n_storage_fds, n_socket_fds, context->non_blocking); + r = flags_fds(fds, n_socket_fds, n_storage_fds, context->non_blocking); if (r < 0) { *exit_status = EXIT_FDS; return log_unit_error_errno(unit, r, "Failed to adjust passed file descriptors: %m"); } + /* At this point, the fds we want to pass to the program are all ready and set up, with O_CLOEXEC turned off + * and at the right fd numbers. The are no other fds open, with one exception: the exec_fd if it is defined, + * and it has O_CLOEXEC set, after all we want it to be closed by the execve(), so that our parent knows we + * came this far. */ + secure_bits = context->secure_bits; if (needs_sandboxing) { @@ -3414,10 +3458,35 @@ static int exec_child( LOG_UNIT_INVOCATION_ID(unit)); } - execve(command->path, final_argv, accum_env); + if (exec_fd >= 0) { + uint8_t hot = 1; - if (errno == ENOENT && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) { - log_struct_errno(LOG_INFO, errno, + /* We have finished with all our initializations. Let's now let the manager know that. From this point + * on, if the manager sees POLLHUP on the exec_fd, then execve() was successful. */ + + if (write(exec_fd, &hot, sizeof(hot)) < 0) { + *exit_status = EXIT_EXEC; + return log_unit_error_errno(unit, errno, "Failed to enable exec_fd: %m"); + } + } + + execve(command->path, final_argv, accum_env); + r = -errno; + + if (exec_fd >= 0) { + uint8_t hot = 0; + + /* The execve() failed. This means the exec_fd is still open. Which means we need to tell the manager + * that POLLHUP on it no longer means execve() succeeded. */ + + if (write(exec_fd, &hot, sizeof(hot)) < 0) { + *exit_status = EXIT_EXEC; + return log_unit_error_errno(unit, errno, "Failed to disable exec_fd: %m"); + } + } + + if (r == -ENOENT && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) { + log_struct_errno(LOG_INFO, r, "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, LOG_UNIT_ID(unit), LOG_UNIT_INVOCATION_ID(unit), @@ -3428,7 +3497,7 @@ static int exec_child( } *exit_status = EXIT_EXEC; - return log_unit_error_errno(unit, errno, "Failed to execute command: %m"); + return log_unit_error_errno(unit, r, "Failed to execute command: %m"); } static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***l); @@ -3456,7 +3525,7 @@ int exec_spawn(Unit *unit, assert(context); assert(ret); assert(params); - assert(params->fds || (params->n_storage_fds + params->n_socket_fds <= 0)); + assert(params->fds || (params->n_socket_fds + params->n_storage_fds <= 0)); if (context->std_input == EXEC_INPUT_SOCKET || context->std_output == EXEC_OUTPUT_SOCKET || @@ -3476,8 +3545,8 @@ int exec_spawn(Unit *unit, } else { socket_fd = -1; fds = params->fds; - n_storage_fds = params->n_storage_fds; n_socket_fds = params->n_socket_fds; + n_storage_fds = params->n_storage_fds; } r = exec_context_named_iofds(context, params, named_iofds); @@ -3516,8 +3585,8 @@ int exec_spawn(Unit *unit, socket_fd, named_iofds, fds, - n_storage_fds, n_socket_fds, + n_storage_fds, files_env, unit->manager->user_lookup_fds[1], &exit_status); diff --git a/src/core/execute.h b/src/core/execute.h index de44085492..cd5165c2d1 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -86,10 +86,10 @@ struct ExecStatus { }; typedef enum ExecCommandFlags { - EXEC_COMMAND_IGNORE_FAILURE = 1, - EXEC_COMMAND_FULLY_PRIVILEGED = 2, - EXEC_COMMAND_NO_SETUID = 4, - EXEC_COMMAND_AMBIENT_MAGIC = 8, + EXEC_COMMAND_IGNORE_FAILURE = 1 << 0, + EXEC_COMMAND_FULLY_PRIVILEGED = 1 << 1, + EXEC_COMMAND_NO_SETUID = 1 << 2, + EXEC_COMMAND_AMBIENT_MAGIC = 1 << 3, } ExecCommandFlags; struct ExecCommand { @@ -297,8 +297,8 @@ struct ExecParameters { int *fds; char **fd_names; - size_t n_storage_fds; size_t n_socket_fds; + size_t n_storage_fds; ExecFlags flags; bool selinux_context_net:1; @@ -317,6 +317,9 @@ struct ExecParameters { int stdin_fd; int stdout_fd; int stderr_fd; + + /* An fd that is closed by the execve(), and thus will result in EOF when the execve() is done */ + int exec_fd; }; #include "unit.h" diff --git a/src/core/mount.c b/src/core/mount.c index 21437dad08..16229d4af1 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -747,10 +747,11 @@ static void mount_dump(Unit *u, FILE *f, const char *prefix) { static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { ExecParameters exec_params = { - .flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + .exec_fd = -1, }; pid_t pid; int r; diff --git a/src/core/service.c b/src/core/service.c index db1356c417..6abf87cb39 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -79,9 +79,10 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING }; -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata); +static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata); static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); +static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata); static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); static void service_enter_reload_by_notify(Service *s); @@ -389,6 +390,7 @@ static void service_done(Unit *u) { service_stop_watchdog(s); s->timer_event_source = sd_event_source_unref(s->timer_event_source); + s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source); service_release_resources(u); } @@ -1066,6 +1068,9 @@ static void service_set_state(Service *s, ServiceState state) { !(state == SERVICE_DEAD && UNIT(s)->job)) service_close_socket_fd(s); + if (state != SERVICE_START) + s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source); + if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) service_stop_watchdog(s); @@ -1178,21 +1183,23 @@ static int service_coldplug(Unit *u) { return 0; } -static int service_collect_fds(Service *s, - int **fds, - char ***fd_names, - unsigned *n_storage_fds, - unsigned *n_socket_fds) { +static int service_collect_fds( + Service *s, + int **fds, + char ***fd_names, + size_t *n_socket_fds, + size_t *n_storage_fds) { _cleanup_strv_free_ char **rfd_names = NULL; _cleanup_free_ int *rfds = NULL; - unsigned rn_socket_fds = 0, rn_storage_fds = 0; + size_t rn_socket_fds = 0, rn_storage_fds = 0; int r; assert(s); assert(fds); assert(fd_names); assert(n_socket_fds); + assert(n_storage_fds); if (s->socket_fd >= 0) { @@ -1256,7 +1263,7 @@ static int service_collect_fds(Service *s, if (s->n_fd_store > 0) { ServiceFDStore *fs; - unsigned n_fds; + size_t n_fds; char **nl; int *t; @@ -1294,6 +1301,63 @@ static int service_collect_fds(Service *s, return 0; } +static int service_allocate_exec_fd_event_source( + Service *s, + int fd, + sd_event_source **ret_event_source) { + + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int r; + + assert(s); + assert(fd >= 0); + assert(ret_event_source); + + r = sd_event_add_io(UNIT(s)->manager->event, &source, fd, 0, service_dispatch_exec_io, s); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to allocate exec_fd event source: %m"); + + /* This is a bit lower priority than SIGCHLD, as that carries a lot more interesting failure information */ + + r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_NORMAL-3); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to adjust priority of exec_fd event source: %m"); + + (void) sd_event_source_set_description(source, "service event_fd"); + + r = sd_event_source_set_io_fd_own(source, true); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to pass ownership of fd to event source: %m"); + + *ret_event_source = TAKE_PTR(source); + return 0; +} + +static int service_allocate_exec_fd( + Service *s, + sd_event_source **ret_event_source, + int* ret_exec_fd) { + + _cleanup_close_pair_ int p[2] = { -1, -1 }; + int r; + + assert(s); + assert(ret_event_source); + assert(ret_exec_fd); + + if (pipe2(p, O_CLOEXEC|O_NONBLOCK) < 0) + return log_unit_error_errno(UNIT(s), errno, "Failed to allocate exec_fd pipe: %m"); + + r = service_allocate_exec_fd_event_source(s, p[0], ret_event_source); + if (r < 0) + return r; + + p[0] = -1; + *ret_exec_fd = TAKE_FD(p[1]); + + return 0; +} + static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) { assert(s); @@ -1325,9 +1389,12 @@ static int service_spawn( .stdin_fd = -1, .stdout_fd = -1, .stderr_fd = -1, + .exec_fd = -1, }; _cleanup_strv_free_ char **final_env = NULL, **our_env = NULL, **fd_names = NULL; - unsigned n_storage_fds = 0, n_socket_fds = 0, n_env = 0; + _cleanup_(sd_event_source_unrefp) sd_event_source *exec_fd_source = NULL; + size_t n_socket_fds = 0, n_storage_fds = 0, n_env = 0; + _cleanup_close_ int exec_fd = -1; _cleanup_free_ int *fds = NULL; pid_t pid; int r; @@ -1353,11 +1420,19 @@ static int service_spawn( s->exec_context.std_output == EXEC_OUTPUT_SOCKET || s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { - r = service_collect_fds(s, &fds, &fd_names, &n_storage_fds, &n_socket_fds); + r = service_collect_fds(s, &fds, &fd_names, &n_socket_fds, &n_storage_fds); if (r < 0) return r; - log_unit_debug(UNIT(s), "Passing %i fds to service", n_storage_fds + n_socket_fds); + log_unit_debug(UNIT(s), "Passing %zu fds to service", n_socket_fds + n_storage_fds); + } + + if (!FLAGS_SET(flags, EXEC_IS_CONTROL) && s->type == SERVICE_EXEC) { + assert(!s->exec_fd_event_source); + + r = service_allocate_exec_fd(s, &exec_fd_source, &exec_fd); + if (r < 0) + return r; } r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); @@ -1450,8 +1525,8 @@ static int service_spawn( exec_params.environment = final_env; exec_params.fds = fds; exec_params.fd_names = fd_names; - exec_params.n_storage_fds = n_storage_fds; exec_params.n_socket_fds = n_socket_fds; + exec_params.n_storage_fds = n_storage_fds; exec_params.watchdog_usec = s->watchdog_usec; exec_params.selinux_context_net = s->socket_fd_selinux_context_net; if (s->type == SERVICE_IDLE) @@ -1459,6 +1534,7 @@ static int service_spawn( exec_params.stdin_fd = s->stdin_fd; exec_params.stdout_fd = s->stdout_fd; exec_params.stderr_fd = s->stderr_fd; + exec_params.exec_fd = exec_fd; r = exec_spawn(UNIT(s), c, @@ -1470,6 +1546,9 @@ static int service_spawn( if (r < 0) return r; + s->exec_fd_event_source = TAKE_PTR(exec_fd_source); + s->exec_fd_hot = false; + r = unit_watch_pid(UNIT(s), pid); if (r < 0) /* FIXME: we need to do something here */ return r; @@ -1981,14 +2060,12 @@ static void service_enter_start(Service *s) { s->control_pid = pid; service_set_state(s, SERVICE_START); - } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { + } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_EXEC)) { - /* For oneshot services we wait until the start - * process exited, too, but it is our main process. */ + /* For oneshot services we wait until the start process exited, too, but it is our main process. */ - /* For D-Bus services we know the main pid right away, - * but wait for the bus name to appear on the - * bus. Notify services are similar. */ + /* For D-Bus services we know the main pid right away, but wait for the bus name to appear on the + * bus. 'notify' and 'exec' services are similar. */ service_set_main_pid(s, pid); service_set_state(s, SERVICE_START); @@ -2441,6 +2518,13 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (r < 0) return r; + if (s->exec_fd_event_source) { + r = unit_serialize_item_fd(u, f, fds, "exec-fd", sd_event_source_get_io_fd(s->exec_fd_event_source)); + if (r < 0) + return r; + unit_serialize_item(u, f, "exec-fd-hot", yes_no(s->exec_fd_hot)); + } + if (UNIT_ISSET(s->accept_socket)) { r = unit_serialize_item(u, f, "accept-socket", UNIT_DEREF(s->accept_socket)->id); if (r < 0) @@ -2774,6 +2858,18 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->stderr_fd = fdset_remove(fds, fd); s->exec_context.stdio_as_fds = true; } + } else if (streq(key, "exec-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse exec-fd value: %s", value); + else { + s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source); + + fd = fdset_remove(fds, fd); + if (service_allocate_exec_fd_event_source(s, fd, &s->exec_fd_event_source) < 0) + safe_close(fd); + } } else if (streq(key, "watchdog-override-usec")) { usec_t watchdog_override_usec; if (timestamp_deserialize(value, &watchdog_override_usec) < 0) @@ -2857,7 +2953,7 @@ static int service_watch_pid_file(Service *s) { log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); - r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io); + r = path_spec_watch(s->pid_file_pathspec, service_dispatch_inotify_io); if (r < 0) goto fail; @@ -2901,7 +2997,7 @@ static int service_demand_pid_file(Service *s) { return service_watch_pid_file(s); } -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { +static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { PathSpec *p = userdata; Service *s; @@ -2934,6 +3030,59 @@ fail: return 0; } +static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { + Service *s = SERVICE(userdata); + + assert(s); + + log_unit_debug(UNIT(s), "got exec-fd event"); + + /* If Type=exec is set, we'll consider a service started successfully the instant we invoked execve() + * successfully for it. We implement this through a pipe() towards the child, which the kernel automatically + * closes for us due to O_CLOEXEC on execve() in the child, which then triggers EOF on the pipe in the + * parent. We need to be careful however, as there are other reasons that we might cause the child's side of + * the pipe to be closed (for example, a simple exit()). To deal with that we'll ignore EOFs on the pipe unless + * the child signalled us first that it is about to call the execve(). It does so by sending us a simple + * non-zero byte via the pipe. We also provide the child with a way to inform us in case execve() failed: if it + * sends a zero byte we'll ignore POLLHUP on the fd again. */ + + for (;;) { + uint8_t x; + ssize_t n; + + n = read(fd, &x, sizeof(x)); + if (n < 0) { + if (errno == EAGAIN) /* O_NONBLOCK in effect → everything queued has now been processed. */ + return 0; + + return log_unit_error_errno(UNIT(s), errno, "Failed to read from exec_fd: %m"); + } + if (n == 0) { /* EOF → the event we are waiting for */ + + s->exec_fd_event_source = sd_event_source_unref(s->exec_fd_event_source); + + if (s->exec_fd_hot) { /* Did the child tell us to expect EOF now? */ + log_unit_debug(UNIT(s), "Got EOF on exec-fd"); + + s->exec_fd_hot = false; + + /* Nice! This is what we have been waiting for. Transition to next state. */ + if (s->type == SERVICE_EXEC && s->state == SERVICE_START) + service_enter_start_post(s); + } else + log_unit_debug(UNIT(s), "Got EOF on exec-fd while it was disabled, ignoring."); + + return 0; + } + + /* A byte was read → this turns on/off the exec fd logic */ + assert(n == sizeof(x)); + s->exec_fd_hot = x; + } + + return 0; +} + static void service_notify_cgroup_empty_event(Unit *u) { Service *s = SERVICE(u); @@ -3843,7 +3992,8 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = { [SERVICE_ONESHOT] = "oneshot", [SERVICE_DBUS] = "dbus", [SERVICE_NOTIFY] = "notify", - [SERVICE_IDLE] = "idle" + [SERVICE_IDLE] = "idle", + [SERVICE_EXEC] = "exec", }; DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); diff --git a/src/core/service.h b/src/core/service.h index 9c06e91883..b53e4204e7 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -30,6 +30,7 @@ typedef enum ServiceType { SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ + SERVICE_EXEC, /* we fork and wait until we execute exec() (this means our own setup is waited for) */ _SERVICE_TYPE_MAX, _SERVICE_TYPE_INVALID = -1 } ServiceType; @@ -165,6 +166,8 @@ struct Service { NotifyAccess notify_access; NotifyState notify_state; + sd_event_source *exec_fd_event_source; + ServiceFDStore *fd_store; size_t n_fd_store; unsigned n_fd_store_max; @@ -179,6 +182,7 @@ struct Service { unsigned n_restarts; bool flush_n_restarts; + bool exec_fd_hot; }; extern const UnitVTable service_vtable; diff --git a/src/core/socket.c b/src/core/socket.c index 56d32225c4..d488c64e91 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -1867,10 +1867,11 @@ static int socket_coldplug(Unit *u) { static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { ExecParameters exec_params = { - .flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, + .flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + .exec_fd = -1, }; pid_t pid; int r; diff --git a/src/core/swap.c b/src/core/swap.c index b78b1aa266..e01e61e56d 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -606,6 +606,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { .stdin_fd = -1, .stdout_fd = -1, .stderr_fd = -1, + .exec_fd = -1, }; pid_t pid; int r; diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index a87f4625ec..4412fe152f 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -949,7 +949,7 @@ static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) return -EINVAL; if (machine) { - if (!machine_name_is_valid(machine)) + if (!streq(machine, ".host") && !machine_name_is_valid(machine)) return -EINVAL; free_and_replace(b->machine, machine); @@ -1447,7 +1447,7 @@ _public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { assert_return(machine, -EINVAL); assert_return(ret, -EINVAL); - assert_return(machine_name_is_valid(machine), -EINVAL); + assert_return(streq(machine, ".host") || machine_name_is_valid(machine), -EINVAL); r = sd_bus_new(&b); if (r < 0) diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index 9dfba51cea..5940eccead 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -890,20 +890,27 @@ _public_ int sd_machine_get_class(const char *machine, char **class) { const char *p; int r; - assert_return(machine_name_is_valid(machine), -EINVAL); assert_return(class, -EINVAL); - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(NULL, p, NEWLINE, "CLASS", &c, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (!c) - return -EIO; + if (streq(machine, ".host")) { + c = strdup("host"); + if (!c) + return -ENOMEM; + } else { + if (!machine_name_is_valid(machine)) + return -EINVAL; + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(NULL, p, NEWLINE, "CLASS", &c, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (!c) + return -EIO; + } *class = TAKE_PTR(c); - return 0; } diff --git a/test/TEST-23-TYPE-EXEC/Makefile b/test/TEST-23-TYPE-EXEC/Makefile new file mode 100644 index 0000000000..34d7cc6cdf --- /dev/null +++ b/test/TEST-23-TYPE-EXEC/Makefile @@ -0,0 +1,4 @@ +BUILD_DIR=$(shell ../../tools/find-build-dir.sh) + +all setup clean run: + @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ diff --git a/test/TEST-23-TYPE-EXEC/test.sh b/test/TEST-23-TYPE-EXEC/test.sh new file mode 100755 index 0000000000..bdcea239a7 --- /dev/null +++ b/test/TEST-23-TYPE-EXEC/test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -e +TEST_DESCRIPTION="test Type=exec" + +. $TEST_BASE_DIR/test-functions + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service < /testok + +exit 0