machined: support "machinectl bind" on non-directories (#7349)

Fixes: #7195
This commit is contained in:
Lennart Poettering 2017-11-19 14:23:29 +01:00 committed by Zbigniew Jędrzejewski-Szmek
parent 34b3f471f8
commit d3590acede
2 changed files with 74 additions and 36 deletions

View File

@ -208,16 +208,16 @@
<varlistentry>
<term><option>--mkdir</option></term>
<listitem><para>When used with <command>bind</command>, creates
the destination directory before applying the bind
mount.</para></listitem>
<listitem><para>When used with <command>bind</command>, creates the destination file or directory before
applying the bind mount. Note that even though the name of this option suggests that it is suitable only for
directories, this option also creates the destination file node to mount over if the the object to mount is not
a directory, but a regular file, device node, socket or FIFO.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--read-only</option></term>
<listitem><para>When used with <command>bind</command>, applies
a read-only bind mount.</para>
<listitem><para>When used with <command>bind</command>, creates a read-only bind mount.</para>
<para>When used with <command>clone</command>, <command>import-raw</command> or <command>import-tar</command> a
read-only container or VM image is created.</para></listitem>
@ -528,14 +528,16 @@
<varlistentry>
<term><command>bind</command> <replaceable>NAME</replaceable> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>]</term>
<listitem><para>Bind mounts a directory from the host into the specified container. The first directory
argument is the source directory on the host, the second directory argument is the destination directory in the
container. When the latter is omitted, the destination path in the container is the same as the source path on
the host. When combined with the <option>--read-only</option> switch, a ready-only bind 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
<listitem><para>Bind mounts a file or directory from the host into the specified container. The first path
argument is the source file or directory on the host, the second path argument is the destination file or
directory in the container. When the latter is omitted, the destination path in the container is the same as
the source path on the host. When combined with the <option>--read-only</option> switch, a ready-only bind
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
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> containers,
and only if user namespacing (<option>--private-users</option>) is not used.</para></listitem>
and only if user namespacing (<option>--private-users</option>) is not used. This command supports bind
mounting directories, regular files, device nodes, <constant>AF_UNIX</constant> socket nodes, as well as
FIFOs.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -836,11 +836,13 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
bool mount_slave_created = false, mount_slave_mounted = false,
mount_tmp_created = false, mount_tmp_mounted = false,
mount_outside_created = false, mount_outside_mounted = false;
_cleanup_free_ char *chased_src = NULL;
int read_only, make_file_or_directory;
const char *dest, *src;
Machine *m = userdata;
int read_only, make_directory;
pid_t child;
struct stat st;
siginfo_t si;
pid_t child;
uid_t uid;
int r;
@ -850,7 +852,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
if (m->class != MACHINE_CONTAINER)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines.");
r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory);
r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory);
if (r < 0)
return r;
@ -890,6 +892,15 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
if (laccess(p, F_OK) < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points.");
r = chase_symlinks(src, NULL, 0, &chased_src);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to resolve source path: %m");
if (lstat(chased_src, &st) < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to stat() source path: %m");
if (S_ISLNK(st.st_mode)) /* This shouldn't really happen, given that we just chased the symlinks above, but let's better be safe… */
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Source directory can't be a symbolic link");
/* Our goal is to install a new bind mount into the container,
possibly read-only. This is irritatingly complex
unfortunately, currently.
@ -916,18 +927,21 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
goto finish;
}
/* Second, we mount the source directory to a directory inside
of our MS_SLAVE playground. */
/* Second, we mount the source file or directory to a directory inside of our MS_SLAVE playground. */
mount_tmp = strjoina(mount_slave, "/mount");
if (mkdir(mount_tmp, 0700) < 0) {
r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp);
if (S_ISDIR(st.st_mode))
r = mkdir(mount_tmp, 0700) < 0 ? -errno : 0;
else
r = touch(mount_tmp);
if (r < 0) {
sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp);
goto finish;
}
mount_tmp_created = true;
if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) {
r = sd_bus_error_set_errnof(error, errno, "Failed to mount %s: %m", src);
if (mount(chased_src, mount_tmp, NULL, MS_BIND, NULL) < 0) {
r = sd_bus_error_set_errnof(error, errno, "Failed to mount %s: %m", chased_src);
goto finish;
}
@ -940,13 +954,18 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
goto finish;
}
/* Fourth, we move the new bind mount into the propagation
* directory. This way it will appear there read-only
/* Fourth, we move the new bind mount into the propagation directory. This way it will appear there read-only
* right-away. */
mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX");
if (!mkdtemp(mount_outside)) {
r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside);
if (S_ISDIR(st.st_mode))
r = mkdtemp(mount_outside) ? 0 : -errno;
else {
r = mkostemp_safe(mount_outside);
safe_close(r);
}
if (r < 0) {
sd_bus_error_set_errnof(error, errno, "Cannot create propagation file or directory %s: %m", mount_outside);
goto finish;
}
@ -960,7 +979,10 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
mount_outside_mounted = true;
mount_tmp_mounted = false;
(void) rmdir(mount_tmp);
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
mount_tmp_created = false;
(void) umount(mount_slave);
@ -999,8 +1021,14 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
goto child_fail;
}
if (make_directory)
(void) mkdir_p(dest, 0755);
if (make_file_or_directory) {
if (S_ISDIR(st.st_mode))
(void) mkdir_p(dest, 0755);
else {
(void) mkdir_parents(dest, 0755);
safe_close(open(dest, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600));
}
}
/* Fifth, move the mount to the right place inside */
mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside));
@ -1042,19 +1070,27 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
finish:
if (mount_outside_mounted)
umount(mount_outside);
if (mount_outside_created)
rmdir(mount_outside);
(void) umount(mount_outside);
if (mount_outside_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_outside);
else
(void) unlink(mount_outside);
}
if (mount_tmp_mounted)
umount(mount_tmp);
if (mount_tmp_created)
rmdir(mount_tmp);
(void) umount(mount_tmp);
if (mount_tmp_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
}
if (mount_slave_mounted)
umount(mount_slave);
(void) umount(mount_slave);
if (mount_slave_created)
rmdir(mount_slave);
(void) rmdir(mount_slave);
return r;
}