abort when bind mounting a non-existent source path

If you try to pass a bogus path like -b /asdf,/asdf, minijail doesn't
mind and goes ahead and creates the destination (which also creates
the source), and then does a bind mount.  We should instead abort --
if you really want to bind mount a new directory or file, the daemon
should explicitly create the path correctly.

For the -k option, we were stating the pseudo source which could lead
to bad behavior.  e.g. If there was a file in the cwd named "none" or
"proc", we'd stat() it, and then change the destination setup logic.

The current behavior is also a little idiosyncratic: if the source
and dest are the same, there's no error, but if you try to mount to
a different path (-b /asdf,/foo), it'll fail.  Or if you try to use
a chroot/pivot root, it'll fail.

We now enforce absolute paths for sources with the -b & -k options.
This shouldn't be a problem in general, and it makes the behavior a
bit more consistent.

Bug: None
Test: unittests pass
Test: betty VM boots and cheets_StartAndroid passes

Change-Id: I26310ba45b8e463533485de879a19e578d66b0e6
diff --git a/system_unittest.cc b/system_unittest.cc
index f4490ca..0ecfcde 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -29,6 +29,15 @@
 // A random path that really really should not exist on the host.
 const char kNoSuchDir[] = "/.x/..x/...x/path/should/not/exist/";
 
+// A random file that should exist.
+const char kValidFile[] = "/etc/passwd";
+
+// A random directory that should exist.
+const char kValidDir[] = "/";
+
+// A random character device that should exist.
+const char kValidCharDev[] = "/dev/null";
+
 // Return a temp filename in the cwd that this test can manipulate.
 // It will not exist when it returns, and the user has to free the memory.
 char *get_temp_path() {
@@ -121,4 +130,80 @@
   EXPECT_EQ(5u, fread(data, 1, sizeof(data), fp));
   fclose(fp);
   EXPECT_EQ(0, strcmp(data, "1234\n"));
+
+  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.
+  EXPECT_EQ(0, setup_mount_destination(nullptr, kValidDir, 0, 0, false));
+  EXPECT_EQ(0, setup_mount_destination(nullptr, "/proc", 0, 0, true));
+  EXPECT_EQ(0, setup_mount_destination(nullptr, "/dev", 0, 0, false));
+}
+
+// When given a bind mount where the source is relative, reject it.
+TEST(setup_mount_destination, reject_relative_bind) {
+  // Pick a destination we know doesn't exist.
+  EXPECT_NE(0, setup_mount_destination("foo", kNoSuchDir, 0, 0, true));
+}
+
+// A mount of a pseudo filesystem should make the destination dir.
+TEST(setup_mount_destination, create_pseudo_fs) {
+  char *path = get_temp_path();
+  ASSERT_NE(nullptr, path);
+
+  // Passing -1 for uid/gid tells chown to make no changes.
+  EXPECT_EQ(0, setup_mount_destination("none", path, -1, -1, false));
+  // We check it's a directory by deleting it as such.
+  EXPECT_EQ(0, rmdir(path));
+
+  free(path);
+}
+
+// If the source path does not exist, we should error out.
+TEST(setup_mount_destination, missing_source) {
+  // The missing dest path is so we can exercise the source logic.
+  EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, false));
+  EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, true));
+}
+
+// A bind mount of a directory should create the destination dir.
+TEST(setup_mount_destination, create_bind_dir) {
+  char *path = get_temp_path();
+  ASSERT_NE(nullptr, path);
+
+  // Passing -1 for uid/gid tells chown to make no changes.
+  EXPECT_EQ(0, setup_mount_destination(kValidDir, path, -1, -1, true));
+  // We check it's a directory by deleting it as such.
+  EXPECT_EQ(0, rmdir(path));
+
+  free(path);
+}
+
+// A bind mount of a file should create the destination file.
+TEST(setup_mount_destination, create_bind_file) {
+  char *path = get_temp_path();
+  ASSERT_NE(nullptr, path);
+
+  // Passing -1 for uid/gid tells chown to make no changes.
+  EXPECT_EQ(0, setup_mount_destination(kValidFile, path, -1, -1, true));
+  // We check it's a file by deleting it as such.
+  EXPECT_EQ(0, unlink(path));
+
+  free(path);
+}
+
+// A mount of a character device should create the destination char.
+TEST(setup_mount_destination, create_char_dev) {
+  char *path = get_temp_path();
+  ASSERT_NE(nullptr, path);
+
+  // Passing -1 for uid/gid tells chown to make no changes.
+  EXPECT_EQ(0, setup_mount_destination(kValidCharDev, path, -1, -1, false));
+  // We check it's a directory by deleting it as such.
+  EXPECT_EQ(0, rmdir(path));
+
+  free(path);
 }