1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-03 17:47:28 +03:00

Merge pull request #18302 from bluca/mount_image_runtime

systemctl/core: add DBUS method to mount images without service restart
This commit is contained in:
Luca Boccassi 2021-01-22 17:49:11 +00:00 committed by GitHub
commit 95457dc13c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 426 additions and 161 deletions

View File

@ -121,6 +121,12 @@ node /org/freedesktop/systemd1 {
in s destination,
in b read_only,
in b mkdir);
MountImageUnit(in s name,
in s source,
in s destination,
in b read_only,
in b mkdir,
in a(ss) options);
RefUnit(in s name);
UnrefUnit(in s name);
StartTransientUnit(in s name,
@ -774,6 +780,8 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-method" generated="True" extra-ref="BindMountUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MountImageUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="RefUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="UnrefUnit()"/>
@ -1166,6 +1174,9 @@ node /org/freedesktop/systemd1 {
<para><function>BindMountUnit()</function> can be used to bind mount new files or directories into
a running service mount namespace.</para>
<para><function>MountImageUnit()</function> can be used to mount new images into a running service
mount namespace.</para>
<para><function>KillUnit()</function> may be used to kill (i.e. send a signal to) all processes of a
unit. It takes the unit <varname>name</varname>, an enum <varname>who</varname> and a UNIX
<varname>signal</varname> number to send. The <varname>who</varname> enum is one of
@ -2207,6 +2218,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
in s destination,
in b read_only,
in b mkdir);
MountImage(in s source,
in s destination,
in b read_only,
in b mkdir,
in a(ss) options);
GetProcesses(out a(sus) processes);
AttachProcesses(in s subcgroup,
in au pids);
@ -3268,6 +3284,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-method" generated="True" extra-ref="BindMount()"/>
<variablelist class="dbus-method" generated="True" extra-ref="MountImage()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
@ -3829,12 +3847,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<refsect2>
<title>Methods</title>
<para><function>BindMount()</function> implements the same operation as the respective method on the
<interfacename>Manager</interfacename> object (see above). However, this method operates on the service
object and hence does not take a unit name parameter. Invoking the methods directly on the Manager
object has the advantage of not requiring a <function>GetUnit()</function> call to get the unit object
for a specific unit name. Calling the methods on the Manager object is hence a round trip
optimization.</para>
<para><function>BindMount()</function> and <function>MountImage()</function> implement the same operations
as the respective methods on the <interfacename>Manager</interfacename> object (see above). However, these
methods operate on the service object and hence do not take a unit name parameter. Invoking the methods
directly on the Manager object has the advantage of not requiring a <function>GetUnit()</function> call
to get the unit object for a specific unit name. Calling the methods on the Manager object is hence a round
trip optimization.</para>
</refsect2>
<refsect2>

View File

@ -567,6 +567,24 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<option>ExecStartPre=</option>, etc.) </para></listitem>
</varlistentry>
<varlistentry>
<term><command>mount-image</command> <replaceable>UNIT</replaceable> <replaceable>IMAGE</replaceable> [<replaceable>PATH</replaceable> [<replaceable>PARTITION_NAME</replaceable>:<replaceable>MOUNT_OPTIONS</replaceable>]]</term>
<listitem><para>Mounts an image from the host into the specified unit's view. The first path argument is the source
image on the host, the second path argument is the destination directory in the unit's view (ie: inside
<option>RootImage=</option>/<option>RootDirectory=</option>). Any following argument is interpreted as a
colon-separated tuple of partition name and comma-separated list of mount options for that partition. The format is the
same as the service <option>MountImages=</option> setting. When combined with the <option>--read-only</option> switch, a
ready-only mount is created. When combined with the <option>--mkdir</option> switch, the destination path is first
created before the mount is applied. Note that this option is currently only supported for units that run within a mount
namespace (e.g.: with <option>RootImage=</option>, <option>PrivateMounts=</option>, etc.).
Note that the namespace mentioned here, where the image mount will be added to, is the one where the main service
process runs, as other processes run in distinct namespaces (e.g.: <option>ExecReload=</option>,
<option>ExecStartPre=</option>, etc.). Example:
<programlisting>systemctl mount-image foo.service /tmp/img.raw /var/lib/image root:ro,nosuid</programlisting>
<programlisting>systemctl mount-image --mkdir bar.service /tmp/img.raw /var/lib/baz/img</programlisting></para></listitem>
</varlistentry>
<varlistentry>
<term><command>service-log-level</command> <replaceable>SERVICE</replaceable> [<replaceable>LEVEL</replaceable>]</term>

