manager: make systemd+executor a multicall binary

Allow systemd-executor to be compiled into a single binary.
The existing -Dlink-executor-shared=true|false is extended to also
allow -Dlink-executor-shared=single (*). The new mode is opt-in,
to allow experimentation and introduce this smoothly.

This saves a little space, but not as much as I expected:
$ ls -l build/{systemd,systemd-executor} build-new/systemd
-rwxr-xr-x 1 zbyszek zbyszek  631520 May 25 22:44 build/systemd
-rwxr-xr-x 1 zbyszek zbyszek  670464 May 25 22:44 build/systemd-executor
-rwxr-xr-x 1 zbyszek zbyszek 1214488 May 25 22:45 build-new/systemd
(This is with -Dbuildtype=debugoptimized -Db_lto=true).
The combined binary is slightly smaller than the sum of the separate
ones, but not much. In both cases, the binaries are linked to
libsystemd-core which is 10MB, so the size of the binaries themselves
doesn't make much of a difference. The executor needs exec-invoke.c
which is huge and not shared with anything else.

Longer term, I want to allow systemd to be linked statically. In
that case, having systemd-executor separate would be very painful.
So the option to use a multicall binary will be necessary.

Previously, we stored the resolved path to systemd-executor and
used it argv[0]. I don't think this was useful. After all, normally
we would use the non-resolved original path as argv[0]. So that
part is dropped, and the resolved path is only logged, but
"systemd-executor" is always used as argv[0]. This makes the
multicall binary work reliably, no matter what the actual file
name is.

