mirror of
https://github.com/systemd/systemd
synced 2026-03-16 10:04:47 +01:00
Compare commits
No commits in common. "e9a1271a0c99f0fa5a16786c85b44b2a06150ae0" and "db4b6b70434295aa7799ac74b80a6d708d1f0ba4" have entirely different histories.
e9a1271a0c
...
db4b6b7043
@ -289,9 +289,6 @@ All tools:
|
||||
user/group records for dynamically registered service users (i.e. users
|
||||
registered through `DynamicUser=1`).
|
||||
|
||||
* `$SYSTEMD_NSS_LOG_LEVEL=<level>` — If set, sets the log level for `nss-systemd`
|
||||
and other NSS plugins specifically. Takes priority over `$SYSTEMD_LOG_LEVEL`.
|
||||
|
||||
`systemd-timedated`:
|
||||
|
||||
* `$SYSTEMD_TIMEDATED_NTP_SERVICES=…` — colon-separated list of unit names of
|
||||
|
||||
@ -17,8 +17,7 @@ two specific features of container management:
|
||||
2. Stricter default security policies, i.e. sand-boxing of applications.
|
||||
|
||||
The primary tool for interacting with Portable Services is `portablectl`,
|
||||
and they are managed by the `systemd-portabled` service. `systemd-portabled` can
|
||||
run as a system or a user service.
|
||||
and they are managed by the `systemd-portabled` service.
|
||||
|
||||
Portable services don't bring anything inherently new to the table.
|
||||
All they do is put together known concepts to cover a specific set of use-cases in a
|
||||
@ -251,14 +250,6 @@ validated against the (authenticated) image contents.
|
||||
If the field is not specified the image will work fine, but is not necessarily recognizable as
|
||||
portable service image, and any set of units included in the image may be attached, there are no restrictions enforced.
|
||||
|
||||
The [os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html) may
|
||||
optionally be extended with a `PORTABLE_SCOPE=` field listing the scope in which the portable
|
||||
service may be used. This field may be set to either `system`, in which case the portable service
|
||||
can only be attached to the system instance of `systemd-portabled`, `user` in which case the portable
|
||||
can only be attached to a user instance of `systemd-portabled`, or `any` in which case it can be
|
||||
attached to either the system instance or user instances of `systemd-portabled`. If not specified, the
|
||||
`system` scope is implied.
|
||||
|
||||
## Extension Images
|
||||
|
||||
Portable services can be delivered as one or multiple images that extend the base
|
||||
|
||||
@ -636,19 +636,6 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PORTABLE_SCOPE=</varname></term>
|
||||
<listitem><para>Specifies the scope of the portable service. Takes one of <literal>system</literal>,
|
||||
<literal>user</literal>, or <literal>any</literal>. When set to <literal>system</literal>, the
|
||||
portable service can only be attached to the system instance of <command>systemd-portabled</command>.
|
||||
When set to <literal>user</literal>, the portable service can only be attached to the user instance
|
||||
of <command>systemd-portabled</command>. When set to <literal>any</literal>, the portable service
|
||||
can be attached to both the system and user instances of <command>systemd-portabled</command>.
|
||||
If not set, <literal>PORTABLE_SCOPE=system</literal> is implied.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
|
||||
29
meson.build
29
meson.build
@ -144,42 +144,41 @@ modprobedir = prefixdir / 'lib/modprobe.d'
|
||||
pkgdatadir = datadir / 'systemd'
|
||||
environmentdir = prefixdir / 'lib/environment.d'
|
||||
pkgsysconfdir = sysconfdir / 'systemd'
|
||||
userunitdir = libexecdir / 'user'
|
||||
userpresetdir = libexecdir / 'user-preset'
|
||||
userunitdir = prefixdir / 'lib/systemd/user'
|
||||
userpresetdir = prefixdir / 'lib/systemd/user-preset'
|
||||
tmpfilesdir = prefixdir / 'lib/tmpfiles.d'
|
||||
usertmpfilesdir = prefixdir / 'share/user-tmpfiles.d'
|
||||
sysusersdir = prefixdir / 'lib/sysusers.d'
|
||||
sysctldir = prefixdir / 'lib/sysctl.d'
|
||||
binfmtdir = prefixdir / 'lib/binfmt.d'
|
||||
modulesloaddir = prefixdir / 'lib/modules-load.d'
|
||||
networkdir = libexecdir / 'network'
|
||||
networkdir = prefixdir / 'lib/systemd/network'
|
||||
systemgeneratordir = libexecdir / 'system-generators'
|
||||
usergeneratordir = libexecdir / 'user-generators'
|
||||
systemenvgeneratordir = libexecdir / 'system-environment-generators'
|
||||
userenvgeneratordir = libexecdir / 'user-environment-generators'
|
||||
usergeneratordir = prefixdir / 'lib/systemd/user-generators'
|
||||
systemenvgeneratordir = prefixdir / 'lib/systemd/system-environment-generators'
|
||||
userenvgeneratordir = prefixdir / 'lib/systemd/user-environment-generators'
|
||||
systemshutdowndir = libexecdir / 'system-shutdown'
|
||||
systemsleepdir = libexecdir / 'system-sleep'
|
||||
systemunitdir = libexecdir / 'system'
|
||||
systempresetdir = libexecdir / 'system-preset'
|
||||
initrdpresetdir = libexecdir / 'initrd-preset'
|
||||
systemunitdir = prefixdir / 'lib/systemd/system'
|
||||
systempresetdir = prefixdir / 'lib/systemd/system-preset'
|
||||
initrdpresetdir = prefixdir / 'lib/systemd/initrd-preset'
|
||||
udevlibexecdir = prefixdir / 'lib/udev'
|
||||
udevrulesdir = udevlibexecdir / 'rules.d'
|
||||
udevhwdbdir = udevlibexecdir / 'hwdb.d'
|
||||
catalogdir = libexecdir / 'catalog'
|
||||
catalogdir = prefixdir / 'lib/systemd/catalog'
|
||||
kerneldir = prefixdir / 'lib/kernel'
|
||||
kernelinstalldir = kerneldir / 'install.d'
|
||||
factorydir = datadir / 'factory'
|
||||
bootlibdir = libexecdir / 'boot/efi'
|
||||
testsdir = libexecdir / 'tests'
|
||||
bootlibdir = prefixdir / 'lib/systemd/boot/efi'
|
||||
testsdir = prefixdir / 'lib/systemd/tests'
|
||||
unittestsdir = testsdir / 'unit-tests'
|
||||
testdata_dir = testsdir / 'testdata'
|
||||
systemdstatedir = localstatedir / 'lib/systemd'
|
||||
catalogstatedir = systemdstatedir / 'catalog'
|
||||
randomseeddir = localstatedir / 'lib/systemd'
|
||||
systemprofiledir = libexecdir / 'portable' / 'profile'
|
||||
userprofiledir = libexecdir / 'user' / 'portable' / 'profile'
|
||||
profiledir = libexecdir / 'portable' / 'profile'
|
||||
repartdefinitionsdir = libexecdir / 'repart/definitions'
|
||||
ntpservicelistdir = libexecdir / 'ntp-units.d'
|
||||
ntpservicelistdir = prefixdir / 'lib/systemd/ntp-units.d'
|
||||
credstoredir = prefixdir / 'lib/credstore'
|
||||
pcrlockdir = prefixdir / 'lib/pcrlock.d'
|
||||
mimepackagesdir = prefixdir / 'share/mime/packages'
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
[Service]
|
||||
ExecStartPre=cat /usr/lib/os-release
|
||||
ExecStart=sleep 120
|
||||
@ -7,10 +7,8 @@ mkdir -p "$BUILDROOT/var/lib/app1"
|
||||
cat >>"$BUILDROOT/usr/lib/os-release" <<EOF
|
||||
MARKER=1
|
||||
PORTABLE_PREFIXES=app0 minimal minimal-app0
|
||||
PORTABLE_SCOPE=any
|
||||
EOF
|
||||
if [ ! -L "$BUILDROOT/etc/os-release" ]; then
|
||||
cp "$BUILDROOT/usr/lib/os-release" "$BUILDROOT/etc/os-release"
|
||||
fi
|
||||
cp "$BUILDROOT/usr/lib/systemd/system/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/system/minimal-app0-foo.service"
|
||||
cp "$BUILDROOT/usr/lib/systemd/user/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/user/minimal-app0-foo.service"
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
[Service]
|
||||
ExecStartPre=cat /usr/lib/os-release
|
||||
ExecStart=sleep 120
|
||||
@ -7,10 +7,8 @@ mkdir -p "$BUILDROOT/var/lib/app1"
|
||||
cat >>"$BUILDROOT/usr/lib/os-release" <<EOF
|
||||
MARKER=2
|
||||
PORTABLE_PREFIXES=app0 minimal minimal-app0
|
||||
PORTABLE_SCOPE=any
|
||||
EOF
|
||||
if [ ! -L "$BUILDROOT/etc/os-release" ]; then
|
||||
cp "$BUILDROOT/usr/lib/os-release" "$BUILDROOT/etc/os-release"
|
||||
fi
|
||||
cp "$BUILDROOT/usr/lib/systemd/system/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/system/minimal-app0-bar.service"
|
||||
cp "$BUILDROOT/usr/lib/systemd/user/minimal-app0.service" "$BUILDROOT/usr/lib/systemd/user/minimal-app0-bar.service"
|
||||
|
||||
@ -2752,7 +2752,7 @@ static int offline_security_checks(
|
||||
(void) mkdir_parents(dropin, 0755);
|
||||
|
||||
if (!is_path(profile)) {
|
||||
r = find_portable_profile(scope, profile, unit_name, &profile_path);
|
||||
r = find_portable_profile(profile, unit_name, &profile_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find portable profile %s: %m", profile);
|
||||
profile = profile_path;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "capability-util.h"
|
||||
#include "copy.h"
|
||||
#include "dirent-util.h"
|
||||
#include "dissect-image.h"
|
||||
#include "fd-util.h"
|
||||
@ -382,7 +383,64 @@ int import_make_foreign_userns(int *userns_fd) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
|
||||
int import_copy_foreign(
|
||||
int source_fd,
|
||||
int target_fd,
|
||||
int *userns_fd) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(source_fd >= 0);
|
||||
assert(target_fd >= 0);
|
||||
assert(userns_fd);
|
||||
|
||||
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
|
||||
* for that (allocated if needed), which should be foreign UID range */
|
||||
|
||||
r = import_make_foreign_userns(userns_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pidref_safe_fork_full(
|
||||
"copy-tree",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { *userns_fd, source_fd, target_fd }, 3,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
r = namespace_enter(
|
||||
/* pidns_fd= */ -EBADF,
|
||||
/* mntns_fd= */ -EBADF,
|
||||
/* netns_fd= */ -EBADF,
|
||||
*userns_fd,
|
||||
/* root_fd= */ -EBADF);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to join user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = copy_tree_at(
|
||||
source_fd, /* from= */ NULL,
|
||||
target_fd, /* to= */ NULL,
|
||||
/* override_uid= */ UID_INVALID,
|
||||
/* override_gid= */ GID_INVALID,
|
||||
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
|
||||
/* denylist= */ NULL,
|
||||
/* subvolumes= */ NULL);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to copy tree: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int import_remove_tree_foreign(const char *path, int *userns_fd) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
@ -392,9 +450,62 @@ int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_close_ int tree_fd = -EBADF;
|
||||
r = mountfsd_mount_directory(
|
||||
path,
|
||||
*userns_fd,
|
||||
DISSECT_IMAGE_FOREIGN_UID,
|
||||
&tree_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pidref_safe_fork_full(
|
||||
"rm-tree",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { *userns_fd, tree_fd }, 2,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* child */
|
||||
|
||||
r = namespace_enter(
|
||||
/* pidns_fd= */ -EBADF,
|
||||
/* mntns_fd= */ -EBADF,
|
||||
/* netns_fd= */ -EBADF,
|
||||
*userns_fd,
|
||||
/* root_fd= */ -EBADF);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to join user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
|
||||
if (dfd < 0) {
|
||||
log_error_errno(r, "Failed to reopen tree fd: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(userns_fd);
|
||||
|
||||
/* Try the userns dance first, to remove foreign UID range owned trees */
|
||||
if (FLAGS_SET(flags, IMPORT_FOREIGN_UID))
|
||||
(void) remove_tree_foreign(path, *userns_fd);
|
||||
(void) import_remove_tree_foreign(path, userns_fd);
|
||||
|
||||
r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
|
||||
if (r < 0)
|
||||
|
||||
@ -47,6 +47,9 @@ int import_allocate_event_with_signals(sd_event **ret);
|
||||
|
||||
int import_make_foreign_userns(int *userns_fd);
|
||||
|
||||
int import_copy_foreign(int source_fd, int target_fd, int *userns_fd);
|
||||
|
||||
int import_remove_tree_foreign(const char *path, int *userns_fd);
|
||||
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags);
|
||||
|
||||
#define IMPORT_BUFFER_SIZE (128U*1024U)
|
||||
|
||||
@ -309,7 +309,7 @@ static int tar_pull_make_local_copy(TarPull *p) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = copy_tree_at_foreign(p->tree_fd, copy_fd, p->userns_fd);
|
||||
r = import_copy_foreign(p->tree_fd, copy_fd, &p->userns_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
|
||||
@ -103,7 +103,6 @@ struct sd_dhcp_client {
|
||||
bool socket_priority_set;
|
||||
bool ipv6_acquired;
|
||||
bool bootp;
|
||||
bool send_release;
|
||||
};
|
||||
|
||||
static const uint8_t default_req_opts[] = {
|
||||
@ -664,14 +663,6 @@ int sd_dhcp_client_set_bootp(sd_dhcp_client *client, int bootp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_client_set_send_release(sd_dhcp_client *client, int enable) {
|
||||
assert_return(client, -EINVAL);
|
||||
|
||||
client->send_release = enable;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_set_state(sd_dhcp_client *client, DHCPState state) {
|
||||
assert(client);
|
||||
|
||||
@ -2356,18 +2347,13 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static int client_send_release(sd_dhcp_client *client) {
|
||||
int sd_dhcp_client_send_release(sd_dhcp_client *client) {
|
||||
_cleanup_free_ DHCPPacket *release = NULL;
|
||||
size_t optoffset, optlen;
|
||||
int r;
|
||||
|
||||
assert(client);
|
||||
|
||||
if (!client->send_release)
|
||||
return 0; /* disabled */
|
||||
|
||||
if (!client->lease || client->bootp)
|
||||
return 0; /* there is nothing to be released */
|
||||
if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp)
|
||||
return 0; /* do nothing */
|
||||
|
||||
r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset);
|
||||
if (r < 0)
|
||||
@ -2391,7 +2377,12 @@ static int client_send_release(sd_dhcp_client *client) {
|
||||
return r;
|
||||
|
||||
log_dhcp_client(client, "RELEASE");
|
||||
return 0;
|
||||
|
||||
/* This function is mostly called when stopping daemon. Hence, do not call client_stop() or
|
||||
* client_restart(). Otherwise, the notification callback will be called again and we may easily
|
||||
* enter an infinite loop. */
|
||||
client_initialize(client);
|
||||
return 1; /* sent and stopped. */
|
||||
}
|
||||
|
||||
int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
|
||||
@ -2434,18 +2425,11 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
|
||||
}
|
||||
|
||||
int sd_dhcp_client_stop(sd_dhcp_client *client) {
|
||||
int r;
|
||||
|
||||
if (!client)
|
||||
return 0;
|
||||
|
||||
DHCP_CLIENT_DONT_DESTROY(client);
|
||||
|
||||
r = client_send_release(client);
|
||||
if (r < 0)
|
||||
log_dhcp_client_errno(client, r,
|
||||
"Failed to send DHCP release message, ignoring: %m");
|
||||
|
||||
client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
|
||||
|
||||
return 0;
|
||||
|
||||
@ -33,30 +33,9 @@ int user_search_dirs(const char *suffix, char ***ret_config_dirs, char ***ret_da
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret) {
|
||||
assert(ret);
|
||||
|
||||
/* This does not bother with $CONFIGURATION_DIRECTORY, and hence can be applied to get other
|
||||
* service's config dir */
|
||||
|
||||
switch (scope) {
|
||||
case RUNTIME_SCOPE_USER:
|
||||
return xdg_user_config_dir(suffix, ret);
|
||||
|
||||
case RUNTIME_SCOPE_SYSTEM: {
|
||||
char *d = path_join("/etc", suffix);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
*ret = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* This does not bother with $RUNTIME_DIRECTORY, and hence can be applied to get other service's
|
||||
@ -64,19 +43,24 @@ int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret
|
||||
|
||||
switch (scope) {
|
||||
case RUNTIME_SCOPE_USER:
|
||||
return xdg_user_runtime_dir(suffix, ret);
|
||||
r = xdg_user_runtime_dir(suffix, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case RUNTIME_SCOPE_SYSTEM: {
|
||||
char *d = path_join("/run", suffix);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
*ret = d;
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret) {
|
||||
@ -231,7 +215,7 @@ static int acquire_lookup_dirs(
|
||||
},
|
||||
[LOOKUP_DIR_ATTACHED] = {
|
||||
[RUNTIME_SCOPE_SYSTEM] = { "/etc/systemd/system.attached", "/run/systemd/system.attached" },
|
||||
[RUNTIME_SCOPE_USER] = { "systemd/user.attached", "systemd/user.attached" },
|
||||
/* Portable services are not available to regular users for now. */
|
||||
},
|
||||
};
|
||||
|
||||
@ -349,9 +333,7 @@ static int get_paths_from_environ(const char *var, char ***ret) {
|
||||
|
||||
static char** user_unit_search_dirs(
|
||||
const char *persistent_config,
|
||||
const char *persistent_attached,
|
||||
const char *runtime_config,
|
||||
const char *runtime_attached,
|
||||
const char *global_persistent_config,
|
||||
const char *global_runtime_config,
|
||||
const char *generator,
|
||||
@ -366,7 +348,6 @@ static char** user_unit_search_dirs(
|
||||
/* The returned strv might contain duplicates, and we expect caller to filter them. */
|
||||
|
||||
assert(persistent_config);
|
||||
assert(persistent_attached);
|
||||
assert(global_persistent_config);
|
||||
assert(global_runtime_config);
|
||||
assert(persistent_control);
|
||||
@ -378,8 +359,7 @@ static char** user_unit_search_dirs(
|
||||
STRV_IFNOTNULL(runtime_control),
|
||||
STRV_IFNOTNULL(transient),
|
||||
STRV_IFNOTNULL(generator_early),
|
||||
persistent_config,
|
||||
persistent_attached);
|
||||
persistent_config);
|
||||
if (!paths)
|
||||
return NULL;
|
||||
|
||||
@ -396,7 +376,6 @@ static char** user_unit_search_dirs(
|
||||
/* strv_extend_many() can deal with NULL-s in arguments */
|
||||
if (strv_extend_many(&paths,
|
||||
runtime_config,
|
||||
runtime_attached,
|
||||
global_runtime_config,
|
||||
generator) < 0)
|
||||
return NULL;
|
||||
@ -556,8 +535,7 @@ int lookup_paths_init(
|
||||
break;
|
||||
|
||||
case RUNTIME_SCOPE_USER:
|
||||
add = user_unit_search_dirs(persistent_config, persistent_attached,
|
||||
runtime_config, runtime_attached,
|
||||
add = user_unit_search_dirs(persistent_config, runtime_config,
|
||||
global_persistent_config, global_runtime_config,
|
||||
generator, generator_early, generator_late,
|
||||
transient,
|
||||
|
||||
@ -57,7 +57,6 @@ int lookup_paths_init_or_warn(LookupPaths *lp, RuntimeScope scope, LookupPathsFl
|
||||
void lookup_paths_log(LookupPaths *p);
|
||||
void lookup_paths_done(LookupPaths *p);
|
||||
|
||||
int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret);
|
||||
int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret);
|
||||
int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret);
|
||||
|
||||
|
||||
@ -133,7 +133,6 @@ typedef struct MountImageParameters {
|
||||
char *password;
|
||||
ImagePolicy *image_policy;
|
||||
MountOptions *options;
|
||||
bool relax_extension_release_check;
|
||||
bool verity_sharing;
|
||||
struct iovec verity_root_hash;
|
||||
struct iovec verity_root_hash_sig;
|
||||
@ -377,7 +376,6 @@ static int vl_method_mount_image(
|
||||
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MountImageParameters, password), 0 },
|
||||
{ "imagePolicy", SD_JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
|
||||
{ "mountOptions", SD_JSON_VARIANT_OBJECT, json_dispatch_image_options, offsetof(MountImageParameters, options), 0 },
|
||||
{ "relaxExtensionReleaseChecks", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MountImageParameters, relax_extension_release_check), 0 },
|
||||
{ "veritySharing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MountImageParameters, verity_sharing), 0 },
|
||||
{ "verityDataFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, verity_data_fd_idx), 0 },
|
||||
{ "verityRootHash", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MountImageParameters, verity_root_hash), 0 },
|
||||
@ -540,8 +538,7 @@ static int vl_method_mount_image(
|
||||
/* Maybe the image is a bare filesystem. Note that this requires privileges, as it is
|
||||
* classified by the policy as an 'unprotected' image and will be refused otherwise. */
|
||||
DISSECT_IMAGE_NO_PARTITION_TABLE |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
|
||||
(p.relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0);
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
|
||||
/* Let's see if we have acquired the privilege to mount untrusted images already */
|
||||
bool polkit_have_untrusted_action =
|
||||
@ -713,7 +710,6 @@ static int vl_method_mount_image(
|
||||
return sd_varlink_replybo(
|
||||
link,
|
||||
SD_JSON_BUILD_PAIR_VARIANT("partitions", aj),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("singleFileSystem", di->single_file_system),
|
||||
SD_JSON_BUILD_PAIR_STRING("imagePolicy", ps),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("imageSize", di->image_size),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("sectorSize", di->sector_size),
|
||||
|
||||
@ -1206,6 +1206,14 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
|
||||
log_link_debug(link, "DHCP client is stopped. Acquiring IPv4 link-local address.");
|
||||
|
||||
if (link->dhcp_lease) {
|
||||
if (link->network->dhcp_send_release) {
|
||||
r = sd_dhcp_client_send_release(client);
|
||||
if (r < 0)
|
||||
log_link_full_errno(link,
|
||||
ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_WARNING,
|
||||
r, "Failed to send DHCP RELEASE, ignoring: %m");
|
||||
}
|
||||
|
||||
r = dhcp4_lease_lost(link);
|
||||
if (r < 0) {
|
||||
link_enter_failed(link);
|
||||
@ -1495,11 +1503,6 @@ static int dhcp4_configure(Link *link) {
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to %s BOOTP: %m",
|
||||
enable_disable(link->network->dhcp_use_bootp));
|
||||
|
||||
r = sd_dhcp_client_set_send_release(link->dhcp_client, link->network->dhcp_send_release);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to %s sending release message on stop: %m",
|
||||
enable_disable(link->network->dhcp_send_release));
|
||||
|
||||
r = sd_dhcp_client_attach_event(link->dhcp_client, link->manager->event, 0);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to attach event to DHCPv4 client: %m");
|
||||
@ -1862,27 +1865,8 @@ int link_request_dhcp4_client(Link *link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool link_should_drop_dhcp4_config(Link *link, Network *network) {
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
|
||||
if (!link_dhcp4_enabled(link))
|
||||
/* DHCP client is now disabled. */
|
||||
return true;
|
||||
|
||||
if (link->dhcp_client && link->network->dhcp_use_bootp &&
|
||||
network && !network->dhcp_use_bootp && network->dhcp_send_release)
|
||||
/* The client was enabled as a DHCP client and sending release message is requested, and now
|
||||
* the client is enabled as a BOOTP client. In this case, we need to release the previous
|
||||
* lease, and hence all DHCPv4 configurations (address, routes, DNS servers, and so on) needs
|
||||
* to be dropped. */
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int link_drop_dhcp4_config(Link *link, Network *network) {
|
||||
int ret = 0;
|
||||
int r, ret = 0;
|
||||
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
@ -1890,8 +1874,8 @@ int link_drop_dhcp4_config(Link *link, Network *network) {
|
||||
if (link->network == network)
|
||||
return 0; /* .network file is unchanged. It is not necessary to reconfigure the client. */
|
||||
|
||||
if (link_should_drop_dhcp4_config(link, network)) {
|
||||
/* Stop the client if it is running and drop the lease. */
|
||||
if (!link_dhcp4_enabled(link)) {
|
||||
/* DHCP client is disabled. Stop the client if it is running and drop the lease. */
|
||||
ret = sd_dhcp_client_stop(link->dhcp_client);
|
||||
|
||||
/* Also explicitly drop DHCPv4 address and routes. Why? This is for the case when the DHCPv4
|
||||
@ -1901,6 +1885,17 @@ int link_drop_dhcp4_config(Link *link, Network *network) {
|
||||
RET_GATHER(ret, dhcp4_remove_address_and_routes(link, /* only_marked= */ false));
|
||||
}
|
||||
|
||||
if (link->dhcp_client && link->network->dhcp_use_bootp &&
|
||||
network && !network->dhcp_use_bootp && network->dhcp_send_release) {
|
||||
/* If the client was enabled as a DHCP client, and is now enabled as a BOOTP client, release
|
||||
* the previous lease. Note, this can be easily fail, e.g. when the interface is down. Hence,
|
||||
* ignore any failures here. */
|
||||
r = sd_dhcp_client_send_release(link->dhcp_client);
|
||||
if (r < 0)
|
||||
log_link_full_errno(link, ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to send DHCP RELEASE, ignoring: %m");
|
||||
}
|
||||
|
||||
/* Even if the client is currently enabled and also enabled in the new .network file, detailed
|
||||
* settings for the client may be different. Let's unref() the client. But do not unref() the lease.
|
||||
* it will be unref()ed later when a new lease is acquired. */
|
||||
|
||||
@ -48,18 +48,10 @@ install_data('org.freedesktop.portable1.conf',
|
||||
install_dir : dbuspolicydir)
|
||||
install_data('org.freedesktop.portable1.service',
|
||||
install_dir : dbussystemservicedir)
|
||||
install_data('org.freedesktop.portable1.service-for-session',
|
||||
install_dir : dbussessionservicedir,
|
||||
rename : 'org.freedesktop.portable1.service')
|
||||
install_data('org.freedesktop.portable1.policy',
|
||||
install_dir : polkitpolicydir)
|
||||
|
||||
install_data('profile/system/default/service.conf', install_dir : systemprofiledir / 'default')
|
||||
install_data('profile/system/nonetwork/service.conf', install_dir : systemprofiledir / 'nonetwork')
|
||||
install_data('profile/system/strict/service.conf', install_dir : systemprofiledir / 'strict')
|
||||
install_data('profile/system/trusted/service.conf', install_dir : systemprofiledir / 'trusted')
|
||||
|
||||
install_data('profile/user/default/service.conf', install_dir : userprofiledir / 'default')
|
||||
install_data('profile/user/nonetwork/service.conf', install_dir : userprofiledir / 'nonetwork')
|
||||
install_data('profile/user/strict/service.conf', install_dir : userprofiledir / 'strict')
|
||||
install_data('profile/user/trusted/service.conf', install_dir : userprofiledir / 'trusted')
|
||||
install_data('profile/default/service.conf', install_dir : profiledir / 'default')
|
||||
install_data('profile/nonetwork/service.conf', install_dir : profiledir / 'nonetwork')
|
||||
install_data('profile/strict/service.conf', install_dir : profiledir / 'strict')
|
||||
install_data('profile/trusted/service.conf', install_dir : profiledir / 'trusted')
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[D-BUS Service]
|
||||
Name=org.freedesktop.portable1
|
||||
Exec=/bin/false
|
||||
SystemdService=dbus-org.freedesktop.portable1.service
|
||||
@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <linux/loop.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
@ -36,8 +35,6 @@
|
||||
#include "log.h"
|
||||
#include "loop-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "namespace-util.h"
|
||||
#include "nsresource.h"
|
||||
#include "os-util.h"
|
||||
#include "path-lookup.h"
|
||||
#include "pidref.h"
|
||||
@ -52,7 +49,6 @@
|
||||
#include "string-table.h"
|
||||
#include "strv.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "uid-classification.h"
|
||||
#include "unit-name.h"
|
||||
#include "vpick.h"
|
||||
|
||||
@ -182,90 +178,9 @@ static int send_one_fd_iov_with_data_fd(
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(portable_metadata_hash_ops, char, string_hash_func, string_compare_func,
|
||||
PortableMetadata, portable_metadata_unref);
|
||||
|
||||
static int receive_portable_metadata(
|
||||
int socket_fd,
|
||||
const char *path,
|
||||
PortableMetadata **ret_os_release,
|
||||
Hashmap **ret_unit_files) {
|
||||
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata* os_release = NULL;
|
||||
_cleanup_(hashmap_freep) Hashmap *unit_files = NULL;
|
||||
int r;
|
||||
|
||||
assert(socket_fd >= 0);
|
||||
assert(path);
|
||||
assert(ret_os_release);
|
||||
assert(ret_unit_files);
|
||||
|
||||
unit_files = hashmap_new(&portable_metadata_hash_ops);
|
||||
if (!unit_files)
|
||||
return -ENOMEM;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *add = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
/* We use NAME_MAX space for the SELinux label here. The kernel currently enforces no limit,
|
||||
* but according to suggestions from the SELinux people this will change and it will probably
|
||||
* be identical to NAME_MAX. For now we use that, but this should be updated one day when the
|
||||
* final limit is known. */
|
||||
char iov_buffer[PATH_MAX + NAME_MAX + 2];
|
||||
struct iovec iov = IOVEC_MAKE(iov_buffer, sizeof(iov_buffer));
|
||||
|
||||
ssize_t n = receive_one_fd_iov(socket_fd, &iov, 1, 0, &fd);
|
||||
if (n == -EIO)
|
||||
break;
|
||||
if (n < 0)
|
||||
return log_debug_errno(n, "Failed to receive item: %m");
|
||||
iov_buffer[n] = 0;
|
||||
|
||||
/* We can't really distinguish a zero-length datagram without any fds from EOF (both are
|
||||
* signalled the same way by recvmsg()). Hence, accept either as end notification. */
|
||||
if (isempty(iov_buffer) && fd < 0)
|
||||
break;
|
||||
|
||||
if (isempty(iov_buffer) || fd < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid item sent from child.");
|
||||
|
||||
/* Given recvmsg cannot be used with multiple io vectors if you don't know the size in
|
||||
* advance, use a marker to separate the name and the optional SELinux context. */
|
||||
char *selinux_label = memchr(iov_buffer, 0, n);
|
||||
assert(selinux_label);
|
||||
selinux_label++;
|
||||
|
||||
add = portable_metadata_new(iov_buffer, path, selinux_label, fd);
|
||||
if (!add)
|
||||
return -ENOMEM;
|
||||
fd = -EBADF;
|
||||
|
||||
/* Note that we do not initialize 'add->source' here, as the source path is not usable here
|
||||
* as it refers to a path only valid in the short-living namespaced child process we forked
|
||||
* here. */
|
||||
|
||||
if (PORTABLE_METADATA_IS_UNIT(add)) {
|
||||
r = hashmap_put(unit_files, add->name, add);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to add item to unit file list: %m");
|
||||
|
||||
add = NULL;
|
||||
|
||||
} else if (PORTABLE_METADATA_IS_OS_RELEASE(add) || PORTABLE_METADATA_IS_EXTENSION_RELEASE(add)) {
|
||||
|
||||
assert(!os_release);
|
||||
os_release = TAKE_PTR(add);
|
||||
} else
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
*ret_os_release = TAKE_PTR(os_release);
|
||||
*ret_unit_files = TAKE_PTR(unit_files);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_now(
|
||||
RuntimeScope scope,
|
||||
int rfd,
|
||||
const char *image_path,
|
||||
const char *where,
|
||||
char **matches,
|
||||
const char *image_name,
|
||||
bool path_is_extension,
|
||||
@ -282,7 +197,7 @@ static int extract_now(
|
||||
const char *os_release_id;
|
||||
int r;
|
||||
|
||||
/* Extracts the metadata from a directory tree 'dir_fd'. Extracts two kinds of information: the /etc/os-release
|
||||
/* Extracts the metadata from a directory tree 'where'. Extracts two kinds of information: the /etc/os-release
|
||||
* data, and all unit files matching the specified expression. Note that this function is called in two very
|
||||
* different but also similar contexts. When the tool gets invoked on a directory tree, we'll process it
|
||||
* directly, and in-process, and thus can return the requested data directly, via 'ret_os_release' and
|
||||
@ -292,15 +207,15 @@ static int extract_now(
|
||||
* used to send the data to the parent. */
|
||||
|
||||
assert(scope < _RUNTIME_SCOPE_MAX);
|
||||
assert(rfd >= 0);
|
||||
assert(where);
|
||||
|
||||
/* First, find os-release/extension-release and send it upstream (or just save it). */
|
||||
if (path_is_extension) {
|
||||
ImageClass class = IMAGE_SYSEXT;
|
||||
|
||||
r = open_extension_release_at(rfd, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
if (r == -ENOENT) {
|
||||
r = open_extension_release_at(rfd, IMAGE_CONFEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
r = open_extension_release(where, IMAGE_CONFEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
if (r >= 0)
|
||||
class = IMAGE_CONFEXT;
|
||||
}
|
||||
@ -310,7 +225,7 @@ static int extract_now(
|
||||
os_release_id = strjoina((class == IMAGE_SYSEXT) ? "/usr/lib" : "/etc", "/extension-release.d/extension-release.", image_name);
|
||||
} else {
|
||||
os_release_id = "/etc/os-release";
|
||||
r = open_os_release_at(rfd, &os_release_path, &os_release_fd);
|
||||
r = open_os_release(where, &os_release_path, &os_release_fd);
|
||||
}
|
||||
if (r < 0)
|
||||
log_debug_errno(r,
|
||||
@ -338,14 +253,10 @@ static int extract_now(
|
||||
}
|
||||
}
|
||||
|
||||
/* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual
|
||||
* unit discovery logic. If we're running in a user session, we look for units in
|
||||
* /usr/lib/systemd/user/ and corresponding directories. */
|
||||
r = lookup_paths_init(
|
||||
&paths,
|
||||
scope == RUNTIME_SCOPE_USER ? RUNTIME_SCOPE_GLOBAL : RUNTIME_SCOPE_SYSTEM,
|
||||
LOOKUP_PATHS_SPLIT_USR,
|
||||
/* root_dir= */ NULL);
|
||||
/* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual unit
|
||||
* discovery logic. Note that we force looking inside of /lib/systemd/system/ for units too, as the
|
||||
* image might have a legacy split-usr layout. */
|
||||
r = lookup_paths_init(&paths, scope, LOOKUP_PATHS_SPLIT_USR, where);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to acquire lookup paths: %m");
|
||||
|
||||
@ -354,19 +265,15 @@ static int extract_now(
|
||||
return -ENOMEM;
|
||||
|
||||
STRV_FOREACH(i, paths.search_path) {
|
||||
_cleanup_free_ char *relative = NULL, *resolved = NULL;
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
|
||||
r = chase_and_opendirat(rfd, *i, CHASE_AT_RESOLVE_IN_ROOT, &relative, &d);
|
||||
r = chase_and_opendir(*i, where, 0, &resolved, &d);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = chaseat_prefix_root(relative, image_path, &resolved);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
FOREACH_DIRENT(de, d, return log_debug_errno(errno, "Failed to read directory: %m")) {
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *m = NULL;
|
||||
_cleanup_freecon_ char *con = NULL;
|
||||
@ -425,7 +332,7 @@ static int extract_now(
|
||||
return log_debug_errno(r, "Failed to send unit metadata to parent: %m");
|
||||
}
|
||||
|
||||
m = portable_metadata_new(de->d_name, image_path, con, fd);
|
||||
m = portable_metadata_new(de->d_name, where, con, fd);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
fd = -EBADF;
|
||||
@ -464,106 +371,40 @@ static int portable_extract_by_path(
|
||||
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata* os_release = NULL;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *pinned_image_policy = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
|
||||
_cleanup_close_ int rfd = open(path, O_PATH|O_CLOEXEC);
|
||||
if (rfd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(rfd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat '%s': %m", path);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
r = loop_device_make_by_path(
|
||||
path,
|
||||
O_RDONLY,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&d);
|
||||
if (r == -EISDIR) {
|
||||
_cleanup_free_ char *image_name = NULL;
|
||||
|
||||
/* We can't turn this into a loop-back block device, and this returns EISDIR? Then this is a directory
|
||||
* tree and not a raw device. It's easy then. */
|
||||
|
||||
r = path_extract_filename(path, &image_name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract image name from path '%s': %m", path);
|
||||
|
||||
if (scope == RUNTIME_SCOPE_USER && uid_is_foreign(st.st_uid)) {
|
||||
_cleanup_close_ int userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
_cleanup_close_ int mfd = -EBADF;
|
||||
r = mountfsd_mount_directory_fd(rfd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, &mfd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to open '%s' via mountfsd: %m", path);
|
||||
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR;
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0)
|
||||
return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m");
|
||||
|
||||
_cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR;
|
||||
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return log_debug_errno(errno, "Failed to create pipe: %m");
|
||||
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
|
||||
r = pidref_safe_fork("(sd-extract)",
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_REOPEN_LOG,
|
||||
&child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
seq[0] = safe_close(seq[0]);
|
||||
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
|
||||
|
||||
if (setns(CLONE_NEWUSER, userns_fd) < 0) {
|
||||
r = log_debug_errno(errno, "Failed to join userns: %m");
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
r = extract_now(scope,
|
||||
mfd,
|
||||
path,
|
||||
matches,
|
||||
image_name,
|
||||
path_is_extension,
|
||||
/* relax_extension_release_check= */ false,
|
||||
seq[1],
|
||||
/* ret_os_release= */ NULL,
|
||||
/* ret_unit_files= */ NULL);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
|
||||
seq[1] = safe_close(seq[1]);
|
||||
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
|
||||
|
||||
r = receive_portable_metadata(seq[0], path, &os_release, &unit_files);
|
||||
r = extract_now(scope, path, matches, image_name, path_is_extension, /* relax_extension_release_check= */ false, -1, &os_release, &unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pidref_wait_for_terminate_and_check("(sd-extract)", &child, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r != EXIT_SUCCESS) {
|
||||
if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
|
||||
return log_debug_errno(r, "Failed to extract portable metadata from '%s': %m", path);
|
||||
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child failed.");
|
||||
}
|
||||
} else {
|
||||
r = extract_now(scope,
|
||||
rfd,
|
||||
path,
|
||||
matches,
|
||||
image_name,
|
||||
path_is_extension,
|
||||
/* relax_extension_release_check= */ false,
|
||||
/* socket_fd= */ -EBADF,
|
||||
&os_release,
|
||||
&unit_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
} else if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
|
||||
else {
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
|
||||
_cleanup_(rmdir_and_freep) char *tmpdir = NULL;
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR, errno_pipe_fd[2] = EBADF_PAIR;
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR;
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
|
||||
_cleanup_close_ int userns_fd = -EBADF;
|
||||
DissectImageFlags flags =
|
||||
DISSECT_IMAGE_READ_ONLY |
|
||||
DISSECT_IMAGE_GENERIC_ROOT |
|
||||
@ -580,18 +421,6 @@ static int portable_extract_by_path(
|
||||
else
|
||||
flags |= DISSECT_IMAGE_VALIDATE_OS;
|
||||
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
r = verity_settings_load(
|
||||
&verity,
|
||||
path,
|
||||
/* root_hash_path= */ NULL,
|
||||
/* root_hash_sig_path= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read verity artifacts for %s: %m", path);
|
||||
|
||||
if (verity.data_path)
|
||||
flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
|
||||
|
||||
/* We now have a loopback block device, let's fork off a child in its own mount namespace, mount it
|
||||
* there, and extract the metadata we need. The metadata is sent from the child back to us. */
|
||||
|
||||
@ -603,38 +432,9 @@ static int portable_extract_by_path(
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to create temporary directory: %m");
|
||||
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
r = mountfsd_mount_image_fd(
|
||||
rfd,
|
||||
userns_fd,
|
||||
/* options= */ NULL,
|
||||
image_policy,
|
||||
&verity,
|
||||
flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
|
||||
r = loop_device_make_by_path_at(
|
||||
rfd,
|
||||
/* path= */ NULL,
|
||||
O_RDONLY,
|
||||
/* sector_size= */ UINT32_MAX,
|
||||
LO_FLAGS_PARTSCAN,
|
||||
LOCK_SH,
|
||||
&d);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
|
||||
|
||||
r = dissect_loop_device(
|
||||
d,
|
||||
&verity,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
@ -653,20 +453,17 @@ static int portable_extract_by_path(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = verity_settings_load(&verity, path, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load root hash: %m");
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(m, d->fd, &verity);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load verity sig partition for '%s': %m", path);
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(m, &verity);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to guess verity roothash for '%s': %m", path);
|
||||
}
|
||||
|
||||
if (!m->image_name) {
|
||||
r = dissected_image_name_from_path(path, &m->image_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (ret_pinned_image_policy) {
|
||||
pinned_image_policy = image_policy_new_from_dissected(m, &verity);
|
||||
@ -677,77 +474,97 @@ static int portable_extract_by_path(
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0)
|
||||
return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m");
|
||||
|
||||
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return log_debug_errno(errno, "Failed to create pipe: %m");
|
||||
|
||||
r = pidref_safe_fork(
|
||||
"(sd-dissect)",
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|(scope == RUNTIME_SCOPE_SYSTEM ? FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE : 0),
|
||||
&child);
|
||||
r = pidref_safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_LOG, &child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
seq[0] = safe_close(seq[0]);
|
||||
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
|
||||
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
r = detach_mount_namespace_userns(userns_fd);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to detach mount namespace: %m");
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
}
|
||||
|
||||
r = dissected_image_mount(
|
||||
m,
|
||||
tmpdir,
|
||||
/* uid_shift= */ UID_INVALID,
|
||||
/* uid_range= */ UID_INVALID,
|
||||
userns_fd,
|
||||
/* userns_fd= */ -EBADF,
|
||||
flags);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to mount dissected image '%s': %m", path);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
log_debug_errno(r, "Failed to mount dissected image: %m");
|
||||
goto child_finish;
|
||||
}
|
||||
|
||||
_cleanup_close_ int mfd = open(tmpdir, O_DIRECTORY|O_CLOEXEC);
|
||||
if (mfd < 0) {
|
||||
r = log_debug_errno(errno, "Failed to open '%s': %m", tmpdir);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
}
|
||||
r = extract_now(scope, tmpdir, matches, m->image_name, path_is_extension, relax_extension_release_check, seq[1], NULL, NULL);
|
||||
|
||||
r = extract_now(scope,
|
||||
mfd,
|
||||
path,
|
||||
matches,
|
||||
m->image_name,
|
||||
path_is_extension,
|
||||
relax_extension_release_check,
|
||||
seq[1],
|
||||
/* ret_os_release= */ NULL,
|
||||
/* ret_unit_files= */ NULL);
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
child_finish:
|
||||
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
seq[1] = safe_close(seq[1]);
|
||||
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
|
||||
|
||||
r = receive_portable_metadata(seq[0], path, &os_release, &unit_files);
|
||||
unit_files = hashmap_new(&portable_metadata_hash_ops);
|
||||
if (!unit_files)
|
||||
return -ENOMEM;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *add = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
/* We use NAME_MAX space for the SELinux label here. The kernel currently enforces no limit, but
|
||||
* according to suggestions from the SELinux people this will change and it will probably be
|
||||
* identical to NAME_MAX. For now we use that, but this should be updated one day when the final
|
||||
* limit is known. */
|
||||
char iov_buffer[PATH_MAX + NAME_MAX + 2];
|
||||
struct iovec iov = IOVEC_MAKE(iov_buffer, sizeof(iov_buffer));
|
||||
|
||||
ssize_t n = receive_one_fd_iov(seq[0], &iov, 1, 0, &fd);
|
||||
if (n == -EIO)
|
||||
break;
|
||||
if (n < 0)
|
||||
return log_debug_errno(n, "Failed to receive item: %m");
|
||||
iov_buffer[n] = 0;
|
||||
|
||||
/* We can't really distinguish a zero-length datagram without any fds from EOF (both are signalled the
|
||||
* same way by recvmsg()). Hence, accept either as end notification. */
|
||||
if (isempty(iov_buffer) && fd < 0)
|
||||
break;
|
||||
|
||||
if (isempty(iov_buffer) || fd < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid item sent from child.");
|
||||
|
||||
/* Given recvmsg cannot be used with multiple io vectors if you don't know the size in advance,
|
||||
* use a marker to separate the name and the optional SELinux context. */
|
||||
char *selinux_label = memchr(iov_buffer, 0, n);
|
||||
assert(selinux_label);
|
||||
selinux_label++;
|
||||
|
||||
add = portable_metadata_new(iov_buffer, path, selinux_label, fd);
|
||||
if (!add)
|
||||
return -ENOMEM;
|
||||
fd = -EBADF;
|
||||
|
||||
/* Note that we do not initialize 'add->source' here, as the source path is not usable here as
|
||||
* it refers to a path only valid in the short-living namespaced child process we forked
|
||||
* here. */
|
||||
|
||||
if (PORTABLE_METADATA_IS_UNIT(add)) {
|
||||
r = hashmap_put(unit_files, add->name, add);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_debug_errno(r, "Failed to add item to unit file list: %m");
|
||||
|
||||
add = NULL;
|
||||
|
||||
} else if (PORTABLE_METADATA_IS_OS_RELEASE(add) || PORTABLE_METADATA_IS_EXTENSION_RELEASE(add)) {
|
||||
|
||||
assert(!os_release);
|
||||
os_release = TAKE_PTR(add);
|
||||
} else
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
r = pidref_wait_for_terminate_and_check("(sd-dissect)", &child, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pidref_done(&child);
|
||||
|
||||
if (r != EXIT_SUCCESS) {
|
||||
if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
|
||||
return log_debug_errno(r, "Failed to extract portable metadata from '%s': %m", path);
|
||||
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!os_release)
|
||||
@ -889,17 +706,15 @@ static int extract_image_and_extensions(
|
||||
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
|
||||
* the units are started. Also, collect valid portable prefixes if caller requested that. */
|
||||
if (validate_extension || ret_valid_prefixes) {
|
||||
_cleanup_free_ char *prefixes = NULL, *portable_scope_str = NULL;
|
||||
_cleanup_free_ char *prefixes = NULL;
|
||||
|
||||
r = parse_env_file_fd(
|
||||
os_release->fd, os_release->name,
|
||||
r = parse_env_file_fd(os_release->fd, os_release->name,
|
||||
"ID", &id,
|
||||
"ID_LIKE", &id_like,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"CONFEXT_LEVEL", &confext_level,
|
||||
"PORTABLE_PREFIXES", &prefixes,
|
||||
"PORTABLE_SCOPE", &portable_scope_str);
|
||||
"PORTABLE_PREFIXES", &prefixes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (isempty(id))
|
||||
@ -910,31 +725,6 @@ static int extract_image_and_extensions(
|
||||
if (!valid_prefixes)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
RuntimeScope portable_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
if (portable_scope_str) {
|
||||
if (streq(portable_scope_str, "any"))
|
||||
portable_scope = _RUNTIME_SCOPE_INVALID;
|
||||
else {
|
||||
portable_scope = runtime_scope_from_string(portable_scope_str);
|
||||
if (portable_scope < 0)
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Invalid PORTABLE_SCOPE value '%s' in image %s.",
|
||||
portable_scope_str,
|
||||
name_or_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (portable_scope != _RUNTIME_SCOPE_INVALID && portable_scope != scope)
|
||||
return sd_bus_error_setf(
|
||||
error,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Image %s portable scope '%s' incompatible with portabled runtime scope '%s'.",
|
||||
name_or_path,
|
||||
runtime_scope_to_string(portable_scope),
|
||||
runtime_scope_to_string(scope));
|
||||
}
|
||||
|
||||
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
|
||||
@ -1521,7 +1311,6 @@ static int install_chroot_dropin(
|
||||
}
|
||||
|
||||
static int install_profile_dropin(
|
||||
RuntimeScope scope,
|
||||
const char *image_path,
|
||||
const PortableMetadata *m,
|
||||
const char *dropin_dir,
|
||||
@ -1541,7 +1330,7 @@ static int install_profile_dropin(
|
||||
if (!profile)
|
||||
return 0;
|
||||
|
||||
r = find_portable_profile(scope, profile, m->name, &from);
|
||||
r = find_portable_profile(profile, m->name, &from);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_debug_errno(errno, "Profile '%s' is not accessible: %m", profile);
|
||||
@ -1599,7 +1388,6 @@ static const char *attached_path(const LookupPaths *paths, PortableFlags flags)
|
||||
}
|
||||
|
||||
static int attach_unit_file(
|
||||
RuntimeScope scope,
|
||||
const LookupPaths *paths,
|
||||
const char *image_path,
|
||||
ImageType type,
|
||||
@ -1669,7 +1457,7 @@ static int attach_unit_file(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = install_profile_dropin(scope, image_path, m, dropin_dir, profile, flags, &profile_dropin, changes, n_changes);
|
||||
r = install_profile_dropin(image_path, m, dropin_dir, profile, flags, &profile_dropin, changes, n_changes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1723,11 +1511,13 @@ static int attach_unit_file(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int image_target_path(RuntimeScope scope, const char *image_path, PortableFlags flags, char **ret) {
|
||||
_cleanup_free_ char *where = NULL;
|
||||
const char *fn;
|
||||
static int image_target_path(
|
||||
const char *image_path,
|
||||
PortableFlags flags,
|
||||
char **ret) {
|
||||
|
||||
const char *fn, *where;
|
||||
char *joined = NULL;
|
||||
int r;
|
||||
|
||||
assert(image_path);
|
||||
assert(ret);
|
||||
@ -1735,13 +1525,11 @@ static int image_target_path(RuntimeScope scope, const char *image_path, Portabl
|
||||
fn = last_path_component(image_path);
|
||||
|
||||
if (flags & PORTABLE_RUNTIME)
|
||||
r = runtime_directory_generic(scope, "portables", &where);
|
||||
where = "/run/portables/";
|
||||
else
|
||||
r = config_directory_generic(scope, "portables", &where);
|
||||
if (r < 0)
|
||||
return r;
|
||||
where = "/etc/portables/";
|
||||
|
||||
joined = path_join(where, fn);
|
||||
joined = strjoin(where, fn);
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -1769,48 +1557,13 @@ static int install_image(
|
||||
if (image_in_search_path(scope, IMAGE_PORTABLE, NULL, image_path))
|
||||
return 0;
|
||||
|
||||
r = image_target_path(scope, image_path, flags, &target);
|
||||
r = image_target_path(image_path, flags, &target);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to generate image symlink path: %m");
|
||||
|
||||
(void) mkdir_parents(target, 0755);
|
||||
|
||||
if (flags & PORTABLE_MIXED_COPY_LINK) {
|
||||
if (scope == RUNTIME_SCOPE_USER) {
|
||||
_cleanup_close_ int userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K);
|
||||
if (userns_fd < 0)
|
||||
return log_debug_errno(userns_fd, "Failed to allocate user namespace: %m");
|
||||
|
||||
_cleanup_close_ int fd = open(image_path, O_DIRECTORY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", image_path);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", image_path);
|
||||
|
||||
_cleanup_close_ int tree_fd = -EBADF;
|
||||
if (uid_is_foreign(st.st_uid)) {
|
||||
r = mountfsd_mount_directory_fd(fd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, &tree_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
tree_fd = TAKE_FD(fd);
|
||||
|
||||
_cleanup_close_ int directory_fd = -EBADF;
|
||||
r = mountfsd_make_directory(target, MODE_INVALID, /* flags= */ 0, &directory_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_close_ int copy_fd = -EBADF;
|
||||
r = mountfsd_mount_directory_fd(directory_fd, userns_fd, DISSECT_IMAGE_FOREIGN_UID, ©_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = copy_tree_at_foreign(tree_fd, copy_fd, userns_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
r = copy_tree(image_path,
|
||||
target,
|
||||
UID_INVALID,
|
||||
@ -1825,7 +1578,7 @@ static int install_image(
|
||||
image_path,
|
||||
glyph(GLYPH_ARROW_RIGHT),
|
||||
target);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (symlink(image_path, target) < 0)
|
||||
return log_debug_errno(
|
||||
@ -2062,7 +1815,6 @@ int portable_attach(
|
||||
|
||||
HASHMAP_FOREACH(item, unit_files) {
|
||||
r = attach_unit_file(
|
||||
scope,
|
||||
&paths,
|
||||
image->path,
|
||||
image->type,
|
||||
@ -2369,7 +2121,7 @@ int portable_detach(
|
||||
SET_FOREACH(item, markers) {
|
||||
_cleanup_free_ char *target = NULL;
|
||||
|
||||
r = image_target_path(scope, item, flags, &target);
|
||||
r = image_target_path(item, flags, &target);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item);
|
||||
continue;
|
||||
@ -2533,17 +2285,10 @@ int portable_get_state(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int portable_get_profiles(RuntimeScope scope, char ***ret) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
int r;
|
||||
|
||||
int portable_get_profiles(char ***ret) {
|
||||
assert(ret);
|
||||
|
||||
r = portable_profile_dirs(scope, &dirs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return conf_files_list_strv(ret, NULL, NULL, CONF_FILES_DIRECTORY|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED, (const char* const*) dirs);
|
||||
return conf_files_list_nulstr(ret, NULL, NULL, CONF_FILES_DIRECTORY|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED, PORTABLE_PROFILE_DIRS);
|
||||
}
|
||||
|
||||
static const char* const portable_change_type_table[_PORTABLE_CHANGE_TYPE_MAX] = {
|
||||
|
||||
@ -110,7 +110,7 @@ int portable_get_state(
|
||||
PortableState *ret,
|
||||
sd_bus_error *error);
|
||||
|
||||
int portable_get_profiles(RuntimeScope scope, char ***ret);
|
||||
int portable_get_profiles(char ***ret);
|
||||
|
||||
void portable_changes_free(PortableChange *changes, size_t n_changes);
|
||||
|
||||
|
||||
@ -48,7 +48,6 @@ static bool arg_no_block = false;
|
||||
static char **arg_extension_images = NULL;
|
||||
static bool arg_force = false;
|
||||
static bool arg_clean = false;
|
||||
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep);
|
||||
|
||||
@ -225,9 +224,9 @@ static int acquire_bus(sd_bus **bus) {
|
||||
if (*bus)
|
||||
return 0;
|
||||
|
||||
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, bus);
|
||||
r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
||||
return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM);
|
||||
|
||||
(void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
|
||||
|
||||
@ -1336,8 +1335,6 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_EXTENSION,
|
||||
ARG_FORCE,
|
||||
ARG_CLEAN,
|
||||
ARG_USER,
|
||||
ARG_SYSTEM,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -1360,8 +1357,6 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "extension", required_argument, NULL, ARG_EXTENSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "clean", no_argument, NULL, ARG_CLEAN },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{}
|
||||
};
|
||||
|
||||
@ -1473,14 +1468,6 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_clean = true;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "btrfs-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-object.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "discover-image.h"
|
||||
#include "fd-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
@ -26,15 +28,10 @@ static int property_get_pool_path(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *dir = NULL;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
(void) image_get_pool_path(m->runtime_scope, IMAGE_PORTABLE, &dir);
|
||||
|
||||
return sd_bus_message_append(reply, "s", strempty(dir));
|
||||
return sd_bus_message_append(reply, "s", "/var/lib/portables");
|
||||
}
|
||||
|
||||
static int property_get_pool_usage(
|
||||
@ -46,13 +43,19 @@ static int property_get_pool_usage(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
uint64_t usage = UINT64_MAX;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
(void) image_get_pool_usage(m->runtime_scope, IMAGE_PORTABLE, &usage);
|
||||
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd >= 0) {
|
||||
BtrfsQuotaInfo q;
|
||||
|
||||
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
|
||||
usage = q.referenced;
|
||||
}
|
||||
|
||||
return sd_bus_message_append(reply, "t", usage);
|
||||
}
|
||||
@ -66,13 +69,19 @@ static int property_get_pool_limit(
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
uint64_t size = UINT64_MAX;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
(void) image_get_pool_limit(m->runtime_scope, IMAGE_PORTABLE, &size);
|
||||
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (fd >= 0) {
|
||||
BtrfsQuotaInfo q;
|
||||
|
||||
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
|
||||
size = q.referenced_max;
|
||||
}
|
||||
|
||||
return sd_bus_message_append(reply, "t", size);
|
||||
}
|
||||
@ -87,13 +96,12 @@ static int property_get_profiles(
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
|
||||
r = portable_get_profiles(m->runtime_scope, &l);
|
||||
r = portable_get_profiles(&l);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -311,7 +319,6 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
|
||||
flags |= PORTABLE_RUNTIME;
|
||||
}
|
||||
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
@ -322,7 +329,6 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = portable_detach(
|
||||
m->runtime_scope,
|
||||
@ -368,7 +374,6 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus
|
||||
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
|
||||
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.manage-images",
|
||||
@ -379,10 +384,11 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = image_set_pool_limit(m->runtime_scope, IMAGE_MACHINE, limit);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
(void) btrfs_qgroup_set_limit("/var/lib/portables", 0, limit);
|
||||
|
||||
r = btrfs_subvol_set_subtree_quota_limit("/var/lib/portables", 0, limit);
|
||||
if (r == -ENOTTY)
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m");
|
||||
|
||||
@ -454,7 +454,6 @@ static int bus_image_method_detach(
|
||||
flags |= PORTABLE_RUNTIME;
|
||||
}
|
||||
|
||||
if (m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.portable1.attach-images",
|
||||
@ -465,7 +464,6 @@ static int bus_image_method_detach(
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = portable_detach(
|
||||
m->runtime_scope,
|
||||
@ -1017,7 +1015,7 @@ int bus_image_acquire(
|
||||
|
||||
/* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
|
||||
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_ALL && m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
polkit_action,
|
||||
@ -1068,7 +1066,7 @@ int bus_image_acquire(
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
|
||||
"Image path '%s' is not normalized.", name_or_path);
|
||||
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH && m->runtime_scope != RUNTIME_SCOPE_USER) {
|
||||
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
polkit_action,
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "path-lookup.h"
|
||||
#include "portabled.h"
|
||||
#include "service-util.h"
|
||||
#include "signal-util.h"
|
||||
@ -23,7 +22,7 @@
|
||||
static Manager* manager_unref(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
|
||||
|
||||
static int manager_new(RuntimeScope scope, Manager **ret) {
|
||||
static int manager_new(Manager **ret) {
|
||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
@ -34,13 +33,9 @@ static int manager_new(RuntimeScope scope, Manager **ret) {
|
||||
return -ENOMEM;
|
||||
|
||||
*m = (Manager) {
|
||||
.runtime_scope = scope,
|
||||
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
|
||||
};
|
||||
|
||||
r = runtime_directory_generic(scope, "systemd/portables", &m->state_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_default(&m->event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -75,8 +70,6 @@ static Manager* manager_unref(Manager *m) {
|
||||
sd_bus_flush_close_unref(m->bus);
|
||||
sd_event_unref(m->event);
|
||||
|
||||
free(m->state_dir);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
@ -86,21 +79,9 @@ static int manager_connect_bus(Manager *m) {
|
||||
assert(m);
|
||||
assert(!m->bus);
|
||||
|
||||
if (m->runtime_scope == RUNTIME_SCOPE_SYSTEM) {
|
||||
r = sd_bus_default_system(&m->bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to system bus: %m");
|
||||
} else {
|
||||
assert(m->runtime_scope == RUNTIME_SCOPE_USER);
|
||||
|
||||
r = sd_bus_default_user(&m->bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to user bus: %m");
|
||||
}
|
||||
|
||||
r = sd_bus_attach_event(m->bus, m->event, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach user bus to event loop: %m");
|
||||
|
||||
r = bus_add_implementation(m->bus, &manager_object, m);
|
||||
if (r < 0)
|
||||
@ -114,6 +95,10 @@ static int manager_connect_bus(Manager *m) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to request name: %m");
|
||||
|
||||
r = sd_bus_attach_event(m->bus, m->event, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach bus to event loop: %m");
|
||||
|
||||
(void) sd_bus_set_exit_on_disconnect(m->bus, true);
|
||||
|
||||
return 0;
|
||||
@ -140,7 +125,6 @@ static bool check_idle(void *userdata) {
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(manager_unrefp) Manager *m = NULL;
|
||||
RuntimeScope scope = RUNTIME_SCOPE_SYSTEM;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
@ -149,14 +133,17 @@ static int run(int argc, char *argv[]) {
|
||||
"Manage registrations of portable images.",
|
||||
BUS_IMPLEMENTATIONS(&manager_object,
|
||||
&log_control_object),
|
||||
&scope,
|
||||
/* runtime_scope= */ NULL,
|
||||
argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
umask(0022);
|
||||
|
||||
r = manager_new(scope, &m);
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
r = manager_new(&m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate manager object: %m");
|
||||
|
||||
|
||||
@ -17,8 +17,7 @@ typedef struct Manager {
|
||||
LIST_HEAD(Operation, operations);
|
||||
unsigned n_operations;
|
||||
|
||||
RuntimeScope runtime_scope;
|
||||
char *state_dir;
|
||||
RuntimeScope runtime_scope; /* for now always RUNTIME_SCOPE_SYSTEM */
|
||||
} Manager;
|
||||
|
||||
extern const BusObjectImplementation manager_object;
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
# The "default" security profile for services, i.e. a number of useful restrictions
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=-/etc/resolv.conf
|
||||
BindReadOnlyPaths=/run/dbus/system_bus_socket
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
|
||||
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_NET_ADMIN \
|
||||
CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_SETGID CAP_SETPCAP \
|
||||
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
@ -1,28 +0,0 @@
|
||||
# The "nonetwork" security profile for services, i.e. like "default" but without networking
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=/run/dbus/system_bus_socket
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
|
||||
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_SETGID CAP_SETPCAP \
|
||||
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
PrivateNetwork=yes
|
||||
IPAddressDeny=any
|
||||
@ -1,27 +0,0 @@
|
||||
# The "strict" security profile for services, all options turned on
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
BindLogSockets=yes
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=
|
||||
PrivateDevices=yes
|
||||
PrivateUsers=yes
|
||||
ProtectSystem=strict
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
DelegateNamespaces=no
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
PrivateNetwork=yes
|
||||
IPAddressDeny=any
|
||||
TasksMax=4
|
||||
@ -1,8 +0,0 @@
|
||||
# The "trusted" profile for services, i.e. no restrictions are applied apart from a private /tmp
|
||||
|
||||
[Service]
|
||||
MountAPIVFS=yes
|
||||
PrivateTmp=yes
|
||||
BindPaths=/run
|
||||
BindReadOnlyPaths=/etc/machine-id
|
||||
BindReadOnlyPaths=-/etc/resolv.conf
|
||||
@ -663,7 +663,7 @@ static int pick_image_search_path(
|
||||
}
|
||||
|
||||
case RUNTIME_SCOPE_USER: {
|
||||
if (!IN_SET(class, IMAGE_MACHINE, IMAGE_PORTABLE))
|
||||
if (class != IMAGE_MACHINE)
|
||||
break;
|
||||
|
||||
static const uint64_t dirs[] = {
|
||||
@ -676,7 +676,7 @@ static int pick_image_search_path(
|
||||
FOREACH_ELEMENT(d, dirs) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
r = sd_path_lookup(*d, image_dirname_to_string(class), &p);
|
||||
r = sd_path_lookup(*d, "machines", &p);
|
||||
if (r == -ENXIO) /* No XDG_RUNTIME_DIR set */
|
||||
continue;
|
||||
if (r < 0)
|
||||
@ -1469,7 +1469,7 @@ static int get_pool_directory(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unprivileged_clone(Image *i, const char *new_path) {
|
||||
static int unpriviled_clone(Image *i, const char *new_path) {
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
@ -1510,7 +1510,45 @@ static int unprivileged_clone(Image *i, const char *new_path) {
|
||||
return r;
|
||||
|
||||
/* Fork off child that moves into userns and does the copying */
|
||||
return copy_tree_at_foreign(tree_fd, target_fd, userns_fd);
|
||||
r = pidref_safe_fork_full(
|
||||
"clone-tree",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { userns_fd, tree_fd, target_fd }, 3,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_REOPEN_LOG,
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Process that was supposed to clone tree failed: %m");
|
||||
if (r == 0) {
|
||||
/* child */
|
||||
|
||||
r = namespace_enter(
|
||||
/* pidns_fd= */ -EBADF,
|
||||
/* mntns_fd= */ -EBADF,
|
||||
/* netns_fd= */ -EBADF,
|
||||
userns_fd,
|
||||
/* root_fd= */ -EBADF);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to join user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = copy_tree_at(
|
||||
tree_fd, /* from= */ NULL,
|
||||
target_fd, /* to= */ NULL,
|
||||
/* override_uid= */ UID_INVALID,
|
||||
/* override_gid= */ GID_INVALID,
|
||||
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
|
||||
/* denylist= */ NULL,
|
||||
/* subvolumes= */ NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to copy clone tree: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope scope) {
|
||||
@ -1558,7 +1596,7 @@ int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope sco
|
||||
return r;
|
||||
|
||||
if (i->foreign_uid_owned)
|
||||
r = unprivileged_clone(i, new_path);
|
||||
r = unpriviled_clone(i, new_path);
|
||||
else {
|
||||
r = btrfs_subvol_snapshot_at(
|
||||
AT_FDCWD, i->path,
|
||||
|
||||
@ -40,7 +40,6 @@
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fsck-util.h"
|
||||
#include "fstab-util.h"
|
||||
#include "gpt.h"
|
||||
#include "hash-funcs.h"
|
||||
#include "hexdecoct.h"
|
||||
@ -64,7 +63,6 @@
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
#include "resize-fs.h"
|
||||
#include "rm-rf.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "siphash24.h"
|
||||
#include "stat-util.h"
|
||||
@ -634,7 +632,7 @@ static void check_partition_flags(
|
||||
}
|
||||
#endif
|
||||
|
||||
int dissected_image_name_from_path(const char *path, char **ret) {
|
||||
static int make_image_name(const char *path, char **ret) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
@ -669,7 +667,7 @@ static int dissected_image_new(const char *path, DissectedImage **ret) {
|
||||
assert(ret);
|
||||
|
||||
if (path) {
|
||||
r = dissected_image_name_from_path(path, &name);
|
||||
r = make_image_name(path, &name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -2737,37 +2735,26 @@ int dissected_image_mount(
|
||||
assert(where);
|
||||
|
||||
if (FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS)) {
|
||||
log_debug("Checking if '%s' is an OS tree", where);
|
||||
|
||||
r = path_is_os_tree(where);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to check is '%s' is an OS tree: %m", where);
|
||||
if (r > 0) {
|
||||
log_debug("Succesfully identified '%s' as an OS tree", where);
|
||||
return r;
|
||||
if (r > 0)
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT) && m->image_name) {
|
||||
log_debug("Checking if '%s' is an extension tree", where);
|
||||
|
||||
r = extension_has_forbidden_content(where);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to check if '%s' contains content forbidden for an extension image: %m", where);
|
||||
return r;
|
||||
if (r == 0) {
|
||||
ImageClass class = IMAGE_SYSEXT;
|
||||
r = path_is_extension_tree(class, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK));
|
||||
if (r == 0) {
|
||||
class = IMAGE_CONFEXT;
|
||||
r = path_is_extension_tree(class, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK));
|
||||
}
|
||||
r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK));
|
||||
if (r == 0)
|
||||
r = path_is_extension_tree(IMAGE_CONFEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to check if '%s' is an extension tree: %m", where);
|
||||
if (r > 0) {
|
||||
log_debug("Successfully identified '%s' as a %s extension tree", where, image_class_to_string(class));
|
||||
return r;
|
||||
if (r > 0)
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
return -ENOMEDIUM;
|
||||
@ -4512,7 +4499,7 @@ Architecture dissected_image_architecture(DissectedImage *m) {
|
||||
}
|
||||
|
||||
bool dissected_image_is_portable(DissectedImage *m) {
|
||||
return m && (strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES") || strv_env_pairs_get(m->os_release, "PORTABLE_SCOPE"));
|
||||
return m && strv_env_pairs_get(m->os_release, "PORTABLE_PREFIXES");
|
||||
}
|
||||
|
||||
bool dissected_image_is_initrd(DissectedImage *m) {
|
||||
@ -5094,7 +5081,6 @@ static void partition_fields_done(PartitionFields *f) {
|
||||
|
||||
typedef struct MountImageReplyParameters {
|
||||
sd_json_variant *partitions;
|
||||
bool single_file_system;
|
||||
char *image_policy;
|
||||
uint64_t image_size;
|
||||
uint32_t sector_size;
|
||||
@ -5124,7 +5110,6 @@ int mountfsd_mount_image_fd(
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "partitions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(struct MountImageReplyParameters, partitions), SD_JSON_MANDATORY },
|
||||
{ "singleFileSystem", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct MountImageReplyParameters, single_file_system), 0 },
|
||||
{ "imagePolicy", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct MountImageReplyParameters, image_policy), 0 },
|
||||
{ "imageSize", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct MountImageReplyParameters, image_size), SD_JSON_MANDATORY },
|
||||
{ "sectorSize", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(struct MountImageReplyParameters, sector_size), SD_JSON_MANDATORY },
|
||||
@ -5188,32 +5173,13 @@ int mountfsd_mount_image_fd(
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *mount_options = NULL;
|
||||
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
|
||||
_cleanup_free_ char *filtered = NULL;
|
||||
|
||||
const char *o = mount_options_from_designator(options, i);
|
||||
if (!o)
|
||||
continue;
|
||||
|
||||
/* We communicate relaxExtensionReleaseCheck separately via the varlink API, so filter it out
|
||||
* from the mount options we pass to mountfsd. */
|
||||
if (IN_SET(i, PARTITION_ROOT, PARTITION_USR)) {
|
||||
r = fstab_filter_options(
|
||||
o,
|
||||
"x-systemd.relax-extension-release-check\0",
|
||||
/* ret_namefound= */ NULL,
|
||||
/* ret_value= */ NULL,
|
||||
/* ret_values= */ NULL,
|
||||
&filtered);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to filter mount options: %m");
|
||||
|
||||
if (isempty(filtered))
|
||||
continue;
|
||||
}
|
||||
|
||||
r = sd_json_variant_merge_objectbo(
|
||||
&mount_options,
|
||||
SD_JSON_BUILD_PAIR_STRING(partition_designator_to_string(i), filtered ?: o));
|
||||
SD_JSON_BUILD_PAIR_STRING(partition_designator_to_string(i), o));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build mount options array: %m");
|
||||
}
|
||||
@ -5230,7 +5196,6 @@ int mountfsd_mount_image_fd(
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("growFileSystems", FLAGS_SET(flags, DISSECT_IMAGE_GROWFS)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(!!ps, "imagePolicy", SD_JSON_BUILD_STRING(ps)),
|
||||
JSON_BUILD_PAIR_VARIANT_NON_NULL("mountOptions", mount_options),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("relaxExtensionReleaseChecks", mount_options_relax_extension_release_checks(options)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("veritySharing", FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(verity_data_fd >= 0, "verityDataFileDescriptor", SD_JSON_BUILD_UNSIGNED(userns_fd >= 0 ? 2 : 1)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(verity && iovec_is_set(&verity->root_hash), "verityRootHash", JSON_BUILD_IOVEC_HEX(&verity->root_hash)),
|
||||
@ -5309,7 +5274,6 @@ int mountfsd_mount_image_fd(
|
||||
};
|
||||
}
|
||||
|
||||
di->single_file_system = p.single_file_system;
|
||||
di->image_size = p.image_size;
|
||||
di->sector_size = p.sector_size;
|
||||
di->image_uuid = p.image_uuid;
|
||||
@ -5345,7 +5309,7 @@ int mountfsd_mount_image(
|
||||
return r;
|
||||
|
||||
if (!di->image_name) {
|
||||
r = dissected_image_name_from_path(path, &di->image_name);
|
||||
r = make_image_name(path, &di->image_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -5527,105 +5491,3 @@ int mountfsd_make_directory(
|
||||
|
||||
return mountfsd_make_directory_fd(fd, dirname, mode, flags, ret_directory_fd);
|
||||
}
|
||||
|
||||
int copy_tree_at_foreign(int source_fd, int target_fd, int userns_fd) {
|
||||
int r;
|
||||
|
||||
assert(source_fd >= 0);
|
||||
assert(target_fd >= 0);
|
||||
assert(userns_fd >= 0);
|
||||
|
||||
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
|
||||
* for that, which should be foreign UID range */
|
||||
|
||||
r = pidref_safe_fork_full(
|
||||
"copy-tree",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { userns_fd, source_fd, target_fd }, 3,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_REOPEN_LOG,
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
r = namespace_enter(
|
||||
/* pidns_fd= */ -EBADF,
|
||||
/* mntns_fd= */ -EBADF,
|
||||
/* netns_fd= */ -EBADF,
|
||||
userns_fd,
|
||||
/* root_fd= */ -EBADF);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to join user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = copy_tree_at(
|
||||
source_fd, /* from= */ NULL,
|
||||
target_fd, /* to= */ NULL,
|
||||
/* override_uid= */ UID_INVALID,
|
||||
/* override_gid= */ GID_INVALID,
|
||||
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
|
||||
/* denylist= */ NULL,
|
||||
/* subvolumes= */ NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to copy tree: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remove_tree_foreign(const char *path, int userns_fd) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(userns_fd >= 0);
|
||||
|
||||
_cleanup_close_ int tree_fd = -EBADF;
|
||||
r = mountfsd_mount_directory(
|
||||
path,
|
||||
userns_fd,
|
||||
DISSECT_IMAGE_FOREIGN_UID,
|
||||
&tree_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pidref_safe_fork_full(
|
||||
"rm-tree",
|
||||
/* stdio_fds= */ NULL,
|
||||
(int[]) { userns_fd, tree_fd }, 2,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* child */
|
||||
|
||||
r = namespace_enter(
|
||||
/* pidns_fd= */ -EBADF,
|
||||
/* mntns_fd= */ -EBADF,
|
||||
/* netns_fd= */ -EBADF,
|
||||
userns_fd,
|
||||
/* root_fd= */ -EBADF);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to join user namespace: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
|
||||
if (dfd < 0) {
|
||||
log_error_errno(r, "Failed to reopen tree fd: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -177,7 +177,6 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
|
||||
int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
|
||||
|
||||
int dissected_image_acquire_metadata(DissectedImage *m, int userns_fd, DissectImageFlags extra_flags);
|
||||
int dissected_image_name_from_path(const char *path, char **ret);
|
||||
|
||||
Architecture dissected_image_architecture(DissectedImage *m);
|
||||
|
||||
@ -276,6 +275,3 @@ int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags
|
||||
|
||||
int mountfsd_make_directory_fd(int parent_fd, const char *name, mode_t mode, DissectImageFlags flags, int *ret_directory_fd);
|
||||
int mountfsd_make_directory(const char *path, mode_t mode, DissectImageFlags flags, int *ret_directory_fd);
|
||||
|
||||
int copy_tree_at_foreign(int source_fd, int target_fd, int userns_fd);
|
||||
int remove_tree_foreign(const char *path, int userns_fd);
|
||||
|
||||
@ -183,7 +183,7 @@ int fdset_new_fill(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
|
||||
"Failed to open /proc/self/fd/, /proc/ is not mounted.");
|
||||
|
||||
return log_debug_errno(errno, "Failed to open /proc/self/fd/: %m");
|
||||
return log_debug_errno(errno, "Failed to open /proc/self/fd/: %m ");
|
||||
}
|
||||
|
||||
s = fdset_new();
|
||||
@ -214,16 +214,13 @@ int fdset_new_fill(
|
||||
|
||||
fl = RET_NERRNO(fcntl(fd, F_GETFD));
|
||||
if (fl < 0) {
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
(void) fd_get_path(fd, &path);
|
||||
log_debug_errno(fl, "Failed to get flags of fd=%d (%s): %m",
|
||||
return log_debug_errno(fl,
|
||||
"Failed to get flag of fd=%d (%s): %m ",
|
||||
fd, strna(path));
|
||||
}
|
||||
|
||||
return fl;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(fl, FD_CLOEXEC) != !!filter_cloexec)
|
||||
continue;
|
||||
}
|
||||
@ -232,28 +229,22 @@ int fdset_new_fill(
|
||||
if (filter_cloexec <= 0) {
|
||||
r = fd_cloexec(fd, true);
|
||||
if (r < 0) {
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
(void) fd_get_path(fd, &path);
|
||||
log_debug_errno(r, "Failed to set CLOEXEC flag on fd=%d (%s): %m",
|
||||
return log_debug_errno(r,
|
||||
"Failed to set CLOEXEC flag fd=%d (%s): %m ",
|
||||
fd, strna(path));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
r = fdset_put(s, fd);
|
||||
if (r < 0) {
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
(void) fd_get_path(fd, &path);
|
||||
log_debug_errno(r, "Failed to put fd=%d (%s) into fdset: %m",
|
||||
return log_debug_errno(r,
|
||||
"Failed to put fd=%d (%s) into fdset: %m ",
|
||||
fd, strna(path));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(s);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "iterator.h"
|
||||
#include "shared-forward.h"
|
||||
#include "iterator.h"
|
||||
|
||||
FDSet* fdset_new(void);
|
||||
FDSet* fdset_free(FDSet *s);
|
||||
|
||||
@ -690,6 +690,7 @@ int loop_device_make_by_path_at(
|
||||
bool direct = false;
|
||||
|
||||
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
|
||||
assert(path);
|
||||
assert(ret);
|
||||
assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY));
|
||||
|
||||
@ -729,8 +730,8 @@ int loop_device_make_by_path_at(
|
||||
} else if (open_flags < 0)
|
||||
open_flags = O_RDWR;
|
||||
|
||||
log_debug("Opened %s in %s access mode%s, with O_DIRECT %s%s.",
|
||||
path ?: "loop device",
|
||||
log_debug("Opened '%s' in %s access mode%s, with O_DIRECT %s%s.",
|
||||
path,
|
||||
open_flags == O_RDWR ? "O_RDWR" : "O_RDONLY",
|
||||
open_flags != rdwr_flags ? " (O_RDWR was requested but not allowed)" : "",
|
||||
direct ? "enabled" : "disabled",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sd-json.h"
|
||||
|
||||
@ -13,21 +12,8 @@
|
||||
sd_json_dispatch_flags_t nss_json_dispatch_flags = SD_JSON_ALLOW_EXTENSIONS;
|
||||
|
||||
static void log_setup_nss_internal(void) {
|
||||
int r;
|
||||
|
||||
log_set_assert_return_is_critical_from_env();
|
||||
log_parse_environment_variables();
|
||||
|
||||
const char *e = getenv("SYSTEMD_NSS_LOG_LEVEL");
|
||||
if (e) {
|
||||
/* NSS plugins are linked statically to all of our own libraries so this will only override
|
||||
* the log level for the NSS plugin, and not for the entire systemd binary, since each will
|
||||
* have their own log_level TLS variable. */
|
||||
r = log_set_max_level_from_string(e);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse NSS log level '%s', ignoring: %m", e);
|
||||
}
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
nss_json_dispatch_flags = SD_JSON_LOG;
|
||||
}
|
||||
|
||||
@ -5,70 +5,10 @@
|
||||
#include "alloc-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "path-lookup.h"
|
||||
#include "portable-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int portable_profile_dirs(RuntimeScope scope, char ***ret) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
switch (scope) {
|
||||
|
||||
case RUNTIME_SCOPE_SYSTEM:
|
||||
r = strv_from_nulstr(&dirs, PORTABLE_PROFILE_DIRS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case RUNTIME_SCOPE_USER: {
|
||||
_cleanup_free_ char *d = NULL;
|
||||
|
||||
r = xdg_user_config_dir("systemd/portable/profile", &d);
|
||||
if (r < 0 && r != -ENXIO)
|
||||
return r;
|
||||
if (r >= 0) {
|
||||
r = strv_consume(&dirs, TAKE_PTR(d));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = xdg_user_runtime_dir("systemd/portable/profile", &d);
|
||||
if (r < 0 && r != -ENXIO)
|
||||
return r;
|
||||
if (r >= 0) {
|
||||
r = strv_consume(&dirs, TAKE_PTR(d));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_fallthrough_;
|
||||
}
|
||||
|
||||
case RUNTIME_SCOPE_GLOBAL:
|
||||
r = strv_extend_strv(
|
||||
&dirs,
|
||||
CONF_PATHS_STRV("systemd/user/portable/profile"),
|
||||
/* filter_duplicates= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(dirs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int find_portable_profile(RuntimeScope scope, const char *name, const char *unit, char **ret_path) {
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
int find_portable_profile(const char *name, const char *unit, char **ret_path) {
|
||||
const char *dot;
|
||||
int r;
|
||||
|
||||
@ -77,14 +17,10 @@ int find_portable_profile(RuntimeScope scope, const char *name, const char *unit
|
||||
|
||||
assert_se(dot = strrchr(unit, '.'));
|
||||
|
||||
r = portable_profile_dirs(scope, &dirs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(p, dirs) {
|
||||
NULSTR_FOREACH(p, PORTABLE_PROFILE_DIRS) {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
joined = strjoin(*p, "/", name, "/", dot + 1, ".conf");
|
||||
joined = strjoin(p, "/", name, "/", dot + 1, ".conf");
|
||||
if (!joined)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@ -6,5 +6,4 @@
|
||||
|
||||
#define PORTABLE_PROFILE_DIRS CONF_PATHS_NULSTR("systemd/portable/profile")
|
||||
|
||||
int portable_profile_dirs(RuntimeScope scope, char ***ret);
|
||||
int find_portable_profile(RuntimeScope scope, const char *name, const char *unit, char **ret_path);
|
||||
int find_portable_profile(const char *name, const char *unit, char **ret_path);
|
||||
|
||||
@ -62,8 +62,6 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
SD_VARLINK_DEFINE_INPUT(imagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The mount options to be used for the partitions of the image, keyed by the partition designator. Requires elevated privileges via polkit if specified, the polkit request details will list them in the 'mount_options' field."),
|
||||
SD_VARLINK_DEFINE_INPUT(mountOptions, SD_VARLINK_STRING, SD_VARLINK_MAP|SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether to relax the extension release checks when mounting the image."),
|
||||
SD_VARLINK_DEFINE_INPUT(relaxExtensionReleaseChecks, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether to automatically reuse already set up dm-verity devices that share the same roothash."),
|
||||
SD_VARLINK_DEFINE_INPUT(veritySharing, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("File descriptor of the file containing the dm-verity data, if the image is a bare filesystem rather than a DDI."),
|
||||
@ -75,8 +73,6 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("An array with information about contained partitions that have been prepared for mounting, as well as their mount file descriptors."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("Indicates whether the image contains only a single file system, i.e. no partition table."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(singleFileSystem, SD_VARLINK_BOOL, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The used image policy."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(imagePolicy, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The size of the image in bytes."),
|
||||
|
||||
@ -148,7 +148,6 @@ int sd_dhcp_client_set_fallback_lease_lifetime(
|
||||
int sd_dhcp_client_set_bootp(
|
||||
sd_dhcp_client *client,
|
||||
int bootp);
|
||||
int sd_dhcp_client_set_send_release(sd_dhcp_client *client, int enable);
|
||||
|
||||
int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
|
||||
int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
|
||||
@ -156,6 +155,7 @@ int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
|
||||
int sd_dhcp_client_is_running(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_stop(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_start(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_send_release(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_send_decline(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_send_renew(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have);
|
||||
|
||||
@ -382,7 +382,7 @@ static int process_magic_file(
|
||||
* so that people fix it. After all we want to retain liberty to maybe one day place some useful data
|
||||
* inside it */
|
||||
if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0)
|
||||
log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn);
|
||||
log_warning("Hash of best before marker file '%s' has unexpected value, ignoring.", fn);
|
||||
|
||||
struct tm parsed_tm = {};
|
||||
const char *n = strptime(e, "%Y-%m-%d", &parsed_tm);
|
||||
|
||||
@ -11,6 +11,6 @@ ExecStart=@command@
|
||||
ExecStartPost=/usr/lib/systemd/tests/testdata/units/post.sh
|
||||
Type=oneshot
|
||||
MemoryAccounting=@memory-accounting@
|
||||
Environment=SYSTEMD_PAGER= SYSTEMD_NSS_LOG_LEVEL=info @env@
|
||||
Environment=SYSTEMD_PAGER= @env@
|
||||
UnsetEnvironment=JOURNAL_STREAM
|
||||
@service@
|
||||
|
||||
@ -1,324 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
# shellcheck disable=SC2233,SC2235
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
if [[ ! -f /usr/lib/systemd/system/systemd-mountfsd.socket ]] ||
|
||||
[[ ! -f /usr/lib/systemd/system/systemd-nsresourced.socket ]] ||
|
||||
! command -v mksquashfs ||
|
||||
! grep -q bpf /sys/kernel/security/lsm ||
|
||||
! find /usr/lib* -name libbpf.so.1 2>/dev/null | grep . ||
|
||||
systemd-analyze compare-versions "$(uname -r)" lt 6.5 ||
|
||||
systemd-analyze compare-versions "$(pkcheck --version | awk '{print $3}')" lt 124 ||
|
||||
systemctl --version | grep -- "-BTF" >/dev/null; then
|
||||
echo "Skipping mountfsd/nsresourced tests"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl start systemd-mountfsd.socket systemd-nsresourced.socket
|
||||
|
||||
# Arrays cannot be exported, so redefine in each test script
|
||||
ARGS=()
|
||||
if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
|
||||
# If we're running under sanitizers, we need to use a less restrictive
|
||||
# profile, otherwise LSan syscall would get blocked by seccomp
|
||||
ARGS+=(--profile=trusted)
|
||||
fi
|
||||
|
||||
# To be able to mount images as an unprivileged user we need verity sidecars so generate them for app1 which
|
||||
# doesn't have them by default.
|
||||
veritysetup format /tmp/app1.raw /tmp/app1.verity --root-hash-file /tmp/app1.roothash
|
||||
openssl smime -sign -nocerts -noattr -binary \
|
||||
-in /tmp/app1.roothash \
|
||||
-inkey /usr/share/mkosi.key \
|
||||
-signer /usr/share/mkosi.crt \
|
||||
-outform der \
|
||||
-out /tmp/app1.roothash.p7s
|
||||
chmod go+r /tmp/app1*
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
|
||||
rm -f /tmp/app1.verity /tmp/app1.roothash /tmp/app1.roothash.p7s
|
||||
loginctl disable-linger testuser
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
# For unprivileged user manager
|
||||
loginctl enable-linger testuser
|
||||
|
||||
systemctl start user@4711.service
|
||||
|
||||
portablectl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 portablectl --user "$@"
|
||||
}
|
||||
|
||||
busctl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 busctl --user "$@"
|
||||
}
|
||||
|
||||
systemctl_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 systemctl --user "$@"
|
||||
}
|
||||
|
||||
runas_user() {
|
||||
runas testuser env XDG_RUNTIME_DIR=/run/user/4711 "$@"
|
||||
}
|
||||
|
||||
# Start the user portable daemon
|
||||
systemctl_user start dbus-org.freedesktop.portable1.service
|
||||
|
||||
: "Test basic attach, reattach and detach for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime /usr/share/minimal_0.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached minimal-app0
|
||||
portablectl_user inspect /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-foo.service
|
||||
systemctl_user is-active minimal-app0-bar.service && exit 1
|
||||
|
||||
# Ensure pinning by policy works
|
||||
cat /run/user/4711/systemd/user.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
grep -q -F 'root=signed+squashfs:' /run/user/4711/systemd/user.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached minimal-app0
|
||||
portablectl_user inspect /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-bar.service
|
||||
systemctl_user is-active minimal-app0-foo.service && exit 1
|
||||
|
||||
portablectl_user list | grep -F "minimal_1" >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' >/dev/null
|
||||
|
||||
portablectl_user detach --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user list | grep -F "No images." >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 >/dev/null
|
||||
|
||||
: "Test --force for user portable services"
|
||||
|
||||
runas_user mkdir -p /run/user/4711/systemd/user.attached/minimal-app0.service.d/
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service.d/10-profile.conf >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
runas_user tee /run/user/4711/systemd/user.attached/minimal-app0.service.d/20-portable.conf >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Minimal App 0
|
||||
EOF
|
||||
systemctl_user daemon-reload
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --force --now --runtime /usr/share/minimal_0.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached --force minimal-app0
|
||||
portablectl_user inspect --force /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-foo.service
|
||||
systemctl_user is-active minimal-app0-bar.service && exit 1
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user is-attached --force minimal-app0
|
||||
portablectl_user inspect --force /usr/share/minimal_0.raw minimal-app0.service
|
||||
systemctl_user is-active minimal-app0.service
|
||||
systemctl_user is-active minimal-app0-bar.service
|
||||
systemctl_user is-active minimal-app0-foo.service && exit 1
|
||||
|
||||
portablectl_user list | grep -F "minimal_1" >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' >/dev/null
|
||||
|
||||
portablectl_user detach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl_user list | grep -F "No images." >/dev/null
|
||||
busctl_user tree org.freedesktop.portable1 --no-pager | grep -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 >/dev/null
|
||||
|
||||
: "Test extension images for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
# Ensure pinning by policy works
|
||||
grep -q -F 'RootImagePolicy=root=signed+squashfs:' /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
grep -q -F 'ExtensionImagePolicy=root=signed+squashfs:' /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/user/4711/systemd/user.attached/app0.service.d/20-portable.conf
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
: "Test versioned extension images for user portable services"
|
||||
|
||||
cp /tmp/app0.raw /tmp/app0_1.0.raw
|
||||
cp /tmp/app0.verity /tmp/app0_1.0.verity
|
||||
cp /tmp/app0.roothash /tmp/app0_1.0.roothash
|
||||
cp /tmp/app0.roothash.p7s /tmp/app0_1.0.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension app0_1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0_1.0.raw /usr/share/minimal_1.raw app0
|
||||
rm -f /tmp/app0_1.0*
|
||||
|
||||
: "Test reattach with version changes for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
# Ensure that adding or removing a version to the image doesn't break reattaching
|
||||
cp /tmp/app1.raw /tmp/app1_2.raw
|
||||
cp /tmp/app1.verity /tmp/app1_2.verity
|
||||
cp /tmp/app1.roothash /tmp/app1_2.roothash
|
||||
cp /tmp/app1.roothash.p7s /tmp/app1_2.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1_2.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_2 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_1.raw app1
|
||||
|
||||
: "Test --no-reload for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --force --no-reload --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
portablectl_user "${ARGS[@]}" attach --force --no-reload --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
systemctl_user daemon-reload
|
||||
systemctl_user restart app1.service
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
# : "Test vpick for user portable services"
|
||||
|
||||
mkdir -p /tmp/app1.v/
|
||||
cp /tmp/app1.raw /tmp/app1.v/app1_1.0.raw
|
||||
cp /tmp/app1.verity /tmp/app1.v/app1_1.0.verity
|
||||
cp /tmp/app1.roothash /tmp/app1.v/app1_1.0.roothash
|
||||
cp /tmp/app1.roothash.p7s /tmp/app1.v/app1_1.0.roothash.p7s
|
||||
cp /tmp/app1_2.raw /tmp/app1.v/app1_2.0.raw
|
||||
cp /tmp/app1_2.verity /tmp/app1.v/app1_2.0.verity
|
||||
cp /tmp/app1_2.roothash /tmp/app1.v/app1_2.0.roothash
|
||||
cp /tmp/app1_2.roothash.p7s /tmp/app1.v/app1_2.0.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_2.0.raw minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
rm -f /tmp/app1.v/app1_2.0*
|
||||
portablectl_user "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_1.raw app1
|
||||
|
||||
systemctl_user is-active app1.service
|
||||
status="$(portablectl_user is-attached --extension app1_1.0.raw minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app1.v/ /usr/share/minimal_0.raw app1
|
||||
rm -f /tmp/app1.v/app1_1.0*
|
||||
|
||||
: "Test extension-release.NAME override for user portable services"
|
||||
|
||||
cp /tmp/app0.raw /tmp/app10.raw
|
||||
cp /tmp/app0.verity /tmp/app10.verity
|
||||
cp /tmp/app0.roothash /tmp/app10.roothash
|
||||
cp /tmp/app0.roothash.p7s /tmp/app10.roothash.p7s
|
||||
portablectl_user "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/app10.raw" >/dev/null
|
||||
|
||||
# Ensure that we can detach even when an image has been deleted already (stop the unit manually as
|
||||
# portablectl won't find it)
|
||||
rm -f /tmp/app10*
|
||||
systemctl_user stop app0.service
|
||||
portablectl_user detach --force --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
: "Test confext images for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
systemctl_user is-active app0.service
|
||||
status="$(portablectl_user is-attached --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
portablectl_user inspect --force --cat --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/conf0.raw" >/dev/null
|
||||
|
||||
portablectl_user detach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0
|
||||
|
||||
: "Test multiple portables sharing the same base image for user portable services"
|
||||
|
||||
portablectl_user "${ARGS[@]}" attach --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
|
||||
portablectl_user "${ARGS[@]}" attach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app1
|
||||
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
(! portablectl_user detach --runtime /usr/share/minimal_0.raw app)
|
||||
|
||||
status="$(portablectl_user is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
# Ensure 'portablectl list' shows the correct status for both images
|
||||
portablectl_user list
|
||||
portablectl_user list | grep -F "minimal_0" | grep -F "attached-runtime" >/dev/null
|
||||
portablectl_user list | grep -F "app0" | grep -F "attached-runtime" >/dev/null
|
||||
portablectl_user list | grep -F "app1" | grep -F "attached-runtime" >/dev/null
|
||||
|
||||
portablectl_user detach --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app
|
||||
|
||||
status="$(portablectl_user is-attached --extension app1 minimal_0)"
|
||||
[[ "${status}" == "attached-runtime" ]]
|
||||
|
||||
portablectl_user detach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app
|
||||
@ -296,25 +296,22 @@ install_extension_images() {
|
||||
fi
|
||||
|
||||
local initdir="/var/tmp/app0"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
echo "$version_id" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
(
|
||||
echo "$version_id"
|
||||
echo "SYSEXT_IMAGE_ID=app"
|
||||
) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
for scope in system user; do
|
||||
mkdir -p "$initdir/usr/lib/systemd/$scope"
|
||||
cat >"$initdir/usr/lib/systemd/$scope/app0.service" <<EOF
|
||||
cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/script0.sh
|
||||
TemporaryFileSystem=/var/lib /home
|
||||
TemporaryFileSystem=/var/lib
|
||||
StateDirectory=app0
|
||||
RuntimeDirectory=app0
|
||||
EOF
|
||||
done
|
||||
cat >"$initdir/opt/script0.sh" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
@ -354,7 +351,7 @@ EOF
|
||||
chmod go+r /tmp/conf0*
|
||||
|
||||
initdir="/var/tmp/app1"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt"
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
(
|
||||
echo "$version_id"
|
||||
@ -364,18 +361,14 @@ EOF
|
||||
echo "PORTABLE_PREFIXES=app1"
|
||||
) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
for scope in system user; do
|
||||
mkdir -p "$initdir/usr/lib/systemd/$scope"
|
||||
cat >"$initdir/usr/lib/systemd/$scope/app1.service" <<EOF
|
||||
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/script1.sh
|
||||
TemporaryFileSystem=/home
|
||||
StateDirectory=app1
|
||||
RuntimeDirectory=app1
|
||||
EOF
|
||||
done
|
||||
cat >"$initdir/opt/script1.sh" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
@ -20,7 +20,6 @@ ExecStart={{LIBEXECDIR}}/systemd-portabled
|
||||
BusName=org.freedesktop.portable1
|
||||
CapabilityBoundingSet=CAP_KILL CAP_SYS_PTRACE CAP_SYS_ADMIN CAP_SETGID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
RestrictRealtime=yes
|
||||
|
||||
@ -38,11 +38,6 @@ units = [
|
||||
'conditions' : ['ENABLE_IMPORTD'],
|
||||
'symlinks' : ['sockets.target.wants/'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-portabled.service.in',
|
||||
'conditions' : ['ENABLE_PORTABLED'],
|
||||
'symlinks' : ['dbus-org.freedesktop.portable1.service'],
|
||||
},
|
||||
{ 'file' : 'paths.target' },
|
||||
{ 'file' : 'printer.target' },
|
||||
{ 'file' : 'session.slice' },
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Portable Service Manager
|
||||
Documentation=man:systemd-portabled.service(8)
|
||||
Documentation=man:org.freedesktop.portable1(5)
|
||||
|
||||
[Service]
|
||||
ExecStart={{LIBEXECDIR}}/systemd-portabled --user
|
||||
BusName=org.freedesktop.portable1
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
SystemCallFilter=@system-service @mount
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
LockPersonality=yes
|
||||
IPAddressDeny=any
|
||||
{{SERVICE_WATCHDOG}}
|
||||
Loading…
x
Reference in New Issue
Block a user