View File

@ -214,7 +214,7 @@ _systemctl () {
list-timers list-units list-unit-files poweroff
reboot rescue show-environment suspend get-default
is-system-running preset-all'
[FILE]='link switch-root bind'
[FILE]='link switch-root bind mount-image'
[TARGETS]='set-default'
[MACHINES]='list-machines'
[LOG_LEVEL]='log-level'

View File

@ -32,6 +32,7 @@
"list-dependencies:Show unit dependency tree"
"clean:Remove configuration, state, cache, logs or runtime data of units"
"bind:Bind mount a path from the host into a unit's namespace"
"mount-image:Mount an image from the host into a unit's namespace"
)
local -a machine_commands=(
@ -383,6 +384,10 @@ done
_files
}
(( $+functions[_systemctl_mount-image] )) || _systemctl_mount-image() {
_files
}
# no systemctl completion for:
# [STANDALONE]='daemon-reexec daemon-reload default
# emergency exit halt kexec list-jobs list-units

View File

@ -1497,74 +1497,6 @@ static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint6
static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(namespace_flag, "t", uint64_t, unsigned long, "%" PRIu64, namespace_flags_to_string);
static BUS_DEFINE_SET_TRANSIENT_TO_STRING(mount_flags, "t", uint64_t, unsigned long, "%" PRIu64, mount_propagation_flags_to_string_with_check);
/* ret_format_str is an accumulator, so if it has any pre-existing content, new options will be appended to it */
static int read_mount_options(sd_bus_message *message, sd_bus_error *error, MountOptions **ret_options, char **ret_format_str, const char *separator) {
_cleanup_(mount_options_free_allp) MountOptions *options = NULL;
_cleanup_free_ char *format_str = NULL;
const char *mount_options, *partition;
int r;
assert(message);
assert(ret_options);
assert(ret_format_str);
assert(separator);
r = sd_bus_message_enter_container(message, 'a', "(ss)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(ss)", &partition, &mount_options)) > 0) {
_cleanup_free_ char *previous = NULL, *escaped = NULL;
_cleanup_free_ MountOptions *o = NULL;
PartitionDesignator partition_designator;
if (chars_intersect(mount_options, WHITESPACE))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid mount options string, contains whitespace character(s): %s", mount_options);
partition_designator = partition_designator_from_string(partition);
if (partition_designator < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid partition name %s", partition);
/* Need to store them in the unit with the escapes, so that they can be parsed again */
escaped = shell_escape(mount_options, ":");
if (!escaped)
return -ENOMEM;
previous = TAKE_PTR(format_str);
format_str = strjoin(previous, previous ? separator : "", partition, ":", escaped);
if (!format_str)
return -ENOMEM;
o = new(MountOptions, 1);
if (!o)
return -ENOMEM;
*o = (MountOptions) {
.partition_designator = partition_designator,
.options = strdup(mount_options),
};
if (!o->options)
return -ENOMEM;
LIST_APPEND(mount_options, options, TAKE_PTR(o));
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!LIST_IS_EMPTY(options)) {
char *final = strjoin(*ret_format_str, !isempty(*ret_format_str) ? separator : "", format_str);
if (!final)
return -ENOMEM;
free_and_replace(*ret_format_str, final);
LIST_JOIN(mount_options, *ret_options, options);
}
return 0;
}
int bus_exec_context_set_transient_property(
Unit *u,
ExecContext *c,
@ -1599,7 +1531,7 @@ int bus_exec_context_set_transient_property(
_cleanup_(mount_options_free_allp) MountOptions *options = NULL;
_cleanup_free_ char *format_str = NULL;
r = read_mount_options(message, error, &options, &format_str, " ");
r = bus_read_mount_options(message, error, &options, &format_str, " ");
if (r < 0)
return r;
@ -3407,7 +3339,7 @@ int bus_exec_context_set_transient_property(
return -ENOMEM;
free_and_replace(format_str, tuple);
r = read_mount_options(message, error, &options, &format_str, ":");
r = bus_read_mount_options(message, error, &options, &format_str, ":");
if (r < 0)
return r;

View File

@ -731,6 +731,11 @@ static int method_bind_mount_unit(sd_bus_message *message, void *userdata, sd_bu
return method_generic_unit_operation(message, userdata, error, bus_service_method_bind_mount, GENERIC_UNIT_VALIDATE_LOADED);
}
static int method_mount_image_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Only add mounts on fully loaded units */
return method_generic_unit_operation(message, userdata, error, bus_service_method_mount_image, GENERIC_UNIT_VALIDATE_LOADED);
}
static int method_ref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Only allow reffing of fully loaded units, and make sure reffing a unit loads it. */
return method_generic_unit_operation(message, userdata, error, bus_unit_method_ref, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
@ -2776,6 +2781,17 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,,
method_bind_mount_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("MountImageUnit",
"sssbba(ss)",
SD_BUS_PARAM(name)
SD_BUS_PARAM(source)
SD_BUS_PARAM(destination)
SD_BUS_PARAM(read_only)
SD_BUS_PARAM(mkdir)
SD_BUS_PARAM(options),
NULL,,
method_mount_image_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("RefUnit",
"s",
SD_BUS_PARAM(name),

View File

@ -95,9 +95,10 @@ static int property_get_exit_status_set(
return sd_bus_message_close_container(reply);
}
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
int read_only, make_file_or_directory;
static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_bus_error *error, bool is_image) {
_cleanup_(mount_options_free_allp) MountOptions *options = NULL;
const char *dest, *src, *propagate_directory;
int read_only, make_file_or_directory;
Unit *u = userdata;
ExecContext *c;
pid_t unit_pid;
@ -120,16 +121,22 @@ int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
if (!path_is_absolute(src) || !path_is_normalized(src))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
if (isempty(dest))
if (!is_image && isempty(dest))
dest = src;
else if (!path_is_absolute(dest) || !path_is_normalized(dest))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
if (is_image) {
r = bus_read_mount_options(message, error, &options, NULL, "");
if (r < 0)
return r;
}
r = bus_verify_manage_units_async_full(
u,
"bind-mount",
is_image ? "mount-image" : "bind-mount",
CAP_SYS_ADMIN,
N_("Authentication is required to bind mount on '$(unit)'."),
N_("Authentication is required to mount on '$(unit)'."),
true,
message,
error);
@ -158,16 +165,30 @@ int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not running");
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
r = bind_mount_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory);
if (is_image)
r = mount_image_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory, options);
else
r = bind_mount_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
return sd_bus_reply_method_return(message, NULL);
}
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_service_method_mount(message, userdata, error, false);
}
int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_service_method_mount(message, userdata, error, true);
}
const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@ -233,6 +254,17 @@ const sd_bus_vtable bus_service_vtable[] = {
bus_service_method_bind_mount,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("MountImage",
"ssbba(ss)",
SD_BUS_PARAM(source)
SD_BUS_PARAM(destination)
SD_BUS_PARAM(read_only)
SD_BUS_PARAM(mkdir)
SD_BUS_PARAM(options),
NULL,,
bus_service_method_mount_image,
SD_BUS_VTABLE_UNPRIVILEGED),
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),

