diff --git a/NEWS b/NEWS index ef3d070df1..80f83da243 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,15 @@ CHANGES WITH 262: have been provided. This clears the way for a new "systemd-sysupdate@.service" unit for varlink activation of sysupdate. + Changes in the system and service manager: + + * The manager now embeds a basic set of unit files (basic.target, + sysinit.target, multi-user.target, reboot.target, shutodown.target, + systemd-poweroff.service, etc.) and will use if files cannot be + loaded from disk. This makes the binary slightly more robust and + allows a container to be started with systemd as PID1 without + installing any unit files. + Changes in systemd-firstboot: * The "systemd.firstboot=" kernel command line option now accepts the diff --git a/README b/README index 4d03c8f012..da71c73be7 100644 --- a/README +++ b/README @@ -330,42 +330,49 @@ POLICY FOR SUPPORT OF DISTRIBUTIONS AND ARCHITECTURES: which support has been added. systemd will emit warnings when architecture-specific constants are not defined. -STATIC COMPILATION AND "STANDALONE" BINARIES: - systemd provides a public shared libraries libsystemd.so and - libudev.so. The latter is deprecated, and the sd-device APIs in - libsystemd should be used instead for new code. In addition, systemd is - built with a private shared library, libsystemd-shared-.so, - that also includes the libsystemd code, and by default most systemd - binaries are linked to it. Using shared libraries saves disk space and - memory at runtime, because only one copy of the code is needed. +STATIC LIBRARIES AND "STANDALONE" BINARIES: + systemd provides public shared libraries libsystemd.so and libudev.so. + The latter is deprecated, and the sd-device APIs in libsystemd should + be used instead for new code. Normally systemd is built with a private + shared library, libsystemd-shared-.so, that also includes the + libsystemd code, and by default most systemd binaries are linked to it. + Using shared libraries saves disk space and memory at runtime, because + only one copy of the code is needed. It is possible to build static versions of systemd public shared libraries (via the configuration options '-Dstatic-libsystemd' and '-Dstatic-libudev'). This allows the libsystemd and libudev code to be linked statically into programs. Note that mixing & matching different versions of libsystemd and systemd is generally not recommended, since - various of its APIs wrap internal state and protocols of systemd - (e.g. logind and udev databases), which are not considered - stable. Hence, using static libraries is not recommended since it - generally means that version of the static libsystemd linked into - applications and the host systemd are not in sync, and will thus create - compatibility problems. + various of its APIs wrap internal state and protocols of systemd (e.g. + logind and udev databases), which are not considered stable. Hence, + using static libraries is not recommended since it generally means that + version of the static libsystemd linked into applications and the host + systemd may get out of sync, creating compatibility problems. In addition, it is possible to disable the use of libsystemd-shared-.so for various components (via the configuration options '-Dlink-*-shared'). In this mode, the libsystemd - and libsystemd-shared code is linked statically into selected - binaries. This option is intended for systems where some of the - components are intended to be delivered independently of the main - systemd package. Finally, some binaries can be compiled in a second - version (via the configuration option '-Dstandalone-binaries'). The - version suffixed with ".standalone" has the libsystemd and - libsystemd-shared code linked statically. Those binaries are intended - as replacements to be used in limited installations where the full - systemd suite is not installed. Yet another option is to rebuild - systemd with a different '-Dshared-lib-tag' setting, allowing different - systemd binaries to be linked to instances of the private shared - library that can be installed in parallel. + and libsystemd-shared code is linked statically into selected binaries. + This option is intended for systems where some of the components are + intended to be delivered independently of the main systemd package. + + In addition, some binaries can be compiled in a second version (via the + configuration option '-Dstandalone-binaries'). The version suffixed + with ".standalone" has the libsystemd and libsystemd-shared code linked + statically. Those binaries are intended as replacements to be used in + limited installations where the full systemd suite is not installed. An + alternative approach is to rebuild systemd with a different + '-Dshared-lib-tag' setting, allowing different systemd binaries to be + linked to instances of the private shared library that can be installed + in parallel. + + When compiled in one of the modes described in this section, the code + has identical functionality (apart from the potential compatibility + issues). + + Finally, fully static compilation of systemd (the program) is possible, + see next section. Again: Using the default shared linking is recommended, building static or "standalone" versions is not. Mixing versions of systemd components @@ -374,6 +381,17 @@ STATIC COMPILATION AND "STANDALONE" BINARIES: combinations upstream and cannot provide support. Distributors making use of those options are responsible if things do not work as expected. +STATIC BINARIES: + It is possible to compile systemd (the program) as a static binary with + the meson options: + --default-library=static --prefer-static -Dbuild-static=true -Dsystemd-multicall-binary=true + This results in a static 'systemd' binary that can be used as PID1 in a + container. This binary does not use dlopen(3), which means that it + cannot dynamically extend its functionality by loading other libraries, + even if they were available in the file system. It also does not + support NSS and instead uses simplified code that only supports static + passwd and group files to resolve users. + USERS AND GROUPS: Default udev rules use the following standard system group names, which need to be resolvable by getgrnam() at any time, even in the very early diff --git a/TODO.md b/TODO.md index 8ef085f693..5537ba86aa 100644 --- a/TODO.md +++ b/TODO.md @@ -2436,6 +2436,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later operate in device mode - add NVMe authentication +- **sigpwr.target** doesn't do anything useful. Consider hooking it up to + poweroff.target. + +- Provide a fallback in **rescue.service** that prints a fixed message + if sulogin-shell could not be started. + - support boot into nvme-over-tcp: add generator that allows specifying nvme devices on kernel cmdline + credentials. Also maybe add interactive mode (where the user is prompted for nvme info), in order to boot from other diff --git a/man/custom-entities.ent.in b/man/custom-entities.ent.in index 4494a1c3ab..beff1529a7 100644 --- a/man/custom-entities.ent.in +++ b/man/custom-entities.ent.in @@ -15,6 +15,7 @@ + diff --git a/man/systemd.special.xml b/man/systemd.special.xml index f6f35b861d..deba4f8e4f 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -1,6 +1,9 @@ + "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ + +%entities; +]> @@ -239,6 +242,13 @@ 3, 5, …; see systemd1. + If default.target is not present on disk and no unit was requested on + the kernel command line, the default set at compile time will be used + (&FALLBACK_DEFAULT_TARGET;). + + If the default unit or one of the fallbacks cannot be started, systemd starts + rescue.target. + For typical unit files please set WantedBy= to a regular target (like multi-user.target or graphical.target), instead of default.target, since such a service will also be run on special diff --git a/meson.build b/meson.build index 20eba736d7..a5572301db 100644 --- a/meson.build +++ b/meson.build @@ -78,8 +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') +conf.set10('SYSTEMD_MULTICALL_BINARY', get_option('systemd-multicall-binary')) + +conf.set10('BUILD_STATIC', get_option('build-static')) # 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. @@ -725,6 +726,9 @@ if fallback_hostname == '' or fallback_hostname[0] == '.' or fallback_hostname[0 endif conf.set_quoted('FALLBACK_HOSTNAME', fallback_hostname) +fallback_default_target = get_option('fallback-default-target') +conf.set_quoted('FALLBACK_DEFAULT_TARGET', fallback_default_target) + extra_net_naming_schemes = [] extra_net_naming_map = [] foreach scheme: get_option('extra-net-naming-schemes').split(',') @@ -1971,6 +1975,9 @@ module_additional_kwargs = { ##################################################################### +# 'src/core' embeds a built-in copy of some unit files, so the unit file list +# from 'units' must be defined first. +subdir('units') # systemd-analyze requires 'libcore' subdir('src/core') # systemd-networkd requires 'libsystemd_network' @@ -2462,7 +2469,6 @@ subdir('shell-completion/zsh') subdir('sysctl.d') subdir('sysusers.d') subdir('tmpfiles.d') -subdir('units') install_subdir('factory/etc', install_dir : factorydir) @@ -2750,6 +2756,7 @@ summary({ 'nobody user name' : nobody_user, 'nobody group name' : nobody_group, 'fallback hostname' : get_option('fallback-hostname'), + 'fallback default target' : fallback_default_target, 'default compression method' : compression, 'default DNSSEC mode' : default_dnssec, 'default DNS-over-TLS mode' : default_dns_over_tls, diff --git a/meson_options.txt b/meson_options.txt index 701272d13a..9c0cda4c38 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -15,8 +15,10 @@ 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 : 'combo', choices : ['true', 'false', 'single'], - description : 'link systemd-executor to libsystemd-shared.so and libsystemd-core.so, or into systemd') +option('systemd-multicall-binary', type : 'boolean', value : false, + description : 'link systemd+systemd-executor as a single binary') +option('link-executor-shared', type : 'boolean', + description : 'link (separate) systemd-executor to libsystemd-shared.so and libsystemd-core.so') option('link-systemctl-shared', type: 'boolean', description : 'link systemctl against libsystemd-shared.so') option('link-networkd-shared', type: 'boolean', @@ -40,6 +42,8 @@ option('static-libudev', type : 'combo', description : 'install a static library for libudev') option('standalone-binaries', type : 'boolean', value : false, description : 'also build standalone versions of supported binaries') +option('build-static', type : 'boolean', value : false, + description : 'build static versions of supported binaries') option('sysvinit-path', type : 'string', value : '/etc/init.d', deprecated : true, description : 'This option is deprecated and will be removed in a future release') @@ -263,6 +267,8 @@ option('clock-valid-range-usec-max', type : 'integer', value : 473364000000000, description : 'maximum value in microseconds for the difference between RTC and epoch, exceeding which is considered an RTC error ["0" disables]') option('default-user-shell', type : 'string', value : '/bin/bash', description : 'default interactive shell') +option('fallback-default-target', type : 'string', value : 'graphical.target', + description : 'unit to activate when default.target is not found on disk') option('system-alloc-uid-min', type : 'integer', value : 0, description : 'minimum system UID used when allocating') diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c index 5c5177c3be..8704e0a6e3 100644 --- a/src/analyze/analyze-condition.c +++ b/src/analyze/analyze-condition.c @@ -109,7 +109,7 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit, if (r < 0) return log_error_errno(r, "Failed to prepare filename %s: %m", unit); - r = manager_load_startable_unit_or_warn(m, NULL, prepared, &u); + r = manager_load_startable_unit_or_warn(m, /* name= */ NULL, prepared, LOG_ERR, &u); if (r < 0) return r; } else { diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 6901956b8d..fbef7e3d2e 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -2792,7 +2792,7 @@ static int offline_security_checks( return log_error_errno(r, "Failed to copy: %m"); } - k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]); + k = manager_load_startable_unit_or_warn(m, /* name= */ NULL, prepared, LOG_ERR, &units[count]); if (k < 0) { RET_GATHER(r, k); continue; diff --git a/src/analyze/analyze-verify-util.c b/src/analyze/analyze-verify-util.c index ad553078d4..e2adf760a6 100644 --- a/src/analyze/analyze-verify-util.c +++ b/src/analyze/analyze-verify-util.c @@ -335,7 +335,7 @@ int verify_units( continue; } - k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]); + k = manager_load_startable_unit_or_warn(m, /* name= */ NULL, prepared, LOG_ERR, &units[count]); if (k < 0) { RET_GATHER(r, k); continue; diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c index 86ec2d28fd..fe2f6a5f88 100644 --- a/src/basic/dlfcn-util.c +++ b/src/basic/dlfcn-util.c @@ -101,6 +101,10 @@ void block_dlopen(void) { } int dlopen_safe(const char *filename, void **ret, const char **reterr_dlerror) { +#if BUILD_STATIC + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "dlopen is not supported in static builds, cannot load %s.", filename); +#else _cleanup_(dlclosep) void *dl = NULL; int r; @@ -142,4 +146,5 @@ int dlopen_safe(const char *filename, void **ret, const char **reterr_dlerror) { *ret = TAKE_PTR(dl); return 0; +#endif } diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 13537593cf..1fc8e67204 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -39,6 +39,7 @@ #include "strv.h" #include "sysctl-util.h" #include "tmpfile-util.h" +#include "user-util.h" #include "xattr-util.h" #if ENABLE_IDN @@ -914,7 +915,7 @@ int getpeergroups(int fd, gid_t **ret) { assert(fd >= 0); assert(ret); - long ngroups_max = sysconf(_SC_NGROUPS_MAX); + int ngroups_max = sysconf_ngroups_max(); if (ngroups_max > 0) n = MAX(n, sizeof(gid_t) * (socklen_t) ngroups_max); diff --git a/src/basic/special.h b/src/basic/special.h index a5cdfebae5..a5774f9590 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -110,6 +110,9 @@ #define SPECIAL_SIGPWR_TARGET "sigpwr.target" #define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" +/* The unit to use as fallback if ctrl-alt-del.target (which is usually an alias) cannot be loaded. */ +#define FALLBACK_CTRL_ALT_DEL_TARGET "reboot.target" + /* Where we add all our system units, users and machines by default */ #define SPECIAL_SYSTEM_SLICE "system.slice" #define SPECIAL_USER_SLICE "user.slice" diff --git a/src/basic/user-util.c b/src/basic/user-util.c index a665a2d780..fe4c0e6a36 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -1090,6 +1090,90 @@ const char* get_home_root(void) { return "/home"; } +static int copy_struct_passwd(const struct passwd *pw, struct passwd **ret) { + assert(pw); + assert(ret); + + size_t need_bytes = sizeof(struct passwd) + + strlen_ptr(pw->pw_name) + 1 + + strlen_ptr(pw->pw_passwd) + 1 + + strlen_ptr(pw->pw_gecos) + 1 + + strlen_ptr(pw->pw_dir) + 1 + + strlen_ptr(pw->pw_shell) + 1; + + char *buf = malloc(need_bytes); + if (!buf) + return -ENOMEM; + + struct passwd *newpw = (struct passwd *) buf; + + /* The layout in our buffer: + * struct passwd, and then individual strings. */ + char *p = buf + sizeof(struct passwd); + + newpw->pw_name = p; + p = stpcpy(p, strempty(pw->pw_name)) + 1; + + newpw->pw_passwd = p; + p = stpcpy(p, strempty(pw->pw_passwd)) + 1; + + newpw->pw_uid = pw->pw_uid; + newpw->pw_gid = pw->pw_gid; + + newpw->pw_gecos = p; + p = stpcpy(p, strempty(pw->pw_gecos)) + 1; + + newpw->pw_dir = p; + p = stpcpy(p, strempty(pw->pw_dir)) + 1; + + newpw->pw_shell = p; + p = stpcpy(p, strempty(pw->pw_shell)) + 1; + + *ret = newpw; + return 0; +} + +/* Iterate the given list of passwd-format files looking for an entry matching the predicate (by + * name if 'name' is non-NULL and by 'uid' if valid). Returns -ESRCH if no entry is found. */ +int lookup_pwent_in_files( + char * const *files, + const char *name, + uid_t uid, + struct passwd **ret) { + + int r; + + assert(files); + assert(name || uid_is_valid(uid)); + + STRV_FOREACH(fname, files) { + _cleanup_fclose_ FILE *f = NULL; + struct passwd *pw; + + r = fopen_unlocked(*fname, "re", &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + while ((r = fgetpwent_sane(f, &pw)) > 0) { + if (name && !streq_ptr(pw->pw_name, name)) + continue; + if (uid_is_valid(uid) && pw->pw_uid != uid) + continue; + if (ret) + return copy_struct_passwd(pw, ret); + return 0; + } + if (r < 0) + return r; + } + + return -ESRCH; +} + +#if !BUILD_STATIC + static size_t getpw_buffer_size(void) { long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); return bufsize <= 0 ? 4096U : (size_t) bufsize; @@ -1101,15 +1185,25 @@ static bool errno_is_user_doesnt_exist(int error) { return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM); } +#endif + int getpwnam_malloc(const char *name, struct passwd **ret) { + /* For static builds use a very simplistic implementation that looks at 'passwd' in /etc/ + * and /usr/lib/, and for non-static builds use NSS. The first matching entry wins. The + * same logic is implemented in getpwnam_malloc, getpwuid_malloc, getgrnam_malloc, and + * getgrgid_malloc. */ + + if (isempty(name)) + return -EINVAL; + +#if BUILD_STATIC + return lookup_pwent_in_files(PASSWD_FILES, name, UID_INVALID, ret); +#else size_t bufsize = getpw_buffer_size(); int r; /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must - * free() the returned structures! */ - - if (isempty(name)) - return -EINVAL; + * free() the returned structure! */ for (;;) { _cleanup_free_ void *buf = NULL; @@ -1145,15 +1239,19 @@ int getpwnam_malloc(const char *name, struct passwd **ret) { return -ENOMEM; bufsize *= 2; } +#endif } int getpwuid_malloc(uid_t uid, struct passwd **ret) { - size_t bufsize = getpw_buffer_size(); - int r; - if (!uid_is_valid(uid)) return -EINVAL; +#if BUILD_STATIC + return lookup_pwent_in_files(PASSWD_FILES, /* name= */ NULL, uid, ret); +#else + size_t bufsize = getpw_buffer_size(); + int r; + for (;;) { _cleanup_free_ void *buf = NULL; @@ -1186,20 +1284,230 @@ int getpwuid_malloc(uid_t uid, struct passwd **ret) { return -ENOMEM; bufsize *= 2; } +#endif } +static int copy_struct_group(const struct group *gr, struct group **ret) { + assert(gr); + assert(ret); + + size_t need_bytes = sizeof(struct group) + + strlen_ptr(gr->gr_name) + 1 + + strlen_ptr(gr->gr_passwd) + 1, + n_mem = 0; + STRV_FOREACH(s, gr->gr_mem) { + need_bytes += sizeof(char*) + strlen(*s) + 1; + n_mem++; + } + need_bytes += sizeof(char*); /* NULL terminator for gr_mem */ + + char *buf = malloc(need_bytes); + if (!buf) + return -ENOMEM; + + struct group *newgr = (struct group *) buf; + + /* The layout in our buffer: + * struct group, ->gr_mem pointers terminated by NULL, ->gr_name, ->gr_passwd, ->gr_mem items */ + /* The ->gr_mem array is first, because it needs alignment. */ + assert_cc(sizeof(struct group) % alignof(char*) == 0); + + char *p = buf + sizeof(struct group) + (n_mem + 1) * sizeof(char*); + + newgr->gr_name = p; + p = stpcpy(p, strempty(gr->gr_name)) + 1; + + newgr->gr_passwd = p; + p = stpcpy(p, strempty(gr->gr_passwd)) + 1; + + newgr->gr_gid = gr->gr_gid; + + newgr->gr_mem = (char**) (buf + sizeof(struct group)); + for (size_t i = 0; i < n_mem; i++) { + newgr->gr_mem[i] = p; + p = stpcpy(p, gr->gr_mem[i]) + 1; + } + newgr->gr_mem[n_mem] = NULL; + + *ret = newgr; + return 0; +} + +/* See lookup_pwent_in_files() for the analogous passwd-file version. */ +int lookup_grent_in_files( + char * const *files, + const char *name, + gid_t gid, + struct group **ret) { + + int r; + + assert(files); + assert(name || gid_is_valid(gid)); + + STRV_FOREACH(fname, files) { + _cleanup_fclose_ FILE *f = NULL; + struct group *gr; + + r = fopen_unlocked(*fname, "re", &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + while ((r = fgetgrent_sane(f, &gr)) > 0) { + if (name && !streq_ptr(gr->gr_name, name)) + continue; + if (gid_is_valid(gid) && gr->gr_gid != gid) + continue; + if (ret) + return copy_struct_group(gr, ret); + return 0; + } + if (r < 0) + return r; + } + + return -ESRCH; +} + +int sysconf_ngroups_max(void) { + /* Query sysconf _SC_NGROUPS_MAX. Returns an int because the expected value is 64k + * and later on this is used as an int with various glibc consumers. */ + + errno = 0; + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + if (ngroups_max <= 0) + return errno_or_else(EOPNOTSUPP); + if (ngroups_max > INT_MAX) + return -ERANGE; + return ngroups_max; +} + +ssize_t lookup_groups_in_files( + char * const *files, + const char *name, + gid_t gid, + gid_t **ret) { + + _cleanup_free_ gid_t *arr = NULL; + ssize_t n_arr = 0; + int r; + + assert(files); + assert(name); + assert(gid_is_valid(gid)); + + int ngroups_max = sysconf_ngroups_max(); + if (ngroups_max < 0) + return ngroups_max; + + if (!GREEDY_REALLOC(arr, n_arr + 1)) + return -ENOMEM; + arr[n_arr++] = gid; + + STRV_FOREACH(fname, files) { + _cleanup_fclose_ FILE *f = NULL; + struct group *gr; + + r = fopen_unlocked(*fname, "re", &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + while ((r = fgetgrent_sane(f, &gr)) > 0) { + if (gid == gr->gr_gid) + continue; + + /* Deduplicate groups, in case the group is doubly defined in the same or + * different files. */ + if (gid_list_has(arr, n_arr, gr->gr_gid)) + continue; + + if (!strv_contains(gr->gr_mem, name)) + continue; + + /* Do not allow more than _SC_NGROUPS_MAX. */ + if (n_arr >= ngroups_max) + return -ENOBUFS; + + if (!GREEDY_REALLOC(arr, n_arr + 1)) + return -ENOMEM; + arr[n_arr++] = gr->gr_gid; + } + if (r < 0) + return r; + } + + if (ret) + *ret = TAKE_PTR(arr); + return n_arr; +} + +int getgrouplist_malloc(const char *user, gid_t gid, gid_t **ret) { +#if BUILD_STATIC + return lookup_groups_in_files(GROUP_FILES, user, gid, ret); +#else + int ngroups_max = sysconf_ngroups_max(); + if (ngroups_max < 0) + return ngroups_max; + + _cleanup_free_ gid_t *gids = new(gid_t, ngroups_max); + if (!gids) + return -ENOMEM; + + int k = ngroups_max; + if (getgrouplist(user, gid, gids, &k) < 0) + return -EIO; /* getgrouplist does not set errno */ + + if (ret) + *ret = TAKE_PTR(gids); + return k; +#endif +} + +int initgroups_wrapper(const char *user, gid_t gid) { +#if BUILD_STATIC + _cleanup_free_ gid_t *groups = NULL; + int r, n_groups; + + n_groups = getgrouplist_malloc(user, gid, &groups); + if (n_groups <= 0) + return n_groups; + + /* Try to set the maximum number of groups the kernel can handle. This is what glibc does. */ + for (; n_groups > 0; n_groups--) { + r = RET_NERRNO(setgroups(n_groups, groups)); + if (r != -EINVAL) + break; + } + + return r; +#else + return RET_NERRNO(initgroups(user, gid)); +#endif +} + +#if !BUILD_STATIC + static size_t getgr_buffer_size(void) { long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); return bufsize <= 0 ? 4096U : (size_t) bufsize; } -int getgrnam_malloc(const char *name, struct group **ret) { - size_t bufsize = getgr_buffer_size(); - int r; +#endif +int getgrnam_malloc(const char *name, struct group **ret) { if (isempty(name)) return -EINVAL; +#if BUILD_STATIC + return lookup_grent_in_files(GROUP_FILES, name, GID_INVALID, ret); +#else + size_t bufsize = getgr_buffer_size(); + int r; + for (;;) { _cleanup_free_ void *buf = NULL; @@ -1232,15 +1540,19 @@ int getgrnam_malloc(const char *name, struct group **ret) { return -ENOMEM; bufsize *= 2; } +#endif } int getgrgid_malloc(gid_t gid, struct group **ret) { - size_t bufsize = getgr_buffer_size(); - int r; - if (!gid_is_valid(gid)) return -EINVAL; +#if BUILD_STATIC + return lookup_grent_in_files(GROUP_FILES, /* name= */ NULL, gid, ret); +#else + size_t bufsize = getgr_buffer_size(); + int r; + for (;;) { _cleanup_free_ void *buf = NULL; @@ -1273,4 +1585,5 @@ int getgrgid_malloc(gid_t gid, struct group **ret) { return -ENOMEM; bufsize *= 2; } +#endif } diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 641bdc06b0..ab9222c500 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -159,6 +159,10 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { return password && password[0] != '$'; } +/* Places where we will try to load account data from */ +#define PASSWD_FILES STRV_MAKE("/etc/passwd", "/usr/lib/passwd") +#define GROUP_FILES STRV_MAKE("/etc/group", "/usr/lib/group") + /* A locked *and* invalid password for "struct spwd"'s .sp_pwdp and "struct passwd"'s .pw_passwd field */ #define PASSWORD_LOCKED_AND_INVALID "!*" @@ -173,6 +177,27 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { */ #define PASSWORD_UNPROVISIONED "!unprovisioned" +int sysconf_ngroups_max(void); + +int lookup_pwent_in_files( + char * const *files, + const char *name, + uid_t uid, + struct passwd **ret); +int lookup_grent_in_files( + char * const *files, + const char *name, + gid_t gid, + struct group **ret); + +ssize_t lookup_groups_in_files( + char * const *files, + const char *name, + gid_t gid, + gid_t **ret); +int getgrouplist_malloc(const char *user, gid_t gid, gid_t **ret); +int initgroups_wrapper(const char *user, gid_t gid); + int getpwuid_malloc(uid_t uid, struct passwd **ret); int getpwnam_malloc(const char *name, struct passwd **ret); diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index b9a5c66ebf..181da27f7a 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -113,7 +113,7 @@ void emergency_action( log_and_status(m, action, flags, "Rebooting", reason); (void) update_reboot_parameter_and_warn(reboot_arg, true); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; case EMERGENCY_ACTION_REBOOT_FORCE: @@ -141,7 +141,7 @@ void emergency_action( case EMERGENCY_ACTION_SOFT_REBOOT: log_and_status(m, action, flags, "Soft-rebooting", reason); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_SOFT_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; case EMERGENCY_ACTION_SOFT_REBOOT_FORCE: @@ -157,7 +157,7 @@ void emergency_action( if (MANAGER_IS_USER(m) || detect_container() > 0) { log_and_status(m, action, flags, "Exiting", reason); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_EXIT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_EXIT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; } @@ -166,7 +166,7 @@ void emergency_action( case EMERGENCY_ACTION_POWEROFF: log_and_status(m, action, flags, "Powering off", reason); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; case EMERGENCY_ACTION_EXIT_FORCE: @@ -199,7 +199,7 @@ void emergency_action( case EMERGENCY_ACTION_KEXEC: log_and_status(m, action, flags, "Executing kexec", reason); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_KEXEC_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_KEXEC_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; case EMERGENCY_ACTION_KEXEC_FORCE: @@ -209,7 +209,7 @@ void emergency_action( case EMERGENCY_ACTION_HALT: log_and_status(m, action, flags, "Halting", reason); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_HALT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); + (void) manager_add_job_by_name_or_warn(m, JOB_START, SPECIAL_HALT_TARGET, JOB_REPLACE_IRREVERSIBLY, NULL, NULL); break; case EMERGENCY_ACTION_HALT_FORCE: diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 74602efc52..7a7f7f2a70 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -890,7 +890,7 @@ static int get_supplementary_groups( assert(ret_gids); /* - * If user is given, then lookup GID and supplementary groups list. + * If user is given, then look up GID and supplementary groups list. * We avoid NSS lookups for gid=0. Also we have to initialize groups * here and as early as possible so we keep the list of supplementary * groups of the caller. @@ -898,15 +898,16 @@ static int get_supplementary_groups( bool keep_groups = false; if (user && gid_is_valid(gid) && gid != 0) { /* First step, initialize groups from /etc/groups */ - if (initgroups(user, gid) < 0) { + r = initgroups_wrapper(user, gid); + if (r < 0) { /* If our primary gid is already the one specified in Group= (i.e. we're running in * user mode), gracefully handle the case where we have no privilege to re-initgroups(). * * Note that group memberships of the current user might have been modified, but * the change will only take effect after re-login. It's better to continue on with * existing credentials rather than erroring out. */ - if (!ERRNO_IS_PRIVILEGE(errno) || gid != getgid()) - return -errno; + if (!ERRNO_IS_PRIVILEGE(r) || gid != getgid()) + return r; } keep_groups = true; @@ -917,34 +918,30 @@ static int get_supplementary_groups( return 0; } - /* - * If SupplementaryGroups= was passed then NGROUPS_MAX has to - * be positive, otherwise fail. - */ - errno = 0; - long ngroups_max = sysconf(_SC_NGROUPS_MAX); - if (ngroups_max <= 0) - return errno_or_else(EOPNOTSUPP); - - _cleanup_free_ gid_t *l_gids = new(gid_t, ngroups_max); - if (!l_gids) - return -ENOMEM; + /* If SupplementaryGroups= was passed then NGROUPS_MAX has to be positive, otherwise fail. */ + _cleanup_free_ gid_t *l_gids = NULL; int k = 0; if (keep_groups) { - /* - * Lookup the list of groups that the user belongs to, we - * avoid NSS lookups here too for gid=0. - */ - k = ngroups_max; - if (getgrouplist(user, gid, l_gids, &k) < 0) - return -EINVAL; + /* Look up the list of groups that the user belongs to. + * We avoid NSS lookups here too for gid=0. */ + + k = getgrouplist_malloc(user, gid, &l_gids); + if (k < 0) + return k; } + int ngroups_max = sysconf_ngroups_max(); + if (ngroups_max < 0) + return ngroups_max; + STRV_FOREACH(i, c->supplementary_groups) { if (k >= ngroups_max) return -E2BIG; + if (!GREEDY_REALLOC(l_gids, k + 1)) + return -ENOMEM; + r = get_group_creds(*i, /* flags= */ 0, /* ret_name= */ NULL, l_gids + k); if (r < 0) return r; @@ -957,12 +954,11 @@ static int get_supplementary_groups( return 0; } - /* Otherwise get the final list of supplementary groups */ - gid_t *groups = newdup(gid_t, l_gids, k); - if (!groups) - return -ENOMEM; + /* We *could* trim the array size with realloc(3), but right now the only caller frees the array + * quickly anyway, so this is not worth the trouble. If other users pop up, this should be + * reconsidered. */ - *ret_gids = groups; + *ret_gids = TAKE_PTR(l_gids); return k; } diff --git a/src/core/executor.c b/src/core/executor.c index b227584789..e771bdc024 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -245,6 +245,6 @@ int run_executor(int argc, char *argv[]) { return r < 0 ? EXIT_FAILURE : r; } -#if !BUILD_EXECUTOR_SINGLE +#if !SYSTEMD_MULTICALL_BINARY _alias_(run_executor) main; #endif diff --git a/src/core/generate-builtin-units.py b/src/core/generate-builtin-units.py new file mode 100755 index 0000000000..6850a9e59b --- /dev/null +++ b/src/core/generate-builtin-units.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Embed the contents of unit files passed on the command line into a C array +# initializer, so that the manager can fall back to a built-in copy when no +# unit file is found on disk. Comment and empty lines are stripped to keep the +# embedded strings small. The unit name is derived from each file's basename. + +import pathlib +import string +import sys + +PRINTABLE = set(string.printable) - set('\t\r\x0b\x0c') + + +def strip_comments(text: str) -> str: + # Unit files use '#' and ';' as comment markers at the start of a line. + # fmt: off + lines = (line for line in text.splitlines() + if line and line[0] not in '#;') + return '\n'.join(lines) + '\n' + + +def c_escape(text: str) -> str: + assert set(text) <= PRINTABLE + return text.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') + + +for path in sys.argv[1:]: + path = pathlib.Path(path) + data = strip_comments(path.read_text()) + print(f'{{ "{path.name}", "{c_escape(data)}" }},') diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ce4517e85b..c9e270a668 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -30,6 +30,7 @@ #include "execute.h" #include "extract-word.h" #include "fd-util.h" +#include "fileio.h" #include "fstab-util.h" #include "hashmap.h" #include "hexdecoct.h" @@ -73,6 +74,16 @@ #include "user-util.h" #include "web-util.h" +/* Built-in copies of a few essential unit files, embedded at build time. They are used as a fallback + * when no fragment for the unit is found on disk, so that the manager can reach a usable state even on + * a system that ships none of these unit files. */ +static const struct { + const char *name; + const char *data; +} builtin_units[] = { +# include "builtin-units.inc" +}; + static int parse_socket_protocol(const char *s) { int r; @@ -6171,6 +6182,16 @@ static int merge_by_names(Unit *u, Set *names, const char *id) { return 0; } +static const char* builtin_unit_lookup(const char *name) { + assert(name); + + FOREACH_ELEMENT(i, builtin_units) + if (streq(i->name, name)) + return i->data; + + return NULL; +} + int unit_load_fragment(Unit *u) { int r; @@ -6252,14 +6273,44 @@ int unit_load_fragment(Unit *u) { r = config_parse(u->id, fragment, f, UNIT_VTABLE(u)->sections, config_item_perf_lookup, load_fragment_gperf_lookup, - 0, - u, - NULL); + /* flags= */ 0, + /* userdata= */ u, + /* ret_stat= */ NULL); if (r == -ENOEXEC) log_unit_notice_errno(u, r, "Unit configuration has fatal error, unit will not be started."); if (r < 0) return r; } + } else if (u->manager->runtime_scope == RUNTIME_SCOPE_SYSTEM) { + /* No fragment found on disk. For system units, fall back to a built-in copy if we have one + * embedded. This way the manager can reach a usable state even if none of these unit files + * are installed. On-disk files always take precedence (including masks), since we only get + * here when nothing was found in the lookup paths. */ + + const char *data = builtin_unit_lookup(u->id); + if (data) { + _cleanup_fclose_ FILE *f = NULL; + + f = fmemopen_unlocked((void*) data, strlen(data), "re"); + if (!f) + return log_oom(); + + log_unit_debug(u, "Loading built-in fragment for %s.", u->id); + + u->load_state = UNIT_LOADED; + u->fragment_mtime = 0; + + r = config_parse(u->id, u->id, f, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + /* flags= */ 0, + /* userdata= */ u, + /* ret_stat= */ NULL); + if (r == -ENOEXEC) + log_unit_notice_errno(u, r, "Built-in unit configuration has fatal error, unit will not be started."); + if (r < 0) + return r; + } } /* Call merge_by_names with the name derived from the fragment path as the preferred name. diff --git a/src/core/main.c b/src/core/main.c index 01911630dc..5c02e30880 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -52,6 +52,7 @@ #include "fileio.h" #include "format-table.h" #include "format-util.h" +#include "fs-util.h" #include "glyph-util.h" #include "hash-funcs.h" #include "hashmap.h" @@ -94,6 +95,7 @@ #include "proc-cmdline.h" #include "process-util.h" #include "random-util.h" +#include "reboot-util.h" #include "rlimit-util.h" #include "rm-rf.h" #include "seccomp-util.h" @@ -1860,6 +1862,52 @@ static int become_shutdown(int objective, int retval) { return -errno; } +static int fallback_shutdown(ManagerObjective objective, int retval) { + int cmd; + + /* Perform the requested shutdown operation ourselves, by issuing the matching reboot() directly. + * This skips the cleanup systemd-shutdown normally does (unmounting file systems, detaching storage, + * killing remaining processes, switching into the exitrd). */ + + switch (objective) { + + case MANAGER_KEXEC: + log_info("Rebooting with kexec."); + (void) kexec(); + /* If we are still here kexec didn't work, fall back to a plain reboot. */ + _fallthrough_; + + case MANAGER_REBOOT: + (void) reboot_with_parameter(REBOOT_LOG); + log_info("Rebooting."); + cmd = RB_AUTOBOOT; + break; + + case MANAGER_EXIT: + if (detect_container() > 0) { + log_info("Exiting container."); + exit(retval); + } + _fallthrough_; + + case MANAGER_POWEROFF: + log_info("Powering off."); + cmd = RB_POWER_OFF; + break; + + case MANAGER_HALT: + log_info("Halting system."); + cmd = RB_HALT_SYSTEM; + break; + + default: + assert_not_reached(); + } + + (void) reboot(cmd); + return log_error_errno(errno, "Failed to shut down: %m"); +} + static void initialize_clock_timewarp(void) { int r; @@ -2130,6 +2178,7 @@ static int do_reexecute( const char **ret_error_message) { size_t i, args_size; + _cleanup_free_ char *our_exe = NULL; const char **args; int r; @@ -2240,14 +2289,32 @@ static int do_reexecute( valgrind_summary_hack(); args[0] = SYSTEMD_BINARY_PATH; - (void) execv(args[0], (char* const*) args); + r = RET_NERRNO(execv(args[0], (char* const*) args)); if (objective == MANAGER_REEXECUTE) { + /* If the binary is missing, try with our current executable too. This way things + * continue to work if the binary is installed into a non-default location. Note that + * we don't use chase here, the symlink the kernel gives is good enough. */ + if (r == -ENOENT) { + int k; + + k = readlink_malloc("/proc/self/exe", &our_exe); + if (k < 0) + log_warning_errno(k, "Failed to read /proc/self/exe: %m"); + else if (endswith(our_exe, " (deleted)")) + log_info("The current binary (/proc/self/exe) has been deleted."); + else { + log_info_errno(r, "Failed to execute our own binary %s, trying /proc/self/exe: %m", args[0]); + args[0] = "/proc/self/exe"; + r = RET_NERRNO(execv(args[0], (char* const*) args)); + } + } + *ret_error_message = "Failed to execute our own binary"; - return log_error_errno(errno, "Failed to execute our own binary %s: %m", args[0]); + return log_error_errno(r, "Failed to execute our own binary %s: %m", args[0]); } - log_debug_errno(errno, "Failed to execute our own binary %s, trying fallback: %m", args[0]); + log_debug_errno(r, "Failed to execute our own binary %s, trying fallback: %m", args[0]); } /* Try the fallback, if there is any, without any serialization. We pass the original argv[] and @@ -2283,13 +2350,12 @@ static int do_reexecute( if (switch_root_init) { args[0] = switch_root_init; - (void) execve(args[0], (char* const*) args, saved_env); - log_warning_errno(errno, "Failed to execute configured init %s, trying fallback: %m", args[0]); + r = RET_NERRNO(execve(args[0], (char* const*) args, saved_env)); + log_warning_errno(r, "Failed to execute configured init %s, trying fallback: %m", args[0]); } args[0] = "/sbin/init"; - (void) execv(args[0], (char* const*) args); - r = -errno; + r = RET_NERRNO(execv(args[0], (char* const*) args)); *ret_error_message = "Failed to execute /sbin/init"; if (r == -ENOENT) { @@ -2301,8 +2367,7 @@ static int do_reexecute( args[0] = "/bin/sh"; args[1] = NULL; - (void) execve(args[0], (char* const*) args, saved_env); - r = -errno; + r = RET_NERRNO(execve(args[0], (char* const*) args, saved_env)); *ret_error_message = "Failed to execute fallback shell"; } @@ -2765,21 +2830,31 @@ static int do_queue_default_job( else unit = SPECIAL_DEFAULT_TARGET; + /* When no unit was explicitly requested, failures to load the default unit are not fatal, + * since we fall back to other targets below. Log them at a lower level in that case. */ + int log_level = arg_default_unit ? LOG_ERR : LOG_INFO; + log_debug("Activating default unit: %s", unit); + r = manager_load_startable_unit_or_warn(m, unit, /* path= */ NULL, log_level, &target); + if (r == -ENOENT && !arg_default_unit) { + if (in_initrd()) + /* Fall back to default.target, which we used to always use by default. + * Only do this if no explicit configuration was given. */ + unit = SPECIAL_DEFAULT_TARGET; + else + /* The default.target symlink was not found on disk and the target was not + * explicitly specified. Fall back to the target configured at build time + * via -Ddefault-target=. */ + unit = FALLBACK_DEFAULT_TARGET; - r = manager_load_startable_unit_or_warn(m, unit, NULL, &target); - if (r < 0 && in_initrd() && !arg_default_unit) { - /* Fall back to default.target, which we used to always use by default. Only do this if no - * explicit configuration was given. */ - - log_info("Falling back to %s.", SPECIAL_DEFAULT_TARGET); - - r = manager_load_startable_unit_or_warn(m, SPECIAL_DEFAULT_TARGET, NULL, &target); + log_info("Falling back to %s.", unit); + r = manager_load_startable_unit_or_warn(m, unit, /* path= */ NULL, log_level, &target); } if (r < 0) { + /* We failed. Activate rescue mode. */ log_info("Falling back to %s.", SPECIAL_RESCUE_TARGET); - r = manager_load_startable_unit_or_warn(m, SPECIAL_RESCUE_TARGET, NULL, &target); + r = manager_load_startable_unit_or_warn(m, SPECIAL_RESCUE_TARGET, /* path= */ NULL, log_level, &target); if (r < 0) { *ret_error_message = r == -ERFKILL ? SPECIAL_RESCUE_TARGET " masked" : "Failed to load " SPECIAL_RESCUE_TARGET; @@ -3906,9 +3981,20 @@ finish: * If we failed above, we want to freeze after finishing cleanup. */ if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && IN_SET(r, MANAGER_EXIT, MANAGER_REBOOT, MANAGER_POWEROFF, MANAGER_HALT, MANAGER_KEXEC)) { - r = become_shutdown(r, retval); - log_error_errno(r, "Failed to execute shutdown binary, %s: %m", getpid_cached() == 1 ? "freezing" : "quitting"); - error_message = "Failed to execute shutdown binary"; + ManagerObjective objective = r; + + r = become_shutdown(objective, retval); + assert(r < 0); + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to execute shutdown binary: %m"); + + /* As PID 1, carry out the requested operation ourselves rather than relying on the shutdown + * binary. This only returns if that didn't work, in which case we freeze/exit/reboot + * below. */ + r = fallback_shutdown(objective, retval); + assert(r < 0); + log_error_errno(r, "Failed to perform shutdown: %m"); + error_message = "Failed to perform shutdown"; } /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with @@ -3932,7 +4018,7 @@ finish: } int main(int argc, char *argv[]) { -#if BUILD_EXECUTOR_SINGLE +#if SYSTEMD_MULTICALL_BINARY if (invoked_as(argv, "executor")) return run_executor(argc, argv); #endif diff --git a/src/core/manager.c b/src/core/manager.c index eedb5e710d..ae44f87b2a 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -900,7 +900,7 @@ static int pin_executor_binary(int *ret_fd) { assert(ret_fd); -#if BUILD_EXECUTOR_SINGLE +#if SYSTEMD_MULTICALL_BINARY int r; r = open_and_check_executable("/proc/self/exe", /* root= */ NULL, &path, ret_fd); @@ -2527,22 +2527,36 @@ int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode return manager_add_job_full(m, type, unit, mode, /* extra_flags= */ 0, affected_jobs, e, ret); } -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, Job **ret) { +static int manager_add_job_by_name_or_warn_with_fallback( + Manager *m, + JobType type, + const char *name, + const char *fallback, + JobMode mode, + Set *affected_jobs, + Job **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - r = manager_add_job_by_name(m, type, name, mode, affected_jobs, &error, ret); + if (r == -ENOENT && fallback) { + log_debug_errno(r, "Failed to enqueue job %s/%s, fallback to %s: %s", + name, job_mode_to_string(mode), fallback, bus_error_message(&error, r)); + sd_bus_error_free(&error); + name = fallback; + r = manager_add_job_by_name(m, type, name, mode, affected_jobs, &error, ret); + } if (r < 0) - return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r)); - + return log_warning_errno(r, "Failed to enqueue job %s/%s: %s", + name, job_mode_to_string(mode), bus_error_message(&error, r)); return r; } +int manager_add_job_by_name_or_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, Job **ret) { + return manager_add_job_by_name_or_warn_with_fallback(m, type, name, /* fallback= */ NULL, mode, affected_jobs, ret); +} + int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error *e) { _cleanup_(transaction_abort_and_freep) Transaction *tr = NULL; int r; @@ -2788,6 +2802,7 @@ int manager_load_startable_unit_or_warn( Manager *m, const char *name, const char *path, + int log_level, Unit **ret) { /* Load a unit, make sure it loaded fully and is not masked. */ @@ -2800,13 +2815,13 @@ int manager_load_startable_unit_or_warn( r = manager_load_unit(m, name, path, &error, &unit); if (r < 0) - return log_error_errno(r, "Failed to load %s %s: %s", - name ? "unit" : "unit file", name ?: path, - bus_error_message(&error, r)); + return log_full_errno(log_level, r, "Failed to load %s %s: %s", + name ? "unit" : "unit file", name ?: path, + bus_error_message(&error, r)); r = bus_unit_validate_load_state(unit, &error); if (r < 0) - return log_error_errno(r, "%s", bus_error_message(&error, r)); + return log_full_errno(log_level, r, "%s", bus_error_message(&error, r)); *ret = unit; return 0; @@ -3205,10 +3220,10 @@ turn_off: return 0; } -static void manager_start_special(Manager *m, const char *name, JobMode mode) { +static void manager_start_special_with_fallback(Manager *m, const char *name, const char *fallback, JobMode mode) { Job *job; - if (manager_add_job_by_name_and_warn(m, JOB_START, name, mode, NULL, &job) < 0) + if (manager_add_job_by_name_or_warn_with_fallback(m, JOB_START, name, fallback, mode, /* affected_jobs= */ NULL, &job) < 0) return; const char *s = unit_status_string(job->unit, NULL); @@ -3220,6 +3235,10 @@ static void manager_start_special(Manager *m, const char *name, JobMode mode) { m->status_ready = false; } +static void manager_start_special(Manager *m, const char *name, JobMode mode) { + manager_start_special_with_fallback(m, name, /* fallback= */ NULL, mode); +} + static void manager_handle_ctrl_alt_del(Manager *m) { assert(m); @@ -3227,7 +3246,11 @@ static void manager_handle_ctrl_alt_del(Manager *m) { * unless it was disabled in system.conf. */ if (ratelimit_below(&m->ctrl_alt_del_ratelimit) || m->cad_burst_action == EMERGENCY_ACTION_NONE) - manager_start_special(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); + manager_start_special_with_fallback( + m, + SPECIAL_CTRL_ALT_DEL_TARGET, + FALLBACK_CTRL_ALT_DEL_TARGET, + JOB_REPLACE_IRREVERSIBLY); else emergency_action( m, diff --git a/src/core/manager.h b/src/core/manager.h index 0a0974bf1c..8262bfbec8 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -592,7 +592,7 @@ bool manager_unit_cache_should_retry_load(Unit *u); int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); int manager_dispatch_external_fd_to_unit(Manager *m, const char *unit_id, const char *fdname, uint64_t index, int fd, const char *log_context); -int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, Unit **ret); +int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, int log_level, Unit **ret); int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); int manager_add_jobs( @@ -624,7 +624,7 @@ int manager_add_job( Job **ret); int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, sd_bus_error *e, Job **ret); -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, Job **ret); +int manager_add_job_by_name_or_warn(Manager *m, JobType type, const char *name, JobMode mode, Set *affected_jobs, Job **ret); int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error *e); void manager_clear_jobs(Manager *m); diff --git a/src/core/meson.build b/src/core/meson.build index 6e68a59d20..26c84fa78b 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -135,6 +135,13 @@ load_fragment_gperf_nulstr_c = custom_target( command : [awk, '-f', '@INPUT0@', '@INPUT1@'], capture : true) +generate_builtin_units = files('generate-builtin-units.py') +builtin_units_inc = custom_target( + input : [generate_builtin_units, embedded_units], + output : 'builtin-units.inc', + command : [python, generate_builtin_units, embedded_units], + capture : true) + generate_bpf_delegate_configs = files('generate-bpf-delegate-configs.py') bpf_delegate_configs_inc = custom_target( input : [generate_bpf_delegate_configs, bpf_delegate_sources], @@ -155,8 +162,8 @@ bpf_delegate_xml = custom_target( capture : true) man_page_depends += bpf_delegate_xml -generated_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc] -libcore_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc] +generated_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc, builtin_units_inc] +libcore_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc, builtin_units_inc] libcore_build_dir = meson.current_build_dir() libcore_name = 'systemd-core-@0@'.format(shared_lib_tag) core_includes = [include_directories('.'), includes] @@ -209,11 +216,12 @@ systemd_deps = [ libselinux_cflags, ] -link_executor_shared = get_option('link-executor-shared') +systemd_link_args = get_option('build-static') ? ['-static'] : [] +systemd_libs = get_option('build-static') ? core_libs_static : core_libs_shared -executor_libs = link_executor_shared == 'true' ? core_libs_shared : core_libs_static +executor_libs = get_option('link-executor-shared') ? core_libs_shared : core_libs_static -if link_executor_shared == 'single' +if conf.get('SYSTEMD_MULTICALL_BINARY') == 1 systemd_sources += systemd_executor_sources systemd_deps += [ libbpf_cflags, @@ -228,7 +236,8 @@ executables += [ 'dbus' : true, 'public' : true, 'sources' : systemd_sources, - 'link_with' : core_libs_shared, + 'link_args' : systemd_link_args, + 'link_with' : systemd_libs, 'dependencies' : systemd_deps, }, @@ -256,7 +265,7 @@ executables += [ }, ] -if link_executor_shared == 'single' +if conf.get('SYSTEMD_MULTICALL_BINARY') == 1 # Symlink for external callers install_symlink('systemd-executor', pointing_to : 'systemd', diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index e44c439fad..2bd0439c59 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -827,6 +827,10 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) { } static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { +#if BUILD_STATIC + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "tcp:// connections are not supported in static builds"); +#else _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; int r; struct addrinfo *result, hints = { @@ -889,6 +893,7 @@ static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { b->is_local = false; return 0; +#endif } static int parse_exec_address(sd_bus *b, const char **p, char **guid) { diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index f28fc2d758..e1bf71bc37 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -768,6 +768,7 @@ static int mangle_uid_into_key( uid_t uid, uint8_t md[static SHA256_DIGEST_LENGTH]) { + _cleanup_free_ struct passwd *pw = NULL; sd_id128_t mid; int r; @@ -778,12 +779,9 @@ static int mangle_uid_into_key( * (specifically, UID, user name, machine ID) with the key we'd otherwise use for system credentials, * and use the resulting hash as actual encryption key. */ - errno = 0; - struct passwd *pw = getpwuid(uid); - if (!pw) - return log_error_errno( - IN_SET(errno, 0, ENOENT) ? SYNTHETIC_ERRNO(ESRCH) : errno, - "Failed to resolve UID " UID_FMT ": %m", uid); + r = getpwuid_malloc(uid, &pw); + if (r < 0) + return log_error_errno(r, "Failed to resolve UID "UID_FMT": %m", uid); r = sd_id128_get_machine(&mid); if (r < 0) diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index 9673415080..adc2f4747f 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -54,13 +54,13 @@ TEST_RET(cgroup_mask, .sd_booted = true) { assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* Load units and verify hierarchy. */ - ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "son.service", NULL, &son)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "daughter.service", NULL, &daughter)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "grandchild.service", NULL, &grandchild)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, &parent_deep)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, &nomem_parent)); - ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, &nomem_leaf)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, LOG_ERR, &parent)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "son.service", NULL, LOG_ERR, &son)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "daughter.service", NULL, LOG_ERR, &daughter)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "grandchild.service", NULL, LOG_ERR, &grandchild)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, LOG_ERR, &parent_deep)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, LOG_ERR, &nomem_parent)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, LOG_ERR, &nomem_leaf)); assert_se(UNIT_GET_SLICE(son) == parent); assert_se(UNIT_GET_SLICE(daughter) == parent); assert_se(UNIT_GET_SLICE(parent_deep) == parent); diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 8674dab488..a35d08ef63 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -1038,7 +1038,7 @@ TEST(condition_test_group) { char gid[DECIMAL_STR_MAX(uint32_t)]; gid_t *gids, max_gid; int ngroups, r, i; - long ngroups_max; + int ngroups_max; xsprintf(gid, "%u", UINT32_C(0xFFFF)); ASSERT_NOT_NULL((condition = condition_new(CONDITION_GROUP, gid, false, false))); @@ -1054,7 +1054,7 @@ TEST(condition_test_group) { ASSERT_OK_POSITIVE(r); condition_free(condition); - ngroups_max = ASSERT_OK_ERRNO(sysconf(_SC_NGROUPS_MAX)); + ngroups_max = ASSERT_OK(sysconf_ngroups_max()); ASSERT_GT(ngroups_max, 0); gids = newa(gid_t, ngroups_max); diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 5a6a1a4204..abe04182be 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -100,9 +100,9 @@ int main(int argc, char *argv[]) { assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); printf("Load1:\n"); - assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, &a) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "b.service", NULL, &b) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "c.service", NULL, &c) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, LOG_ERR, &a) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "b.service", NULL, LOG_ERR, &b) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "c.service", NULL, LOG_ERR, &c) >= 0); manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test1: (Trivial)\n"); @@ -114,8 +114,8 @@ int main(int argc, char *argv[]) { printf("Load2:\n"); manager_clear_jobs(m); - assert_se(manager_load_startable_unit_or_warn(m, "d.service", NULL, &d) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "e.service", NULL, &e) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "d.service", NULL, LOG_ERR, &d) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "e.service", NULL, LOG_ERR, &e) >= 0); manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test2: (Cyclic Order, Unfixable)\n"); @@ -131,7 +131,7 @@ int main(int argc, char *argv[]) { manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load3:\n"); - assert_se(manager_load_startable_unit_or_warn(m, "g.service", NULL, &g) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "g.service", NULL, LOG_ERR, &g) >= 0); manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test5: (Colliding transaction, fail)\n"); @@ -153,7 +153,7 @@ int main(int argc, char *argv[]) { manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load4:\n"); - assert_se(manager_load_startable_unit_or_warn(m, "h.service", NULL, &h) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "h.service", NULL, LOG_ERR, &h) >= 0); manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test10: (Unmergeable job type of auxiliary job, fail)\n"); @@ -162,7 +162,7 @@ int main(int argc, char *argv[]) { printf("Load5:\n"); manager_clear_jobs(m); - assert_se(manager_load_startable_unit_or_warn(m, "i.service", NULL, &i) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "i.service", NULL, LOG_ERR, &i) >= 0); SERVICE(a)->state = SERVICE_RUNNING; SERVICE(d)->state = SERVICE_RUNNING; manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); @@ -176,7 +176,7 @@ int main(int argc, char *argv[]) { printf("Load6:\n"); manager_clear_jobs(m); - assert_se(manager_load_startable_unit_or_warn(m, "a-conj.service", NULL, &a_conj) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "a-conj.service", NULL, LOG_ERR, &a_conj) >= 0); SERVICE(a)->state = SERVICE_DEAD; manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 1a832ad2f7..8206452f4c 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -304,7 +304,7 @@ static void _test(const char *file, unsigned line, const char *func, ASSERT_NOT_NULL(unit_name); - ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, LOG_ERR, &unit)); /* We need to start the slices as well otherwise the slice cgroups might be pruned * in on_cgroup_empty_event. */ start_parent_slices(unit); @@ -322,7 +322,7 @@ static void _test_service(const char *file, unsigned line, const char *func, ASSERT_NOT_NULL(unit_name); - ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, LOG_ERR, &unit)); ASSERT_OK(unit_start(unit, NULL)); check_service_result(file, line, func, m, unit, result_expected); } diff --git a/src/test/test-path.c b/src/test/test-path.c index ff1dee8474..3a4aa64c2f 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -150,7 +150,7 @@ TEST_RET(path_exists) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, NULL); @@ -183,7 +183,7 @@ TEST_RET(path_existsglob) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, NULL); @@ -216,7 +216,7 @@ TEST_RET(path_changed) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, NULL); @@ -253,7 +253,7 @@ TEST_RET(path_modified) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, NULL); @@ -291,7 +291,7 @@ TEST_RET(path_unit) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, "path-mycustomunit.service"); @@ -320,7 +320,7 @@ TEST_RET(path_directorynotempty) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, LOG_ERR, &unit)); Path *path = PATH(unit); Service *service = service_for_path(m, path, NULL); @@ -360,7 +360,7 @@ TEST_RET(path_makedirectory_directorymode) { return r; Unit *unit; - ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, LOG_ERR, &unit)); ASSERT_FAIL(access(test_path, F_OK)); diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c index a523c01e8f..83204eb0d9 100644 --- a/src/test/test-sched-prio.c +++ b/src/test/test-sched-prio.c @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* load idle ok */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_ok.service", NULL, &idle_ok) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_ok.service", NULL, LOG_ERR, &idle_ok) >= 0); ser = SERVICE(idle_ok); assert_se(ser->exec_context.cpu_sched_policy == SCHED_OTHER); assert_se(ser->exec_context.cpu_sched_priority == 0); @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) { /* * load idle bad. This should print a warning but we have no way to look at it. */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_bad.service", NULL, &idle_bad) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_bad.service", NULL, LOG_ERR, &idle_bad) >= 0); ser = SERVICE(idle_ok); assert_se(ser->exec_context.cpu_sched_policy == SCHED_OTHER); assert_se(ser->exec_context.cpu_sched_priority == 0); @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) { * load rr ok. * Test that the default priority is moving from 0 to 1. */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_ok.service", NULL, &rr_ok) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_ok.service", NULL, LOG_ERR, &rr_ok) >= 0); ser = SERVICE(rr_ok); assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); assert_se(ser->exec_context.cpu_sched_priority == 1); @@ -62,7 +62,7 @@ int main(int argc, char *argv[]) { * load rr bad. * Test that the value of 0 and 100 is ignored. */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_bad.service", NULL, &rr_bad) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_bad.service", NULL, LOG_ERR, &rr_bad) >= 0); ser = SERVICE(rr_bad); assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); assert_se(ser->exec_context.cpu_sched_priority == 1); @@ -71,13 +71,13 @@ int main(int argc, char *argv[]) { * load rr change. * Test that anything between 1 and 99 can be set. */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_change.service", NULL, &rr_sched) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_change.service", NULL, LOG_ERR, &rr_sched) >= 0); ser = SERVICE(rr_sched); assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); assert_se(ser->exec_context.cpu_sched_priority == 99); /* load ext ok */ - assert_se(manager_load_startable_unit_or_warn(m, "sched_ext_ok.service", NULL, &ext_ok) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "sched_ext_ok.service", NULL, LOG_ERR, &ext_ok) >= 0); ser = SERVICE(ext_ok); assert_se(ser->exec_context.cpu_sched_policy == SCHED_EXT); assert_se(ser->exec_context.cpu_sched_priority == 0); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 038140d4c5..4b14bd4696 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -175,12 +175,10 @@ TEST(getpeercred_getpeergroups) { assert_se(fully_set_uid_gid(test_uid, test_gid, test_gids, n_test_gids) >= 0); } else { - long ngroups_max; - test_uid = getuid(); test_gid = getgid(); - ngroups_max = sysconf(_SC_NGROUPS_MAX); + int ngroups_max = sysconf_ngroups_max(); assert_se(ngroups_max > 0); test_gids = newa(gid_t, ngroups_max); diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 7f71f5fdc7..c218e015fa 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -88,6 +88,9 @@ TEST(unit_name_is_valid) { test_unit_name_is_valid_one("foo.target.wants/plain.service", UNIT_NAME_ANY, false); test_unit_name_is_valid_one("foo.target.conf/foo.conf", UNIT_NAME_ANY, false); test_unit_name_is_valid_one("foo.target.requires/plain.socket", UNIT_NAME_ANY, false); + + /* The build-time configured fallback for default.target must be a valid plain unit name. */ + test_unit_name_is_valid_one(FALLBACK_DEFAULT_TARGET, UNIT_NAME_PLAIN, true); } static void test_unit_name_replace_instance_one(const char *pattern, const char *repl, const char *expected, int ret) { diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index 53ad9422f3..22eb8df085 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -3,12 +3,15 @@ #include #include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" #include "format-util.h" #include "log.h" #include "memory-util.h" #include "path-util.h" #include "string-util.h" #include "tests.h" +#include "tmpfile-util.h" #include "user-util.h" static void test_uid_to_name_one(uid_t uid, const char *name) { @@ -447,4 +450,184 @@ TEST(mangle_gecos) { test_mangle_gecos_one("\xe2\x28\xa1", " ( "); } +TEST(lookup_pwent_in_files) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-user-util-passwd-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + ASSERT_OK(fmkostemp_safe(fn, "w", &f)); + ASSERT_OK(write_string_stream( + f, + "testuser1:x:1234:5678:Test User 1:/home/test1:/bin/sh\n" + "testuser2:x:5678:9999:Test User 2:/home/test2:/bin/bash\n", + /* flags= */ 0)); + ASSERT_OK(fflush_and_check(f)); + + char **files = STRV_MAKE(fn); + + /* Lookup by name */ + _cleanup_free_ struct passwd *pw = NULL; + ASSERT_OK(lookup_pwent_in_files(files, "testuser1", UID_INVALID, &pw)); + ASSERT_STREQ(pw->pw_name, "testuser1"); + ASSERT_EQ(pw->pw_uid, 1234u); + ASSERT_EQ(pw->pw_gid, 5678u); + ASSERT_STREQ(pw->pw_gecos, "Test User 1"); + ASSERT_STREQ(pw->pw_dir, "/home/test1"); + ASSERT_STREQ(pw->pw_shell, "/bin/sh"); + pw = mfree(pw); + + ASSERT_OK(lookup_pwent_in_files(STRV_MAKE(fn, fn, fn, fn), "testuser2", UID_INVALID, &pw)); + ASSERT_STREQ(pw->pw_name, "testuser2"); + ASSERT_EQ(pw->pw_uid, 5678u); + ASSERT_STREQ(pw->pw_shell, "/bin/bash"); + pw = mfree(pw); + + /* Caller doesn't care about contents */ + ASSERT_OK(lookup_pwent_in_files(files, "testuser1", UID_INVALID, /* ret= */ NULL)); + + /* Missing entry */ + ASSERT_ERROR(lookup_pwent_in_files(files, "nosuchuser", UID_INVALID, /* ret= */ NULL), ESRCH); + ASSERT_ERROR(lookup_pwent_in_files(files, "nosuchuser", UID_INVALID, &pw), ESRCH); + ASSERT_NULL(pw); + + /* Lookup by uid */ + ASSERT_OK(lookup_pwent_in_files(files, /* name= */ NULL, 1234, &pw)); + ASSERT_STREQ(pw->pw_name, "testuser1"); + pw = mfree(pw); + + ASSERT_OK(lookup_pwent_in_files(files, /* name= */ NULL, 5678, &pw)); + ASSERT_STREQ(pw->pw_name, "testuser2"); + pw = mfree(pw); + + /* Missing uid */ + ASSERT_ERROR(lookup_pwent_in_files(files, /* name= */ NULL, 424242, &pw), ESRCH); + ASSERT_ERROR(lookup_pwent_in_files(files, /* name= */ NULL, 424242, NULL), ESRCH); + + /* Non-existent file in the list is skipped */ + ASSERT_OK(lookup_pwent_in_files( + STRV_MAKE("/nonexistent-dir/passwd", fn), + "testuser1", + UID_INVALID, + &pw)); + ASSERT_STREQ(pw->pw_name, "testuser1"); + pw = mfree(pw); + + /* All files missing → -ESRCH */ + ASSERT_ERROR(lookup_pwent_in_files( + STRV_MAKE("/nonexistent-dir/passwd"), + "testuser1", + UID_INVALID, + &pw), ESRCH); + + /* First match wins: same name in two files, but the first file's entry should be picked */ + _cleanup_(unlink_tempfilep) char fn2[] = "/tmp/test-user-util-passwd2-XXXXXX"; + _cleanup_fclose_ FILE *f2 = NULL; + + ASSERT_OK(fmkostemp_safe(fn2, "w", &f2)); + ASSERT_OK(write_string_stream( + f2, + "testuser1:x:111:222:First:/h/first:/bin/zsh\n", + /* flags= */ 0)); + ASSERT_OK(fflush_and_check(f2)); + + ASSERT_OK(lookup_pwent_in_files( + STRV_MAKE(fn2, fn), + "testuser1", + UID_INVALID, + &pw)); + ASSERT_EQ(pw->pw_uid, 111u); + ASSERT_STREQ(pw->pw_gecos, "First"); +} + +TEST(lookup_grent_in_files) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-user-util-group-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + ASSERT_OK(fmkostemp_safe(fn, "w", &f)); + ASSERT_OK(write_string_stream( + f, + "testgroup1:x:100:testuser1,testuser2\n" + "testgroup2:x:200:\n", + /* flags= */ 0)); + ASSERT_OK(fflush_and_check(f)); + + char **files = STRV_MAKE(fn); + + /* Lookup by name */ + _cleanup_free_ struct group *gr = NULL; + ASSERT_OK(lookup_grent_in_files(files, "testgroup1", GID_INVALID, &gr)); + ASSERT_STREQ(gr->gr_name, "testgroup1"); + ASSERT_EQ(gr->gr_gid, 100u); + ASSERT_STREQ(gr->gr_mem[0], "testuser1"); + ASSERT_STREQ(gr->gr_mem[1], "testuser2"); + ASSERT_NULL(gr->gr_mem[2]); + gr = mfree(gr); + + ASSERT_OK(lookup_grent_in_files(files, "testgroup2", GID_INVALID, &gr)); + ASSERT_STREQ(gr->gr_name, "testgroup2"); + ASSERT_EQ(gr->gr_gid, 200u); + ASSERT_NULL(gr->gr_mem[0]); + gr = mfree(gr); + + /* Caller doesn't care about contents */ + ASSERT_OK(lookup_grent_in_files(files, "testgroup1", GID_INVALID, /* ret= */ NULL)); + + /* Missing entry */ + ASSERT_ERROR(lookup_grent_in_files(files, "nosuchgroup", GID_INVALID, &gr), ESRCH); + ASSERT_NULL(gr); + + /* Lookup by gid */ + ASSERT_OK(lookup_grent_in_files(files, /* name= */ NULL, 100, &gr)); + ASSERT_STREQ(gr->gr_name, "testgroup1"); + gr = mfree(gr); + + ASSERT_OK(lookup_grent_in_files(files, /* name= */ NULL, 200, &gr)); + ASSERT_STREQ(gr->gr_name, "testgroup2"); + gr = mfree(gr); + + /* Missing gid */ + ASSERT_ERROR(lookup_grent_in_files(files, /* name= */ NULL, 424242, &gr), ESRCH); + + /* Non-existent file is skipped */ + ASSERT_OK(lookup_grent_in_files( + STRV_MAKE("/nonexistent-dir/group", fn), + "testgroup1", + GID_INVALID, + &gr)); + ASSERT_STREQ(gr->gr_name, "testgroup1"); +} + +TEST(lookup_groups_in_files) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-user-util-group-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + ASSERT_OK(fmkostemp_safe(fn, "w", &f)); + ASSERT_OK(write_string_stream( + f, + "testgroup1:x:100:testuser1,testuser2\n" + "testgroup2:x:200:testuser1\n" + "testgroup3:x:300:\n" + "testgroup4:x:400:root,somebody,testuser1\n", + /* flags= */ 0)); + ASSERT_OK(fflush_and_check(f)); + + char **files = STRV_MAKE(fn); + + /* Lookup by name */ + _cleanup_free_ gid_t *arr = NULL; + + ASSERT_EQ(lookup_groups_in_files(files, "testuser1", 100, &arr), 3); + gid_t exp1[] = { 100, 200, 400 }; + ASSERT_EQ(memcmp(arr, exp1, sizeof(exp1)), 0); + arr = mfree(arr); + + ASSERT_EQ(lookup_groups_in_files(files, "testuser1", 0, &arr), 4); + gid_t exp2[] = { 0, 100, 200, 400 }; + ASSERT_EQ(memcmp(arr, exp2, sizeof(exp2)), 0); + arr = mfree(arr); + + ASSERT_EQ(lookup_groups_in_files(files, "testuser3", 300, &arr), 1); + gid_t exp3[] = { 300 }; + ASSERT_EQ(memcmp(arr, exp3, sizeof(exp3)), 0); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c index 75e9085c26..c0490546b8 100644 --- a/src/tmpfiles/offline-passwd.c +++ b/src/tmpfiles/offline-passwd.c @@ -53,10 +53,10 @@ static int populate_uid_cache(const char *root, Hashmap **ret) { /* The directory list is hardcoded here: /etc is the standard, and rpm-ostree uses /usr/lib. This * could be made configurable, but I don't see the point right now. */ - FOREACH_STRING(fname, "/etc/passwd", "/usr/lib/passwd") { + STRV_FOREACH(fname, PASSWD_FILES) { _cleanup_fclose_ FILE *f = NULL; - r = open_passwd_file(root, fname, &f); + r = open_passwd_file(root, *fname, &f); if (r == -ENOENT) continue; if (r < 0) @@ -93,10 +93,10 @@ static int populate_gid_cache(const char *root, Hashmap **ret) { if (!cache) return -ENOMEM; - FOREACH_STRING(fname, "/etc/group", "/usr/lib/group") { + STRV_FOREACH(fname, GROUP_FILES) { _cleanup_fclose_ FILE *f = NULL; - r = open_passwd_file(root, fname, &f); + r = open_passwd_file(root, *fname, &f); if (r == -ENOENT) continue; if (r < 0) diff --git a/units/meson.build b/units/meson.build index 60ce3bf0ca..d61138bf7b 100644 --- a/units/meson.build +++ b/units/meson.build @@ -1070,6 +1070,33 @@ units = [ udev_units = [] hwdb_units = [] +# Unit files whose contents are embedded into the manager binary, so that it can +# fall back to a built-in copy when the file is not present on disk. Referenced +# from src/core/meson.build. +embedded_unit_names = [ + 'basic.target', + 'exit.target', + 'final.target', + 'graphical.target', + 'halt.target', + 'kexec.target', + 'multi-user.target', + 'poweroff.target', + 'reboot.target', + 'shutdown.target', + 'sigpwr.target', + 'soft-reboot.target', + 'sysinit.target', + 'systemd-exit.service', + 'systemd-halt.service', + 'systemd-kexec.service', + 'systemd-poweroff.service', + 'systemd-reboot.service', + 'systemd-soft-reboot.service', + 'umount.target', +] +embedded_units = [] + foreach unit : units source = unit.get('file') @@ -1092,7 +1119,7 @@ foreach unit : units endforeach if needs_jinja - t = custom_target( + processed = custom_target( input : source, output : name, command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], @@ -1100,9 +1127,9 @@ foreach unit : units install_dir : systemunitdir, install_tag : unit.get('install_tag', '')) if unit.get('install_tag', '') == 'udev' - udev_units += t + udev_units += processed elif unit.get('install_tag', '') == 'hwdb' - hwdb_units += t + hwdb_units += processed endif elif install install_data(source, @@ -1110,6 +1137,11 @@ foreach unit : units install_tag : unit.get('install_tag', '')) endif + if name in embedded_unit_names + # Embed either the processed output or the original file. + embedded_units += needs_jinja ? processed : source + endif + if install foreach target : unit.get('symlinks', []) if target.endswith('/')