(*) This means that compat as the commandline level is maintained:
'meson setup build -Dlink-executor-shared=true …' works as before.
Unfortunately, when using an existing build directory, meson chokes
on the type change and refuses to reconfigure the directory or change
the option or do anything useful. I think meson is DTWT here, but
this is hard to fix. So the build directory probably needs to be
recreated.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-06-22 17:19:54 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent 710d5aebbb
commit 0c6186695a
9 changed files with 123 additions and 53 deletions
+3
View File
@@ -78,6 +78,9 @@ conf.set10('FUZZ_USE_SIZE_LIMIT', fuzzer_build)
# We'll set this to '1' for EFI builds in a different place.
conf.set10('SD_BOOT', false)
link_executor_shared = get_option('link-executor-shared')
conf.set10('BUILD_EXECUTOR_SINGLE', link_executor_shared == 'single')
# Create a title-less summary section early, so it ends up first in the output.
# More items are added later after they have been detected.
summary({
+2 -2
View File
@@ -15,8 +15,8 @@ option('split-bin', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'sbin is not a symlink to bin')
option('link-udev-shared', type : 'boolean',
description : 'link systemd-udevd and its helpers to libsystemd-shared.so')
option('link-executor-shared', type : 'boolean',
description : 'link systemd-executor to libsystemd-shared.so and libsystemd-core.so')
option('link-executor-shared', type : 'combo', choices : ['true', 'false', 'single'],
description : 'link systemd-executor to libsystemd-shared.so and libsystemd-core.so, or into systemd')
option('link-systemctl-shared', type: 'boolean',
description : 'link systemctl against libsystemd-shared.so')
option('link-networkd-shared', type: 'boolean',
+1 -2
View File
@@ -479,7 +479,6 @@ int exec_spawn(
assert(unit);
assert(unit->manager);
assert(unit->manager->executor_fd >= 0);
assert(unit->manager->executor_path);
assert(command);
assert(context);
assert(params);
@@ -580,7 +579,7 @@ int exec_spawn(
/* The executor binary is pinned, to avoid compatibility problems during upgrades. */
r = posix_spawn_wrapper(
FORMAT_PROC_FD_PATH(unit->manager->executor_fd),
STRV_MAKE(unit->manager->executor_path,
STRV_MAKE("systemd-executor",
"--deserialize", serialization_fd_number,
"--log-level", max_log_levels,
"--log-target", log_target_to_string(manager_get_executor_log_target(unit->manager))),
+10 -3
View File
@@ -13,6 +13,7 @@
#include "exec-invoke.h"
#include "execute.h"
#include "execute-serialize.h"
#include "executor.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fdset.h"
@@ -38,7 +39,7 @@ static int help(void) {
if (r < 0)
return log_oom();
r = option_parser_get_help_table(&options);
r = option_parser_get_help_table_ns("systemd-executor", &options);
if (r < 0)
return r;
@@ -62,11 +63,13 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
OptionParser opts = { argc, argv };
OptionParser opts = { argc, argv, .namespace = "systemd-executor" };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_NAMESPACE("systemd-executor"): {}
OPTION_COMMON_HELP:
return help();
@@ -226,7 +229,7 @@ static int run(int argc, char *argv[]) {
return exit_status;
}
int main(int argc, char *argv[]) {
int run_executor(int argc, char *argv[]) {
int r;
/* We use safe_fork() for spawning sd-pam helper process, which internally calls rename_process().
@@ -241,3 +244,7 @@ int main(int argc, char *argv[]) {
return r < 0 ? EXIT_FAILURE : r;
}
#if !BUILD_EXECUTOR_SINGLE
_alias_(run_executor) main;
#endif
+3
View File
@@ -0,0 +1,3 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
int run_executor(int argc, char *argv[]);
+18 -3
View File
@@ -45,6 +45,7 @@
#include "emergency-action.h"
#include "env-util.h"
#include "escape.h"
#include "executor.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fdset.h"
@@ -1038,13 +1039,19 @@ static int parse_argv(int argc, char *argv[]) {
assert(argv);
int log_level_shift = getpid_cached() == 1 ? LOG_DEBUG - LOG_ERR : 0;
OptionParser opts = { argc, argv, .log_level_shift = log_level_shift };
OptionParser opts = {
argc, argv,
.namespace = "systemd",
.log_level_shift = log_level_shift,
};
/* Note: when new options are added here, also add them to the exclusion list in proc-cmdline.c! */
FOREACH_OPTION(c, &opts)
switch (c) {
OPTION_NAMESPACE("systemd"): {}
OPTION_COMMON_HELP:
arg_action = ACTION_HELP;
break;
@@ -1279,7 +1286,7 @@ static int help(void) {
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = option_parser_get_help_table(&options);
r = option_parser_get_help_table_ns("systemd", &options);
if (r < 0)
return r;
@@ -3430,7 +3437,7 @@ static int save_env(void) {
return 0;
}
int main(int argc, char *argv[]) {
static int run_systemd(int argc, char *argv[]) {
dual_timestamp
initrd_timestamp = DUAL_TIMESTAMP_NULL,
userspace_timestamp = DUAL_TIMESTAMP_NULL,
@@ -3911,3 +3918,11 @@ finish:
reset_arguments();
return retval;
}
int main(int argc, char *argv[]) {
#if BUILD_EXECUTOR_SINGLE
if (invoked_as(argv, "executor"))
return run_executor(argc, argv);
#endif
return run_systemd(argc, argv);
}
+27 -6
View File
@@ -895,6 +895,30 @@ usec_t manager_default_timeout(RuntimeScope scope) {
return scope == RUNTIME_SCOPE_SYSTEM ? DEFAULT_TIMEOUT_USEC : DEFAULT_USER_TIMEOUT_USEC;
}
static int pin_executor_binary(int *ret_fd) {
_cleanup_free_ char *path = NULL;
assert(ret_fd);
#if BUILD_EXECUTOR_SINGLE
int r;
r = open_and_check_executable("/proc/self/exe", /* root= */ NULL, &path, ret_fd);
if (r < 0)
return log_debug_errno(r, "Failed to pin executor binary %s: %m", "/proc/self/exe");
#else
int fd;
fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH, &path);
if (fd < 0)
return log_debug_errno(fd, "Failed to pin executor binary %s: %m", SYSTEMD_EXECUTOR_BINARY_PATH);
*ret_fd = fd;
#endif
log_debug("Using systemd-executor binary %s.", path);
return 0;
}
int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, Manager **ret) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -1057,11 +1081,9 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags,
}
if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) {
m->executor_fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH, &m->executor_path);
if (m->executor_fd < 0)
return log_debug_errno(m->executor_fd, "Failed to pin executor binary: %m");
log_debug("Using systemd-executor binary from '%s'.", m->executor_path);
r = pin_executor_binary(&m->executor_fd);
if (r < 0)
return r;
}
/* Note that we do not set up the notify fd here. We do that after deserialization,
@@ -1797,7 +1819,6 @@ Manager* manager_free(Manager *m) {
safe_close(m->restrict_fsaccess_bss_map_fd);
safe_close(m->executor_fd);
free(m->executor_path);
return mfree(m);
}
-1
View File
@@ -505,7 +505,6 @@ typedef struct Manager {
/* Pin the systemd-executor binary, so that it never changes until re-exec, ensuring we don't have
* serialization/deserialization compatibility issues during upgrades. */
char *executor_path;
int executor_fd;
unsigned soft_reboots_count;
+59 -36
View File
@@ -189,17 +189,38 @@ libcore = shared_library(
install : true,
install_dir : pkglibdir)
executor_libs = get_option('link-executor-shared') ? \
[
libcore,
libshared,
] : [
libc_wrapper_static,
libcore_static,
libshared_static,
libbasic_static,
libsystemd_static,
core_libs_static = [
libc_wrapper_static,
libcore_static,
libshared_static,
libbasic_static,
libsystemd_static,
]
core_libs_shared = [
libcore,
libshared,
]
systemd_deps = [
libapparmor_cflags,
libkmod_cflags,
libmount_cflags,
libseccomp_cflags,
libselinux_cflags,
]
link_executor_shared = get_option('link-executor-shared')
executor_libs = link_executor_shared == 'true' ? core_libs_shared : core_libs_static
if link_executor_shared == 'single'
systemd_sources += systemd_executor_sources
systemd_deps += [
libbpf_cflags,
libcryptsetup_cflags,
libpam_cflags,
]
endif
executables += [
libexec_template + {
@@ -207,33 +228,10 @@ executables += [
'dbus' : true,
'public' : true,
'sources' : systemd_sources,
'link_with' : [
libcore,
libshared,
],
'dependencies' : [
libapparmor_cflags,
libkmod_cflags,
libmount_cflags,
libseccomp_cflags,
libselinux_cflags,
],
},
libexec_template + {
'name' : 'systemd-executor',
'public' : true,
'sources' : systemd_executor_sources,
'link_with' : executor_libs,
'dependencies' : [
libapparmor_cflags,
libbpf_cflags,
libcryptsetup_cflags,
libmount_cflags,
libpam_cflags,
libseccomp_cflags,
libselinux_cflags,
],
'link_with' : core_libs_shared,
'dependencies' : systemd_deps,
},
fuzz_template + {
'sources' : files('fuzz-unit-file.c'),
'link_with' : [
@@ -258,6 +256,31 @@ executables += [
},
]
if link_executor_shared == 'single'
# Symlink for external callers
install_symlink('systemd-executor',
pointing_to : 'systemd',
install_dir : libexecdir)
else
executables += [
libexec_template + {
'name' : 'systemd-executor',
'public' : true,
'sources' : systemd_executor_sources,
'link_with' : executor_libs,
'dependencies' : [
libapparmor_cflags,
libbpf_cflags,
libcryptsetup_cflags,
libmount_cflags,
libpam_cflags,
libseccomp_cflags,
libselinux_cflags,
],
},
]
endif
in_files = [['system.conf', pkgconfigfiledir],
['user.conf', pkgconfigfiledir],
['org.freedesktop.systemd1.policy', polkitpolicydir]]