[starnix] Allow specifying ext4 or remotefs as the root in the cml

Change-Id: I397da01c63fab0b657692bad00cdb2ca5b0f7855
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/558968
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Theodore Dubois <tbodt@google.com>
Reviewed-by: Adam Barth <abarth@google.com>
diff --git a/src/proc/bin/starnix/runner.rs b/src/proc/bin/starnix/runner.rs
index 5d822e7..c939168 100644
--- a/src/proc/bin/starnix/runner.rs
+++ b/src/proc/bin/starnix/runner.rs
@@ -32,6 +32,7 @@
 
 use crate::auth::Credentials;
 use crate::fs::devfs::DevFs;
+use crate::fs::ext4::ExtFilesystem;
 use crate::fs::fuchsia::{create_file_from_handle, RemoteFs};
 use crate::fs::tmpfs::TmpFs;
 use crate::fs::*;
@@ -41,7 +42,6 @@
 use crate::syscalls::table::dispatch_syscall;
 use crate::syscalls::*;
 use crate::task::*;
-use crate::types::*;
 
 // TODO: Should we move this code into fuchsia_zircon? It seems like part of a better abstraction
 // for exception channels.
@@ -205,7 +205,37 @@
     Ok(files)
 }
 
-async fn start_component(
+fn create_filesystem_from_spec<'a>(
+    pkg: &fio::DirectorySynchronousProxy,
+    spec: &'a str,
+) -> Result<(&'a [u8], FileSystemHandle), Error> {
+    let mut iter = spec.splitn(3, ':');
+    let mount_point =
+        iter.next().ok_or_else(|| anyhow!("mount point is missing from {:?}", spec))?;
+    let fs_type = iter.next().ok_or_else(|| anyhow!("fs type is missing from {:?}", spec))?;
+    let fs_path = iter.next();
+    let fs = match fs_type {
+        "tmpfs" => TmpFs::new(),
+        "devfs" => DevFs::new(),
+        "remotefs" => {
+            let fs_path = fs_path.ok_or_else(|| anyhow!("remotefs requires specifying a path"))?;
+            let rights = fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE;
+            let root = syncio::directory_open_directory_async(&pkg, &fs_path, rights)
+                .map_err(|e| anyhow!("Failed to open root: {}", e))?;
+            RemoteFs::new(root.into_channel(), rights)
+        }
+        "ext4" => {
+            let fs_path = fs_path.ok_or_else(|| anyhow!("ext4 requires specifying a path"))?;
+            let vmo =
+                syncio::directory_open_vmo(&pkg, &fs_path, fio::VMO_FLAG_READ, zx::Time::INFINITE)?;
+            ExtFilesystem::new(vmo)?
+        }
+        _ => anyhow::bail!("invalid fs type {:?}", fs_type),
+    };
+    Ok((mount_point.as_bytes(), fs))
+}
+
+fn start_component(
     kernel: Arc<Kernel>,
     start_info: ComponentStartInfo,
     controller: ServerEnd<ComponentControllerMarker>,
@@ -216,33 +246,22 @@
         start_info.numbered_handles,
     );
 
-    let root_path = runner::get_program_string(&start_info, "root")
-        .ok_or_else(|| anyhow!("No root in component manifest"))?
-        .to_owned();
+    let mounts =
+        runner::get_program_strvec(&start_info, "mounts").map(|a| a.clone()).unwrap_or(vec![]);
     let binary_path = CString::new(runner::get_program_binary(&start_info)?)?;
-
     let args = runner::get_program_strvec(&start_info, "args")
         .map(|args| {
             args.iter().map(|arg| CString::new(arg.clone())).collect::<Result<Vec<CString>, _>>()
         })
         .unwrap_or(Ok(vec![]))?;
-    let mount = runner::get_program_strvec(&start_info, "mount")
-        .map(|mounts| {
-            mounts
-                .iter()
-                .map(|mount| CString::new(mount.clone()))
-                .collect::<Result<Vec<CString>, _>>()
-        })
-        .unwrap_or(Ok(vec![]))?;
-
-    let user_passwd = runner::get_program_string(&start_info, "user").unwrap_or("fuchsia:x:42:42");
-    let credentials = Credentials::from_passwd(user_passwd)?;
-
     let environ = runner::get_program_strvec(&start_info, "environ")
         .map(|args| {
             args.iter().map(|arg| CString::new(arg.clone())).collect::<Result<Vec<CString>, _>>()
         })
         .unwrap_or(Ok(vec![]))?;
+    let user_passwd = runner::get_program_string(&start_info, "user").unwrap_or("fuchsia:x:42:42");
+    let credentials = Credentials::from_passwd(user_passwd)?;
+
     info!("start_component environment: {:?}", environ);
 
     let ns = start_info.ns.ok_or_else(|| anyhow!("Missing namespace"))?;
