]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
manager: make systemd+executor a multicall binary main
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 21 May 2026 08:08:33 +0000 (10:08 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 22 Jun 2026 15:19:54 +0000 (17:19 +0200)
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.

meson.build
meson_options.txt
src/core/execute.c
src/core/executor.c
src/core/executor.h [new file with mode: 0644]
src/core/main.c
src/core/manager.c
src/core/manager.h
src/core/meson.build

index f03032aa2ecc25cec277d7629876cecfc0cf4e7a..f953eeab13fd340e6bd6b7b3f44b02250bfa741e 100644 (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({
index b2fddf8a34a5d0736c743db49c05ad96e62cf7ad..701272d13a06d92f711dc6a947429c15e1bf6a87 100644 (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',
index 4a95473e46f4f0e47fcf23ac5c41e2d948058255..c2e8bc82b7d0fd532d55086acf008e7d50c6c096 100644 (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))),
index 00761c6e3f7a60367b028f6147ddb5a017870fe0..b227584789be2ce85941b128f176ace619016f7c 100644 (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
diff --git a/src/core/executor.h b/src/core/executor.h
new file mode 100644 (file)
index 0000000..75d2abe
--- /dev/null
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int run_executor(int argc, char *argv[]);
index de0916476fa8e691a7e6ff767c64492c7d0ba9be..6c83953f30e3ecfe1275e2e4b80801e3fbb3687c 100644 (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);
+}
index 1ee2228c8998cca997cdd84c95d11874c45000fd..d89b1eae0dc992ff1113f0dc3015b145e43c9181 100644 (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);
 }
index e655e168c60b4d90d75d94b95212998f8cf41b63..304124de04914305b26c998d2252c1018b98465d 100644 (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;
index af50a2a4fa7d975301eb0d0daf2032712f5b04a1..6e68a59d204db06454984bbd9afc6a17f16fdb60 100644 (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]]