Skip to content

Commit 4db4792

Browse files
authored
Merge pull request systemd#17228 from bluca/bind_path_runtime
core: add systemctl and DBUS method to bind mount new paths without service restart
2 parents 4ea8b44 + fa7a3cd commit 4db4792

36 files changed

+742
-262
lines changed

NEWS

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
systemd System and Service Manager
22

3+
CHANGES WITH 248:
4+
5+
* The MountAPIVFS= service file setting now additionally mounts a tmpfs
6+
on /run/ if it is not already a mount point. A writable /run/ has always
7+
been a requirement for a functioning system, but this was not
8+
guaranteed when using a read-only image.
9+
Users can always specify BindPaths= or InaccessiblePaths= as overrides,
10+
and they will take precedence. If the host's root mount point is used,
11+
there is no change in behaviour.
12+
313
CHANGES WITH 247:
414

515
* KERNEL API INCOMPATIBILITY: Linux 4.14 introduced two new uevents

man/org.freedesktop.systemd1.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ node /org/freedesktop/systemd1 {
116116
SetUnitProperties(in s name,
117117
in b runtime,
118118
in a(sv) properties);
119+
BindMountUnit(in s name,
120+
in s source,
121+
in s destination,
122+
in b read_only,
123+
in b mkdir);
119124
RefUnit(in s name);
120125
UnrefUnit(in s name);
121126
StartTransientUnit(in s name,
@@ -767,6 +772,8 @@ node /org/freedesktop/systemd1 {
767772

768773
<variablelist class="dbus-method" generated="True" extra-ref="SetUnitProperties()"/>
769774

775+
<variablelist class="dbus-method" generated="True" extra-ref="BindMountUnit()"/>
776+
770777
<variablelist class="dbus-method" generated="True" extra-ref="RefUnit()"/>
771778

772779
<variablelist class="dbus-method" generated="True" extra-ref="UnrefUnit()"/>
@@ -1156,6 +1163,9 @@ node /org/freedesktop/systemd1 {
11561163
the "Try" flavor is used in which case a service that isn't running is not affected by the restart. The
11571164
"ReloadOrRestart" flavors attempt a reload if the unit supports it and use a restart otherwise.</para>
11581165

1166+
<para><function>BindMountUnit()</function> can be used to bind mount new files or directories into
1167+
a running service mount namespace.</para>
1168+
11591169
<para><function>KillUnit()</function> may be used to kill (i.e. send a signal to) all processes of a
11601170
unit. It takes the unit <varname>name</varname>, an enum <varname>who</varname> and a UNIX
11611171
<varname>signal</varname> number to send. The <varname>who</varname> enum is one of
@@ -2193,6 +2203,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
21932203
node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
21942204
interface org.freedesktop.systemd1.Service {
21952205
methods:
2206+
BindMount(in s source,
2207+
in s destination,
2208+
in b read_only,
2209+
in b mkdir);
21962210
GetProcesses(out a(sus) processes);
21972211
AttachProcesses(in s subcgroup,
21982212
in au pids);
@@ -3252,6 +3266,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
32523266

32533267
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Service"/>
32543268

3269+
<variablelist class="dbus-method" generated="True" extra-ref="BindMount()"/>
3270+
32553271
<variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
32563272

32573273
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
@@ -3810,6 +3826,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
38103826

38113827
<!--End of Autogenerated section-->
38123828

3829+
<refsect2>
3830+
<title>Methods</title>
3831+
3832+
<para><function>BindMount()</function> implements the same operation as the respective method on the
3833+
<interfacename>Manager</interfacename> object (see above). However, this method operates on the service
3834+
object and hence does not take a unit name parameter. Invoking the methods directly on the Manager
3835+
object has the advantage of not requiring a <function>GetUnit()</function> call to get the unit object
3836+
for a specific unit name. Calling the methods on the Manager object is hence a round trip
3837+
optimization.</para>
3838+
</refsect2>
3839+
38133840
<refsect2>
38143841
<title>Properties</title>
38153842

man/systemctl.xml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,23 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
550550
</listitem>
551551
</varlistentry>
552552

553+
<varlistentry>
554+
<term><command>bind</command> <replaceable>UNIT</replaceable> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>]</term>
555+
556+
<listitem><para>Bind mounts a file or directory from the host into the specified unit's view. The first path
557+
argument is the source file or directory on the host, the second path argument is the destination file or
558+
directory in the unit's view. When the latter is omitted, the destination path in the unit's view is the same as
559+
the source path on the host. When combined with the <option>--read-only</option> switch, a ready-only bind
560+
mount is created. When combined with the <option>--mkdir</option> switch, the destination path is first created
561+
before the mount is applied. Note that this option is currently only supported for units that run within a mount
562+
namespace (e.g.: with <option>RootImage=</option>, <option>PrivateMounts=</option>, etc.). This command supports bind
563+
mounting directories, regular files, device nodes, <constant>AF_UNIX</constant> socket nodes, as well as FIFOs.
564+
The bind mount is ephemeral, and it is undone as soon as the current unit process exists.
565+
Note that the namespace mentioned here, where the bind mount will be added to, is the one where the main service
566+
process runs, as other processes run in distinct namespaces (e.g.: <option>ExecReload=</option>,
567+
<option>ExecStartPre=</option>, etc.) </para></listitem>
568+
</varlistentry>
569+
553570
<varlistentry>
554571
<term><command>service-log-level</command> <replaceable>SERVICE</replaceable> [<replaceable>LEVEL</replaceable>]</term>
555572

@@ -2246,6 +2263,21 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
22462263
</listitem>
22472264
</varlistentry>
22482265

2266+
<varlistentry>
2267+
<term><option>--mkdir</option></term>
2268+
2269+
<listitem><para>When used with <command>bind</command>, creates the destination file or directory before
2270+
applying the bind mount. Note that even though the name of this option suggests that it is suitable only for
2271+
directories, this option also creates the destination file node to mount over if the object to mount is not
2272+
a directory, but a regular file, device node, socket or FIFO.</para></listitem>
2273+
</varlistentry>
2274+
2275+
<varlistentry>
2276+
<term><option>--read-only</option></term>
2277+
2278+
<listitem><para>When used with <command>bind</command>, creates a read-only bind mount.</para></listitem>
2279+
</varlistentry>
2280+
22492281
<xi:include href="user-system-options.xml" xpointer="host" />
22502282
<xi:include href="user-system-options.xml" xpointer="machine" />
22512283

man/systemd.exec.xml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,15 +275,20 @@
275275
<term><varname>MountAPIVFS=</varname></term>
276276

277277
<listitem><para>Takes a boolean argument. If on, a private mount namespace for the unit's processes is created
278-
and the API file systems <filename>/proc/</filename>, <filename>/sys/</filename>, and <filename>/dev/</filename>
279-
are mounted inside of it, unless they are already mounted. Note that this option has no effect unless used in
280-
conjunction with <varname>RootDirectory=</varname>/<varname>RootImage=</varname> as these three mounts are
278+
and the API file systems <filename>/proc/</filename>, <filename>/sys/</filename>, <filename>/dev/</filename> and
279+
<filename>/run/</filename> (as an empty <literal>tmpfs</literal>) are mounted inside of it, unless they are
280+
already mounted. Note that this option has no effect unless used in conjunction with
281+
<varname>RootDirectory=</varname>/<varname>RootImage=</varname> as these four mounts are
281282
generally mounted in the host anyway, and unless the root directory is changed, the private mount namespace
282-
will be a 1:1 copy of the host's, and include these three mounts. Note that the <filename>/dev/</filename> file
283+
will be a 1:1 copy of the host's, and include these four mounts. Note that the <filename>/dev/</filename> file
283284
system of the host is bind mounted if this option is used without <varname>PrivateDevices=</varname>. To run
284285
the service with a private, minimal version of <filename>/dev/</filename>, combine this option with
285286
<varname>PrivateDevices=</varname>.</para>
286287

288+
<para>In order to allow propagating mounts at runtime in a safe manner, <filename>/run/systemd/propagate</filename>
289+
on the host will be used to set up new mounts, and <filename>/run/host/incoming/</filename> in the private namespace
290+
will be used as an intermediate step to store them before being moved to the final mount point.</para>
291+
287292
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
288293
</varlistentry>
289294

meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,6 +2198,8 @@ public_programs += executable(
21982198
'src/systemctl/systemctl-log-setting.h',
21992199
'src/systemctl/systemctl-logind.c',
22002200
'src/systemctl/systemctl-logind.h',
2201+
'src/systemctl/systemctl-mount.c',
2202+
'src/systemctl/systemctl-mount.h',
22012203
'src/systemctl/systemctl-preset-all.c',
22022204
'src/systemctl/systemctl-preset-all.h',
22032205
'src/systemctl/systemctl-reset-failed.c',

shell-completion/bash/systemctl.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ _systemctl () {
214214
list-timers list-units list-unit-files poweroff
215215
reboot rescue show-environment suspend get-default
216216
is-system-running preset-all'
217-
[FILE]='link switch-root'
217+
[FILE]='link switch-root bind'
218218
[TARGETS]='set-default'
219219
[MACHINES]='list-machines'
220220
[LOG_LEVEL]='log-level'

shell-completion/zsh/_systemctl.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"reset-failed:Reset failed state for all, one, or more units"
3232
"list-dependencies:Show unit dependency tree"
3333
"clean:Remove configuration, state, cache, logs or runtime data of units"
34+
"bind:Bind mount a path from the host into a unit's namespace"
3435
)
3536

3637
local -a machine_commands=(
@@ -378,6 +379,10 @@ done
378379
_files
379380
}
380381

382+
(( $+functions[_systemctl_bind] )) || _systemctl_bind() {
383+
_files
384+
}
385+
381386
# no systemctl completion for:
382387
# [STANDALONE]='daemon-reexec daemon-reload default
383388
# emergency exit halt kexec list-jobs list-units

src/core/dbus-manager.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "dbus-job.h"
1717
#include "dbus-manager.h"
1818
#include "dbus-scope.h"
19+
#include "dbus-service.h"
1920
#include "dbus-unit.h"
2021
#include "dbus.h"
2122
#include "env-util.h"
@@ -725,6 +726,11 @@ static int method_set_unit_properties(sd_bus_message *message, void *userdata, s
725726
return method_generic_unit_operation(message, userdata, error, bus_unit_method_set_properties, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
726727
}
727728

729+
static int method_bind_mount_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
730+
/* Only add mounts on fully loaded units */
731+
return method_generic_unit_operation(message, userdata, error, bus_service_method_bind_mount, GENERIC_UNIT_VALIDATE_LOADED);
732+
}
733+
728734
static int method_ref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
729735
/* Only allow reffing of fully loaded units, and make sure reffing a unit loads it. */
730736
return method_generic_unit_operation(message, userdata, error, bus_unit_method_ref, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
@@ -2760,6 +2766,16 @@ const sd_bus_vtable bus_manager_vtable[] = {
27602766
NULL,,
27612767
method_set_unit_properties,
27622768
SD_BUS_VTABLE_UNPRIVILEGED),
2769+
SD_BUS_METHOD_WITH_NAMES("BindMountUnit",
2770+
"sssbb",
2771+
SD_BUS_PARAM(name)
2772+
SD_BUS_PARAM(source)
2773+
SD_BUS_PARAM(destination)
2774+
SD_BUS_PARAM(read_only)
2775+
SD_BUS_PARAM(mkdir),
2776+
NULL,,
2777+
method_bind_mount_unit,
2778+
SD_BUS_VTABLE_UNPRIVILEGED),
27632779
SD_BUS_METHOD_WITH_NAMES("RefUnit",
27642780
"s",
27652781
SD_BUS_PARAM(name),

src/core/dbus-service.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
#include "dbus-manager.h"
1212
#include "dbus-service.h"
1313
#include "dbus-util.h"
14+
#include "execute.h"
1415
#include "exit-status.h"
1516
#include "fd-util.h"
1617
#include "fileio.h"
18+
#include "locale-util.h"
19+
#include "mount-util.h"
1720
#include "parse-util.h"
1821
#include "path-util.h"
22+
#include "selinux-access.h"
1923
#include "service.h"
2024
#include "signal-util.h"
2125
#include "string-util.h"
@@ -91,6 +95,79 @@ static int property_get_exit_status_set(
9195
return sd_bus_message_close_container(reply);
9296
}
9397

98+
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
99+
int read_only, make_file_or_directory;
100+
const char *dest, *src, *propagate_directory;
101+
Unit *u = userdata;
102+
ExecContext *c;
103+
pid_t unit_pid;
104+
int r;
105+
106+
assert(message);
107+
assert(u);
108+
109+
if (!MANAGER_IS_SYSTEM(u->manager))
110+
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Adding bind mounts at runtime is only supported for system managers.");
111+
112+
r = mac_selinux_unit_access_check(u, message, "start", error);
113+
if (r < 0)
114+
return r;
115+
116+
r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory);
117+
if (r < 0)
118+
return r;
119+
120+
if (!path_is_absolute(src) || !path_is_normalized(src))
121+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
122+
123+
if (isempty(dest))
124+
dest = src;
125+
else if (!path_is_absolute(dest) || !path_is_normalized(dest))
126+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
127+
128+
r = bus_verify_manage_units_async_full(
129+
u,
130+
"bind-mount",
131+
CAP_SYS_ADMIN,
132+
N_("Authentication is required to bind mount on '$(unit)'."),
133+
true,
134+
message,
135+
error);
136+
if (r < 0)
137+
return r;
138+
if (r == 0)
139+
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
140+
141+
if (u->type != UNIT_SERVICE)
142+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not of type .service");
143+
144+
/* If it would be dropped at startup time, return an error. The context should always be available, but
145+
* there's an assert in exec_needs_mount_namespace, so double-check just in case. */
146+
c = unit_get_exec_context(u);
147+
if (!c)
148+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot access unit execution context");
149+
if (path_startswith_strv(dest, c->inaccessible_paths))
150+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s is not accessible to this unit", dest);
151+
152+
/* Ensure that the unit was started in a private mount namespace */
153+
if (!exec_needs_mount_namespace(c, NULL, unit_get_exec_runtime(u)))
154+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit not running in private mount namespace, cannot activate bind mount");
155+
156+
unit_pid = unit_main_pid(u);
157+
if (unit_pid == 0 || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
158+
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not running");
159+
160+
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
161+
r = bind_mount_in_namespace(unit_pid,
162+
propagate_directory,
163+
"/run/systemd/incoming/",
164+
src, dest, read_only, make_file_or_directory);
165+
if (r < 0)
166+
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
167+
168+
return sd_bus_reply_method_return(message, NULL);
169+
}
170+
94171
const sd_bus_vtable bus_service_vtable[] = {
95172
SD_BUS_VTABLE_START(0),
96173
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -146,6 +223,16 @@ const sd_bus_vtable bus_service_vtable[] = {
146223
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
147224
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopPostEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
148225

226+
SD_BUS_METHOD_WITH_NAMES("BindMount",
227+
"ssbb",
228+
SD_BUS_PARAM(source)
229+
SD_BUS_PARAM(destination)
230+
SD_BUS_PARAM(read_only)
231+
SD_BUS_PARAM(mkdir),
232+
NULL,,
233+
bus_service_method_bind_mount,
234+
SD_BUS_VTABLE_UNPRIVILEGED),
235+
149236
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
150237
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
151238
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),

src/core/dbus-service.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
extern const sd_bus_vtable bus_service_vtable[];
1010

1111
int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitWriteFlags flags, sd_bus_error *error);
12+
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
1213
int bus_service_commit_properties(Unit *u);

0 commit comments

Comments
 (0)