View File

@ -10,4 +10,5 @@ extern const sd_bus_vtable bus_service_vtable[];
int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitWriteFlags flags, sd_bus_error *error);
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_commit_properties(Unit *u);

View File

@ -3,6 +3,7 @@
#include "bus-polkit.h"
#include "bus-util.h"
#include "dbus-util.h"
#include "escape.h"
#include "parse-util.h"
#include "path-util.h"
#include "unit-printf.h"
@ -186,3 +187,78 @@ int bus_verify_manage_units_async_full(
&u->manager->polkit_registry,
error);
}
/* ret_format_str is an accumulator, so if it has any pre-existing content, new options will be appended to it */
int bus_read_mount_options(
sd_bus_message *message,
sd_bus_error *error,
MountOptions **ret_options,
char **ret_format_str,
const char *separator) {
_cleanup_(mount_options_free_allp) MountOptions *options = NULL;
_cleanup_free_ char *format_str = NULL;
const char *mount_options, *partition;
int r;
assert(message);
assert(ret_options);
assert(separator);
r = sd_bus_message_enter_container(message, 'a', "(ss)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(ss)", &partition, &mount_options)) > 0) {
_cleanup_free_ char *previous = NULL, *escaped = NULL;
_cleanup_free_ MountOptions *o = NULL;
PartitionDesignator partition_designator;
if (chars_intersect(mount_options, WHITESPACE))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid mount options string, contains whitespace character(s): %s", mount_options);
partition_designator = partition_designator_from_string(partition);
if (partition_designator < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid partition name %s", partition);
/* Need to store the options with the escapes, so that they can be parsed again */
escaped = shell_escape(mount_options, ":");
if (!escaped)
return -ENOMEM;
previous = TAKE_PTR(format_str);
format_str = strjoin(previous, previous ? separator : "", partition, ":", escaped);
if (!format_str)
return -ENOMEM;
o = new(MountOptions, 1);
if (!o)
return -ENOMEM;
*o = (MountOptions) {
.partition_designator = partition_designator,
.options = strdup(mount_options),
};
if (!o->options)
return -ENOMEM;
LIST_APPEND(mount_options, options, TAKE_PTR(o));
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!LIST_IS_EMPTY(options)) {
if (ret_format_str) {
char *final = strjoin(*ret_format_str, !isempty(*ret_format_str) ? separator : "", format_str);
if (!final)
return -ENOMEM;
free_and_replace(*ret_format_str, final);
}
LIST_JOIN(mount_options, *ret_options, options);
}
return 0;
}

