Allow systemd to be built as as single statically-linked binary (#42820)

The idea is that we can build a container by building a single-binary
systemd:
```console
$ meson setup build-static --default-library=static --prefer-static --auto-features=disabled -Dbuild-static=true -Dsystemd-multicall-binary=true && ninja -C build-static systemd
$ mkdir /var/tmp/container/usr/lib -p
$ cp build-static/systemd /var/tmp/container/usr/lib/
$ echo 'ID=quick' >/var/tmp/container/usr/lib/os-release
$ systemd-nspawn --restrict-address-families=af_unix --register=no --private-users=managed -D /var/tmp/container/ /usr/lib/systemd
░ Spawning container container on /var/tmp/container.
░ Press Ctrl-] three times within 1s to kill container; two times followed by r
░ to reboot container; two times followed by p to poweroff container.
Selected user namespace base 1855193088 and range 65536.
systemd 262~devel running in system mode (-PAM -AUDIT +SELINUX -APPARMOR +IMA +IPE +SMACK -SECCOMP -GCRYPT +GNUTLS +OPENSSL -ACL +BLKID +CURL -ELFUTILS -FIDO2 +IDN2 +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 -PWQUALITY +P11KIT +QRENCODE +TPM2 -BZIP2 -LZ4 +XZ +ZLIB +ZSTD -BPF_FRAMEWORK -BTF -XKBCOMMON +UTMP -LIBARCHIVE)
Detected virtualization systemd-nspawn.
Detected architecture x86-64.
Detected first boot.

Welcome to Linux!

Initializing machine ID from container UUID.
Failed to open netlink, ignoring: Address family not supported by protocol
Applying preset policy.
Populated /etc with preset unit settings.
Unit default.target not found.
Falling back to graphical.target.
Mount unit not supported, skipping *MountsFor= dependencies.
Queued start job for default target graphical.target.
[  OK  ] Reached target sysinit.target.
[  OK  ] Reached target basic.target.
System is tainted: unmerged-bin:var-run-bad
[  OK  ] Reached target multi-user.target.
[  OK  ] Reached target graphical.target.
Startup finished in 61ms.
```
The container can be reloaded with SIGTERM, powered off with SIGRTMIN+4,
etc. SIGRTMIN+5 should cause a reboot but it currently fails:
```
...
Rebooting.
Container container is being rebooted.
Failed to attach root directory: Invalid argument
Failed to receive mount namespace fd from outer child: Input/output error
```
It's a bug … somewhere, but probably not caused by the linking changes
being done here.
This commit is contained in:
Lennart Poettering
2026-07-02 21:56:27 +02:00
committed by GitHub
37 changed files with 1004 additions and 183 deletions
+9
View File
@@ -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
+44 -26
View File
@@ -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-<suffix>.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-<suffix>.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-<suffix>.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
+6
View File
@@ -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
+1
View File
@@ -15,6 +15,7 @@
<!ENTITY HIGH_RLIMIT_NOFILE "{{HIGH_RLIMIT_NOFILE}}">
<!ENTITY DEFAULT_DNSSEC_MODE "{{DEFAULT_DNSSEC_MODE_STR}}">
<!ENTITY DEFAULT_DNS_OVER_TLS_MODE "{{DEFAULT_DNS_OVER_TLS_MODE_STR}}">
<!ENTITY FALLBACK_DEFAULT_TARGET "{{FALLBACK_DEFAULT_TARGET}}">
<!ENTITY DEFAULT_TIMEOUT_SEC "{{DEFAULT_TIMEOUT_SEC}} s">
<!ENTITY DEFAULT_USER_TIMEOUT_SEC "{{DEFAULT_USER_TIMEOUT_SEC}} s">
<!ENTITY SYSTEMD_DEFAULT_KEYMAP "{{SYSTEMD_DEFAULT_KEYMAP}}">
+11 -1
View File
@@ -1,6 +1,9 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY % entities SYSTEM "custom-entities.ent" >
%entities;
]>
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd.special" xmlns:xi="http://www.w3.org/2001/XInclude">
@@ -239,6 +242,13 @@
<varname>3</varname>, <varname>5</varname>, …; see
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
<para>If <filename>default.target</filename> is not present on disk and no unit was requested on
the kernel command line, the default set at compile time will be used
(<filename>&FALLBACK_DEFAULT_TARGET;</filename>).</para>
<para>If the default unit or one of the fallbacks cannot be started, systemd starts
<filename>rescue.target</filename>.</para>
<para>For typical unit files please set <literal>WantedBy=</literal> to a regular target (like
<filename>multi-user.target</filename> or <filename>graphical.target</filename>),
instead of <filename>default.target</filename>, since such a service will also be run on special
+10 -3
View File
@@ -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,
+8 -2
View File
@@ -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')
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+5
View File
@@ -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
}
+2 -1
View File
@@ -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);
+3
View File
@@ -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"
+326 -13
View File
@@ -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
}
+25
View File
@@ -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);
+6 -6
View File
@@ -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:
+24 -28
View File
@@ -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;
}
+1 -1
View File
@@ -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
+33
View File
@@ -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)}" }},')
+54 -3
View File
@@ -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.
+108 -22
View File
@@ -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
+39 -16
View File
@@ -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,
+2 -2
View File
@@ -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);
+16 -7
View File
@@ -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',
+5
View File
@@ -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) {
+4 -6
View File
@@ -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)
+7 -7
View File
@@ -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);
+2 -2
View File
@@ -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);
+9 -9
View File
@@ -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");
+2 -2
View File
@@ -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);
}
+7 -7
View File
@@ -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));
+6 -6
View File
@@ -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);
+1 -3
View File
@@ -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);
+3
View File
@@ -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) {
+183
View File
@@ -3,12 +3,15 @@
#include <unistd.h>
#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);
+4 -4
View File
@@ -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)
+35 -3
View File
@@ -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('/')