create parent paths of target mounts as needed am: 5fdba4ed28 am: 13fb44d0fe
am: b2d99d4799
Change-Id: Ie5c7c7f08202a6f57a61261e5122419cf75517bb
diff --git a/minijail0.1 b/minijail0.1
index e713fed..7c535e0 100644
--- a/minijail0.1
+++ b/minijail0.1
@@ -17,7 +17,7 @@
The \fIsrc\fR path must be an absolute path.
If \fIdest\fR is not specified, it will default to \fIsrc\fR.
If the destination does not exist, it will be created as a file or directory
-based on the \fIsrc\fR type.
+based on the \fIsrc\fR type (including missing parent directories).
.TP
\fB-c <caps>\fR
Restrict capabilities to \fIcaps\fR. When used in conjunction with \fB-u\fR and
@@ -81,7 +81,8 @@
If the mount is not a pseudo filesystem (e.g. proc or sysfs), \fIsrc\fR path
must be an absolute path (e.g. \fI/dev/sda1\fR and not \fIsda1\fR).
-If the destination does not exist, it will be created as a directory.
+If the destination does not exist, it will be created as a directory (including
+missing parent directories).
.TP
\fB-K\fR
Don't mark all existing mounts as MS_PRIVATE.
diff --git a/system.c b/system.c
index 74e97c2..bb1abf9 100644
--- a/system.c
+++ b/system.c
@@ -215,6 +215,39 @@
}
/*
+ * Create the |path| directory and its parents (if need be) with |mode|.
+ * If not |isdir|, then |path| is actually a file, so the last component
+ * will not be created.
+ */
+int mkdir_p(const char *path, mode_t mode, bool isdir)
+{
+ char *dir = strdup(path);
+ if (!dir)
+ return -errno;
+
+ /* Starting from the root, work our way out to the end. */
+ char *p = strchr(dir + 1, '/');
+ while (p) {
+ *p = '\0';
+ if (mkdir(dir, mode) && errno != EEXIST) {
+ free(dir);
+ return -errno;
+ }
+ *p = '/';
+ p = strchr(p + 1, '/');
+ }
+
+ /*
+ * Create the last directory. We still check EEXIST here in case
+ * of trailing slashes.
+ */
+ free(dir);
+ if (isdir && mkdir(path, mode) && errno != EEXIST)
+ return -errno;
+ return 0;
+}
+
+/*
* setup_mount_destination: Ensures the mount target exists.
* Creates it if needed and possible.
*/
@@ -267,11 +300,16 @@
domkdir = true;
}
- /* Now that we know what we want to do, do it! */
- if (domkdir) {
- if (mkdir(dest, 0700))
- return -errno;
- } else {
+ /*
+ * Now that we know what we want to do, do it!
+ * We always create the intermediate dirs and the final path with 0755
+ * perms and root/root ownership. This shouldn't be a problem because
+ * the actual mount will set those perms/ownership on the mount point
+ * which is all people should need to access it.
+ */
+ if (mkdir_p(dest, 0755, domkdir))
+ return -errno;
+ if (!domkdir) {
int fd = open(dest, O_RDWR | O_CREAT | O_CLOEXEC, 0700);
if (fd < 0)
return -errno;
diff --git a/system.h b/system.h
index 7f36ad2..b816f5f 100644
--- a/system.h
+++ b/system.h
@@ -51,6 +51,8 @@
int write_pid_to_path(pid_t pid, const char *path);
int write_proc_file(pid_t pid, const char *content, const char *basename);
+int mkdir_p(const char *path, mode_t mode, bool isdir);
+
int setup_mount_destination(const char *source, const char *dest, uid_t uid,
uid_t gid, bool bind);
diff --git a/system_unittest.cc b/system_unittest.cc
index db5fe98..c584808 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <gtest/gtest.h>
@@ -125,6 +126,57 @@
}
// If the destination exists, there's nothing to do.
+// Also check trailing slash handling.
+TEST(mkdir_p, dest_exists) {
+ EXPECT_EQ(0, mkdir_p("/", 0, true));
+ EXPECT_EQ(0, mkdir_p("///", 0, true));
+ EXPECT_EQ(0, mkdir_p("/proc", 0, true));
+ EXPECT_EQ(0, mkdir_p("/proc/", 0, true));
+ EXPECT_EQ(0, mkdir_p("/dev", 0, true));
+ EXPECT_EQ(0, mkdir_p("/dev/", 0, true));
+}
+
+// Create a directory tree that doesn't exist.
+TEST(mkdir_p, create_tree) {
+ char *path = get_temp_path();
+ ASSERT_NE(nullptr, path);
+ unlink(path);
+
+ // Run `mkdir -p <path>/a/b/c`.
+ char *path_a, *path_a_b, *path_a_b_c;
+ ASSERT_NE(-1, asprintf(&path_a, "%s/a", path));
+ ASSERT_NE(-1, asprintf(&path_a_b, "%s/b", path_a));
+ ASSERT_NE(-1, asprintf(&path_a_b_c, "%s/c", path_a_b));
+
+ // First try creating it as a file.
+ EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, false));
+
+ // Make sure the final path doesn't exist yet.
+ struct stat st;
+ EXPECT_EQ(0, stat(path_a_b, &st));
+ EXPECT_EQ(true, S_ISDIR(st.st_mode));
+ EXPECT_EQ(-1, stat(path_a_b_c, &st));
+
+ // Then create it as a complete dir.
+ EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, true));
+
+ // Make sure the final dir actually exists.
+ EXPECT_EQ(0, stat(path_a_b_c, &st));
+ EXPECT_EQ(true, S_ISDIR(st.st_mode));
+
+ // Clean up.
+ ASSERT_EQ(0, rmdir(path_a_b_c));
+ ASSERT_EQ(0, rmdir(path_a_b));
+ ASSERT_EQ(0, rmdir(path_a));
+ ASSERT_EQ(0, rmdir(path));
+
+ free(path_a_b_c);
+ free(path_a_b);
+ free(path_a);
+ free(path);
+}
+
+// If the destination exists, there's nothing to do.
TEST(setup_mount_destination, dest_exists) {
// Pick some paths that should always exist. We pass in invalid pointers
// for other args so we crash if the dest check doesn't short circuit.