View File

@ -3,6 +3,7 @@
#include "sd-bus.h"
#include "dissect-image.h"
#include "unit.h"
int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
@ -249,3 +250,5 @@ static inline int bus_set_transient_usec_fix_0(Unit *u, const char *name, usec_t
return bus_set_transient_usec_internal(u, name, p, true, message, flags, error);
}
int bus_verify_manage_units_async_full(Unit *u, const char *verb, int capability, const char *polkit_message, bool interactive, sd_bus_message *call, sd_bus_error *error);
int bus_read_mount_options(sd_bus_message *message, sd_bus_error *error, MountOptions **ret_options, char **ret_format_str, const char *separator);

View File

@ -962,75 +962,13 @@ static int mount_run(const MountEntry *m) {
}
static int mount_images(const MountEntry *m) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
DissectImageFlags dissect_image_flags;
int r;
assert(m);
r = verity_settings_load(&verity, mount_entry_source(m), NULL, NULL);
r = verity_dissect_and_mount(mount_entry_source(m), mount_entry_path(m), m->image_options);
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
dissect_image_flags =
(m->read_only ? DISSECT_IMAGE_READ_ONLY : 0) |
(verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0);
r = loop_device_make_by_path(
mount_entry_source(m),
m->read_only ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
verity.data_path ? 0 : LO_FLAGS_PARTSCAN,
&loop_device);
if (r < 0)
return log_debug_errno(r, "Failed to create loop device for image: %m");
r = dissect_image(
loop_device->fd,
&verity,
m->image_options,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
if (!verity.data_path && r == -ENOPKG)
r = dissect_image(
loop_device->fd,
&verity,
m->image_options,
dissect_image_flags|DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)
return log_debug_errno(r, "Failed to dissect image: %m");
r = dissected_image_decrypt(
dissected_image,
NULL,
&verity,
dissect_image_flags,
&decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
r = mkdir_p_label(mount_entry_path(m), 0755);
if (r < 0)
return log_debug_errno(r, "Failed to create destination directory %s: %m", mount_entry_path(m));
r = umount_recursive(mount_entry_path(m), 0);
if (r < 0)
return log_debug_errno(r, "Failed to umount under destination directory %s: %m", mount_entry_path(m));
r = dissected_image_mount(dissected_image, mount_entry_path(m), UID_INVALID, dissect_image_flags);
if (r < 0)
return log_debug_errno(r, "Failed to mount image: %m");
if (decrypted_image) {
r = decrypted_image_relinquish(decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to relinquish decrypted image: %m");
}
loop_device_relinquish(loop_device);
return log_debug_errno(r, "Failed to mount image %s on %s: %m", mount_entry_source(m), mount_entry_path(m));
return 1;
}

View File

@ -226,6 +226,10 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="BindMountUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="MountImageUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="KillUnit"/>
@ -400,6 +404,10 @@
send_interface="org.freedesktop.systemd1.Service"
send_member="BindMount"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Service"
send_member="MountImage"/>
<!-- Managed via polkit or other criteria: org.freedesktop.systemd1.Scope interface -->
<allow send_destination="org.freedesktop.systemd1"

View File

@ -2554,4 +2554,77 @@ static const char *const partition_designator_table[] = {
[PARTITION_VAR] = "var",
};
int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
DissectImageFlags dissect_image_flags;
int r;
assert(src);
assert(dest);
r = verity_settings_load(&verity, src, NULL, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
dissect_image_flags = verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
r = loop_device_make_by_path(
src,
-1,
verity.data_path ? 0 : LO_FLAGS_PARTSCAN,
&loop_device);
if (r < 0)
return log_debug_errno(r, "Failed to create loop device for image: %m");
r = dissect_image(
loop_device->fd,
&verity,
options,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
if (!verity.data_path && r == -ENOPKG)
r = dissect_image(
loop_device->fd,
&verity,
options,
dissect_image_flags|DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)
return log_debug_errno(r, "Failed to dissect image: %m");
r = dissected_image_decrypt(
dissected_image,
NULL,
&verity,
dissect_image_flags,
&decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
r = mkdir_p_label(dest, 0755);
if (r < 0)
return log_debug_errno(r, "Failed to create destination directory %s: %m", dest);
r = umount_recursive(dest, 0);
if (r < 0)
return log_debug_errno(r, "Failed to umount under destination directory %s: %m", dest);
r = dissected_image_mount(dissected_image, dest, UID_INVALID, dissect_image_flags);
if (r < 0)
return log_debug_errno(r, "Failed to mount image: %m");
if (decrypted_image) {
r = decrypted_image_relinquish(decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to relinquish decrypted image: %m");
}
loop_device_relinquish(loop_device);
return 0;
}
DEFINE_STRING_TABLE_LOOKUP(partition_designator, PartitionDesignator);

View File

@ -161,3 +161,5 @@ bool dissected_image_can_do_verity(const DissectedImage *image, PartitionDesigna
bool dissected_image_has_verity(const DissectedImage *image, PartitionDesignator d);
int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image);
int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options);

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <linux/loop.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
@ -8,6 +9,7 @@
#include <unistd.h>
#include "alloc-util.h"
#include "dissect-image.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
@ -27,6 +29,7 @@
#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
#include "user-util.h"
int mount_fd(const char *source,
int target_fd,
@ -747,14 +750,16 @@ int mount_option_mangle(
return 0;
}
int bind_mount_in_namespace(
static int mount_in_namespace(
pid_t target,
const char *propagate_path,
const char *incoming_path,
const char *src,
const char *dest,
bool read_only,
bool make_file_or_directory) {
bool make_file_or_directory,
const MountOptions *options,
bool is_image) {
_cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
_cleanup_close_ int self_mntns_fd = -1, mntns_fd = -1, root_fd = -1, pidns_fd = -1, chased_src_fd = -1;
@ -772,6 +777,7 @@ int bind_mount_in_namespace(
assert(incoming_path);
assert(src);
assert(dest);
assert(!options || is_image);
r = namespace_open(target, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
if (r < 0)
@ -836,7 +842,10 @@ int bind_mount_in_namespace(
/* Second, we mount the source file or directory to a directory inside of our MS_SLAVE playground. */
mount_tmp = strjoina(mount_slave, "/mount");
r = make_mount_point_inode_from_stat(&st, mount_tmp, 0700);
if (is_image)
r = mkdir_p(mount_tmp, 0700);
else
r = make_mount_point_inode_from_stat(&st, mount_tmp, 0700);
if (r < 0) {
log_debug_errno(r, "Failed to create temporary mount point %s: %m", mount_tmp);
goto finish;
@ -844,7 +853,10 @@ int bind_mount_in_namespace(
mount_tmp_created = true;
r = mount_follow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL);
if (is_image)
r = verity_dissect_and_mount(chased_src, mount_tmp, options);
else
r = mount_follow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
goto finish;
@ -861,7 +873,7 @@ int bind_mount_in_namespace(
* right-away. */
mount_outside = strjoina(propagate_path, "/XXXXXX");
if (S_ISDIR(st.st_mode))
if (is_image || S_ISDIR(st.st_mode))
r = mkdtemp(mount_outside) ? 0 : -errno;
else {
r = mkostemp_safe(mount_outside);
@ -881,7 +893,7 @@ int bind_mount_in_namespace(
mount_outside_mounted = true;
mount_tmp_mounted = false;
if (S_ISDIR(st.st_mode))
if (is_image || S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
@ -908,8 +920,11 @@ int bind_mount_in_namespace(
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
if (make_file_or_directory) {
(void) mkdir_parents(dest, 0755);
(void) make_mount_point_inode_from_stat(&st, dest, 0700);
if (!is_image) {
(void) mkdir_parents(dest, 0755);
(void) make_mount_point_inode_from_stat(&st, dest, 0700);
} else
(void) mkdir_p(dest, 0755);
}
/* Fifth, move the mount to the right place inside */
@ -946,7 +961,7 @@ finish:
if (mount_outside_mounted)
(void) umount_verbose(LOG_DEBUG, mount_outside, UMOUNT_NOFOLLOW);
if (mount_outside_created) {
if (S_ISDIR(st.st_mode))
if (is_image || S_ISDIR(st.st_mode))
(void) rmdir(mount_outside);
else
(void) unlink(mount_outside);
@ -955,7 +970,7 @@ finish:
if (mount_tmp_mounted)
(void) umount_verbose(LOG_DEBUG, mount_tmp, UMOUNT_NOFOLLOW);
if (mount_tmp_created) {
if (S_ISDIR(st.st_mode))
if (is_image || S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
@ -968,3 +983,28 @@ finish:
return r;
}
int bind_mount_in_namespace(
pid_t target,
const char *propagate_path,
const char *incoming_path,
const char *src,
const char *dest,
bool read_only,
bool make_file_or_directory) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
}
int mount_image_in_namespace(
pid_t target,
const char *propagate_path,
const char *incoming_path,
const char *src,
const char *dest,
bool read_only,
bool make_file_or_directory,
const MountOptions *options) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
}

View File

@ -6,6 +6,7 @@
#include <unistd.h>
#include "alloc-util.h"
#include "dissect-image.h"
#include "errno-util.h"
#include "macro.h"
@ -99,3 +100,4 @@ static inline char* umount_and_rmdir_and_free(char *p) {
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);

View File

@ -2,6 +2,7 @@
#include "bus-error.h"
#include "bus-locator.h"
#include "dissect-image.h"
#include "systemctl-mount.h"
#include "systemctl-util.h"
#include "systemctl.h"
@ -39,3 +40,77 @@ int mount_bind(int argc, char *argv[], void *userdata) {
return 0;
}
int mount_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *unit = argv[1], *src = argv[2], *dest = argv[3];
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_free_ char *n = NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = unit_name_mangle(unit, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, &n);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = bus_message_new_method_call(
bus,
&m,
bus_systemd_mgr,
"MountImageUnit");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(
m,
"sssbb",
n,
src,
dest,
arg_read_only,
arg_mkdir);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(ss)");
if (r < 0)
return bus_log_create_error(r);
if (argc > 4) {
_cleanup_free_ char *partition = NULL, *mount_options = NULL;
const char *options = argv[4];
r = extract_many_words(&options, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL);
if (r < 0)
return r;
/* Single set of options, applying to the root partition/single filesystem */
if (r == 1) {
r = sd_bus_message_append(m, "(ss)", "root", partition);
if (r < 0)
return bus_log_create_error(r);
} else if (r > 1) {
if (partition_designator_from_string(partition) < 0)
return bus_log_create_error(-EINVAL);
r = sd_bus_message_append(m, "(ss)", partition, mount_options);
if (r < 0)
return bus_log_create_error(r);
}
}
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, -1, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to mount image: %s", bus_error_message(&error, r));
return 0;
}

View File

@ -2,3 +2,4 @@
#pragma once
int mount_bind(int argc, char *argv[], void *userdata);
int mount_image(int argc, char *argv[], void *userdata);

View File

@ -162,6 +162,8 @@ static int systemctl_help(void) {
" set-property UNIT PROPERTY=VALUE... Sets one or more properties of a unit\n"
" bind UNIT PATH [PATH] Bind-mount a path from the host into a\n"
" unit's namespace\n"
" mount-image UNIT PATH [PATH [OPTS]] Mount an image from the host into a\n"
" unit's namespace\n"
" service-log-level SERVICE [LEVEL] Get/set logging threshold for service\n"
" service-log-target SERVICE [TARGET] Get/set logging target for service\n"
" reset-failed [PATTERN...] Reset failed state for all, one, or more\n"
@ -292,7 +294,7 @@ static int systemctl_help(void) {
" 'utc': 'Day YYYY-MM-DD HH:MM:SS UTC\n"
" 'us+utc': 'Day YYYY-MM-DD HH:MM:SS.UUUUUU UTC\n"
" --read-only Create read-only bind mount\n"
" --mkdir Create directory before bind-mounting, if missing\n"
" --mkdir Create directory before mounting, if missing\n"
"\nSee the %2$s for details.\n"
, program_invocation_short_name
, link
@ -1065,6 +1067,7 @@ static int systemctl_main(int argc, char *argv[]) {
{ "add-requires", 3, VERB_ANY, 0, add_dependency },
{ "edit", 2, VERB_ANY, VERB_ONLINE_ONLY, edit },
{ "bind", 3, 4, VERB_ONLINE_ONLY, mount_bind },
{ "mount-image", 4, 5, VERB_ONLINE_ONLY, mount_image },
{}
};

View File

@ -205,6 +205,28 @@ grep -q -F "MARKER=1" ${image_dir}/result/c
grep -F "squashfs" ${image_dir}/result/c | grep -q -F "noatime"
grep -F "squashfs" ${image_dir}/result/c | grep -q -F -v "nosuid"
# Adding a new mounts at runtime works if the unit is in the active state,
# so use Type=notify to make sure there's no race condition in the test
cat > /run/systemd/system/testservice-50d.service <<EOF
[Service]
RuntimeMaxSec=300
Type=notify
RemainAfterExit=yes
MountAPIVFS=yes
PrivateTmp=yes
ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do sleep 0.1; done; mount | grep -F "/tmp/img" | grep -q -F "nosuid"'
EOF
systemctl start testservice-50d.service
systemctl mount-image --mkdir testservice-50d.service ${image}.raw /tmp/img root:nosuid
while systemctl show -P SubState testservice-50d.service | grep -q running
do
sleep 0.1
done
systemctl is-active testservice-50d.service
echo OK >/testok
exit 0