@@ -255,37 +274,26 @@
             .ok_or_else(|| anyhow!("Missing directory handlee in pkg namespace entry"))?
             .into_channel(),
     );
-    let root = syncio::directory_open_directory_async(
+
+    // The mounts are appplied in the order listed. Mounting will fail if the designated mount
+    // point doesn't exist in a previous mount. The root must be first so other mounts can be
+    // applied on top of it.
+    let mut mounts_iter = mounts.iter();
+    let (root_point, root_fs) = create_filesystem_from_spec(
         &pkg,
-        &root_path,
-        fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE,
-    )
-    .map_err(|e| anyhow!("Failed to open root: {}", e))?;
-
-    let files = files_from_numbered_handles(start_info.numbered_handles, &kernel)?;
-
-    let remotefs =
-        RemoteFs::new(root.into_channel(), fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE);
-
-    let fs = FsContext::new(remotefs);
-    for mnt in mount {
-        let mut field_iter = mnt.as_bytes().splitn(2, |c| *c == b':');
-        let mut mount_point =
-            field_iter.next().ok_or_else(|| anyhow!("mount point is missing from {:?}", mnt))?;
-        if mount_point.len() > 0 && mount_point[0] == b'/' {
-            mount_point = &mount_point[1..];
-        }
-        let fs_type =
-            field_iter.next().ok_or_else(|| anyhow!("fs type is missing from {:?}", mnt))?;
-        let child_fs = match fs_type {
-            b"tmpfs" => Ok(TmpFs::new()),
-            b"devfs" => Ok(DevFs::new()),
-            _ => Err(EINVAL),
-        }?;
-
+        mounts_iter.next().ok_or_else(|| anyhow!("Mounts list is empty"))?,
+    )?;
+    if root_point != b"/" {
+        anyhow::bail!("First mount in mounts list is not the root");
+    }
+    let fs = FsContext::new(root_fs);
+    for mount_spec in mounts_iter {
+        let (mount_point, child_fs) = create_filesystem_from_spec(&pkg, mount_spec)?;
         fs.lookup_node(fs.root.clone(), mount_point)?.mount(child_fs)?;
     }
 
+    let files = files_from_numbered_handles(start_info.numbered_handles, &kernel)?;
+
     let task_owner = Task::create_process(&kernel, &binary_path, 0, files, fs, credentials, None)?;
 
     let mut argv = vec![binary_path];
@@ -319,7 +327,7 @@
             fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
                 let kernel = kernel.clone();
                 fasync::Task::local(async move {
-                    if let Err(e) = start_component(kernel, start_info, controller).await {
+                    if let Err(e) = start_component(kernel, start_info, controller) {
                         error!("failed to start component: {}", e);
                     }
                 })
diff --git a/src/proc/tests/android/BUILD.gn b/src/proc/tests/android/BUILD.gn
index bfa8eac..89328a2 100644
--- a/src/proc/tests/android/BUILD.gn
+++ b/src/proc/tests/android/BUILD.gn
@@ -60,6 +60,11 @@
   subdir = "root"
 }
 
+resource("android_system_image_ext") {
+  sources = [ "//prebuilt/starnix/android-image-amd64/system.img" ]
+  outputs = [ "data/system.img" ]
+}
+
 fuchsia_component("init") {
   manifest = "meta/init.cml"
 }
@@ -187,6 +192,7 @@
 fuchsia_test_package("test_android_distro") {
   deps = [
     ":android_system_image",
+    ":android_system_image_ext",
     ":init",
     ":sh",
     ":syscalls_test",
diff --git a/src/proc/tests/android/meta/access_test.cml b/src/proc/tests/android/meta/access_test.cml
index 6de9185..aa9d0f1 100644
--- a/src/proc/tests/android/meta/access_test.cml
+++ b/src/proc/tests/android/meta/access_test.cml
@@ -3,7 +3,9 @@
     program: {
         binary: "system/bin/access_test",
         args: [ "--gunit_filter=*.RelativeFile:*.RelativeDir:*.AbsFile:*.AbsDir:*.RelDoesNotExist:*.AbsDoesNotExist:*.InvalidMode:*.InvalidName:*.UsrReadWrite:*.UsrReadWriteExec" ],
-        root: "root",
-        mount: [ "/tmp:tmpfs" ],
+        mounts: [
+            "/:remotefs:root",
+            "/tmp:tmpfs",
+        ],
     },
 }
diff --git a/src/proc/tests/android/meta/brk_test.cml b/src/proc/tests/android/meta/brk_test.cml
index 8490ca1..72fa6a0 100644
--- a/src/proc/tests/android/meta/brk_test.cml
+++ b/src/proc/tests/android/meta/brk_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/brk_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/clock_getres_test.cml b/src/proc/tests/android/meta/clock_getres_test.cml
index 711acdb..bf1b79f 100644
--- a/src/proc/tests/android/meta/clock_getres_test.cml
+++ b/src/proc/tests/android/meta/clock_getres_test.cml
@@ -3,6 +3,6 @@
     program: {
         binary: "system/bin/clock_getres_test",
         environ: [ "TEST_ON_GVISOR=1" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/clock_gettime_test.cml b/src/proc/tests/android/meta/clock_gettime_test.cml
index 053e082..1886795 100644
--- a/src/proc/tests/android/meta/clock_gettime_test.cml
+++ b/src/proc/tests/android/meta/clock_gettime_test.cml
@@ -4,6 +4,6 @@
         binary: "system/bin/clock_gettime_test",
         args: [ "--gunit_filter=-ClockGettime.JavaThreadTime:ClockGettime/MonotonicClockTest.IsMonotonic*" ],
         environ: [ "TEST_ON_GVISOR=1" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/exit_test.cml b/src/proc/tests/android/meta/exit_test.cml
index ec9cff7..696d904c 100644
--- a/src/proc/tests/android/meta/exit_test.cml
+++ b/src/proc/tests/android/meta/exit_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/exit_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/fork_test.cml b/src/proc/tests/android/meta/fork_test.cml
index dfcd50c..6f9e4a6 100644
--- a/src/proc/tests/android/meta/fork_test.cml
+++ b/src/proc/tests/android/meta/fork_test.cml
@@ -3,6 +3,6 @@
     program: {
         binary: "system/bin/fork_test",
         args: [ "--gunit_filter=ForkTest.*:-*.Alarm" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/getrandom_test.cml b/src/proc/tests/android/meta/getrandom_test.cml
index 906263e..7635321 100644
--- a/src/proc/tests/android/meta/getrandom_test.cml
+++ b/src/proc/tests/android/meta/getrandom_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/getrandom_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/init.cml b/src/proc/tests/android/meta/init.cml
index 1cb3287..ace0f8c 100644
--- a/src/proc/tests/android/meta/init.cml
+++ b/src/proc/tests/android/meta/init.cml
@@ -1,7 +1,10 @@
 {
     program: {
         runner: "starnix",
-        binary: "system/bin/hello_world",
-        root: "root",
+        binary: "system/bin/init",
+        mounts: [
+            "/:ext4:data/system.img",
+            "/dev:tmpfs",
+        ],
     },
 }
diff --git a/src/proc/tests/android/meta/kill_test.cml b/src/proc/tests/android/meta/kill_test.cml
index 8c0f927..b65b09c 100644
--- a/src/proc/tests/android/meta/kill_test.cml
+++ b/src/proc/tests/android/meta/kill_test.cml
@@ -4,6 +4,6 @@
         binary: "system/bin/kill_test",
         args: [ "--gunit_filter=KillTest.CanKillAllPIDs:KillTest.CannotKillInvalidPID:KillTest.CannotKillTid" ],
         environ: [ "TEST_ON_GVISOR=1" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/lseek_test.cml b/src/proc/tests/android/meta/lseek_test.cml
index da51a85..92916d3 100644
--- a/src/proc/tests/android/meta/lseek_test.cml
+++ b/src/proc/tests/android/meta/lseek_test.cml
@@ -3,7 +3,10 @@
     program: {
         binary: "system/bin/lseek_test",
         args: [ "--gunit_filter=-*.DirCurEnd:*.Proc*:*.SysDir:*.SeekCurrentDir:*.EtcPasswdDup" ],
-        root: "root",
-        mount: [ "/tmp:tmpfs" ],
+        args: [ "--gunit_filter=-*.Overflow:*.DirCurEnd:*.Proc*:*.SysDir:*.SeekCurrentDir:*.EtcPasswdDup" ],
+        mounts: [
+            "/:remotefs:root",
+            "/tmp:tmpfs",
+        ],
     },
 }
diff --git a/src/proc/tests/android/meta/munmap_test.cml b/src/proc/tests/android/meta/munmap_test.cml
index 83b56e6..31dc7bb 100644
--- a/src/proc/tests/android/meta/munmap_test.cml
+++ b/src/proc/tests/android/meta/munmap_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/munmap_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/pipe_test.cml b/src/proc/tests/android/meta/pipe_test.cml
index 552fbb8..324a864 100644
--- a/src/proc/tests/android/meta/pipe_test.cml
+++ b/src/proc/tests/android/meta/pipe_test.cml
@@ -3,7 +3,9 @@
     program: {
         binary: "system/bin/pipe_test",
         args: [ "--gunit_filter=-*Proc*:*StatFS*:*.Flags/named*" ],
-        root: "root",
-        mount: [ "/tmp:tmpfs" ],
+        mounts: [
+            "/:remotefs:root",
+            "/tmp:tmpfs",
+        ],
     },
 }
diff --git a/src/proc/tests/android/meta/sh.cml b/src/proc/tests/android/meta/sh.cml
index 0ec7628..68111b7 100644
--- a/src/proc/tests/android/meta/sh.cml
+++ b/src/proc/tests/android/meta/sh.cml
@@ -3,6 +3,6 @@
         runner: "starnix",
         binary: "system/bin/sh",
         args: [ "-i" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/sh_test.cml b/src/proc/tests/android/meta/sh_test.cml
index 6e8889b..0755284 100644
--- a/src/proc/tests/android/meta/sh_test.cml
+++ b/src/proc/tests/android/meta/sh_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/sh",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/sigaction_test.cml b/src/proc/tests/android/meta/sigaction_test.cml
index 499dc48..9e7657d 100644
--- a/src/proc/tests/android/meta/sigaction_test.cml
+++ b/src/proc/tests/android/meta/sigaction_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/sigaction_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/sigaltstack_test.cml b/src/proc/tests/android/meta/sigaltstack_test.cml
index bfe59c6..5eaa113 100644
--- a/src/proc/tests/android/meta/sigaltstack_test.cml
+++ b/src/proc/tests/android/meta/sigaltstack_test.cml
@@ -3,6 +3,6 @@
     program: {
         binary: "system/bin/sigaltstack_test",
         args: [ "--gunit_filter=SigaltstackTest.Success" ],
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/sigprocmask_test.cml b/src/proc/tests/android/meta/sigprocmask_test.cml
index ef9db1e..990a841 100644
--- a/src/proc/tests/android/meta/sigprocmask_test.cml
+++ b/src/proc/tests/android/meta/sigprocmask_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/sigprocmask_test",
-        root: "root",
+        mounts: [ "/:remotefs:root" ],
     },
 }
diff --git a/src/proc/tests/android/meta/symlink_test.cml b/src/proc/tests/android/meta/symlink_test.cml
index 4b1c21c..ba1ff6c 100644
--- a/src/proc/tests/android/meta/symlink_test.cml
+++ b/src/proc/tests/android/meta/symlink_test.cml
@@ -3,7 +3,9 @@
     program: {
         binary: "system/bin/symlink_test",
         args: [ "--gunit_filter=-SymlinkTest.CannotCreateSymlinkInReadOnlyDir:SymlinkTest.SymlinkAtDegradedPermissions:SymlinkTest.ReadlinkAtDegradedPermissions:AbsAndRelTarget/ParamSymlinkTest*" ],
-        root: "root",
-        mount: [ "/tmp:tmpfs" ],
+        mounts: [
+            "/:remotefs:root",
+            "/tmp:tmpfs",
+        ],
     },
 }
diff --git a/src/proc/tests/android/meta/syscalls_test.cml b/src/proc/tests/android/meta/syscalls_test.cml
index d7e8845..7f13c3f 100644
--- a/src/proc/tests/android/meta/syscalls_test.cml
+++ b/src/proc/tests/android/meta/syscalls_test.cml
@@ -2,8 +2,8 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "system/bin/syscalls_test",
-        root: "root",
-        mount: [
+        mounts: [
+            "/:remotefs:root",
             "/data:tmpfs",
             "/tmp:tmpfs",
         ],
diff --git a/src/proc/tests/hello_starnix/meta/hello_starnix.cml b/src/proc/tests/hello_starnix/meta/hello_starnix.cml
index 7881d45..69c9e5b 100644
--- a/src/proc/tests/hello_starnix/meta/hello_starnix.cml
+++ b/src/proc/tests/hello_starnix/meta/hello_starnix.cml
@@ -2,6 +2,6 @@
     program: {
         runner: "starnix",
         binary: "hello_starnix",
-        root: "bin",
+        mounts: [ "/:remotefs:bin" ],
     },
 }
diff --git a/src/proc/tests/hello_starnix/meta/hello_starnix_test.cml b/src/proc/tests/hello_starnix/meta/hello_starnix_test.cml
index 4300ba4..87c3c68 100644
--- a/src/proc/tests/hello_starnix/meta/hello_starnix_test.cml
+++ b/src/proc/tests/hello_starnix/meta/hello_starnix_test.cml
@@ -2,6 +2,6 @@
     include: [ "//src/sys/test_runners/starnix/default.shard.cml" ],
     program: {
         binary: "bin/hello_starnix",
-        root: "",
+        mounts: [ "/:remotefs:/" ],
     },
 }