create parent paths of target mounts as needed

Currently if you want to bind mount a single subdir, you have to make
sure to create the full parent directory chain.  For example, if you
want /var/lib/timezone/ but not the rest of /var, you have to do:
	-k none,/var,tmpfs
	-k none,/var/lib,tmpfs
	-b /var/lib/timezone/
For every additional subdir, you need to add another -k option just to
do an [effective] mkdir with a tmpfs mount.

The current -k/-b behavior is to run mkdir if the target doesn't already
exist, but only for the final target.  Lets extend it to also create any
missing parent paths, so now only the base path needs to be writable:
	-k none,/var,tmpfs
	-b /var/lib/timezone/

Bug: None
Test: `minijail0 --profile minimalistic-mountns -k none,/var,tmpfs -b /var/lib/timezone /bin/date` works
Change-Id: I7f36bcb445ce40ed66a9403a4ee1c1fe3f9e5ea8
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.