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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Executable
+33
@@ -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)}" }},')
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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('/')
|
||||
|
||||
Reference in New Issue
Block a user