[dash-launcher] Implement `chns`, `chexp` and `chout`

These helper binaries allow launching a tool using the
component namespace/exposed/out directory as the root.

To launch the tool `iquery` rooted at the exposed
dir of `/bootstrap/archivist`:

```
> ffx component explore /bootstrap/archivist
$ chexp iquery list
```

This will help tools that have trouble with
compatibility with the dash namespace.

Test: fx test chroot
Change-Id: Ie4fac40799ad6bf07512b8d3831235f3106b48dd
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/687730
Commit-Queue: Xyan Bhatnagar <xbhatnag@google.com>
Reviewed-by: Shai Barack <shayba@google.com>
Reviewed-by: Adam Perry <adamperry@google.com>
diff --git a/src/sys/tools/BUILD.gn b/src/sys/tools/BUILD.gn
index a5a09cc..3be7328 100644
--- a/src/sys/tools/BUILD.gn
+++ b/src/sys/tools/BUILD.gn
@@ -5,6 +5,7 @@
 group("tests") {
   testonly = true
   deps = [
+    "chroot:tests",
     "dash-launcher:tests",
     "log:tests",
   ]
diff --git a/src/sys/tools/chroot/BUILD.gn b/src/sys/tools/chroot/BUILD.gn
new file mode 100644
index 0000000..ea9b51b
--- /dev/null
+++ b/src/sys/tools/chroot/BUILD.gn
@@ -0,0 +1,51 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/components.gni")
+import("//build/rust/rustc_binary.gni")
+
+common_deps = [
+  "//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
+  "//src/lib/fdio",
+  "//src/lib/fidl/rust/fidl",
+  "//src/lib/fuchsia",
+  "//src/lib/fuchsia-async",
+  "//src/lib/fuchsia-component",
+  "//src/lib/fuchsia-fs",
+  "//src/lib/fuchsia-runtime",
+  "//src/lib/zircon/rust:fuchsia-zircon",
+  "//third_party/rust_crates:anyhow",
+  "//third_party/rust_crates:argh",
+]
+
+rustc_binary("chns") {
+  edition = "2021"
+  deps = common_deps
+  sources = [ "src/main.rs" ]
+}
+
+rustc_binary("chout") {
+  edition = "2021"
+  deps = common_deps
+  sources = [ "src/main.rs" ]
+}
+
+rustc_binary("chexp") {
+  edition = "2021"
+  deps = common_deps
+  sources = [ "src/main.rs" ]
+}
+
+group("chroot") {
+  deps = [
+    ":chexp",
+    ":chns",
+    ":chout",
+  ]
+}
+
+group("tests") {
+  testonly = true
+  deps = [ "integration_test" ]
+}
diff --git a/src/sys/tools/chroot/OWNERS b/src/sys/tools/chroot/OWNERS
new file mode 100644
index 0000000..9d2de297
--- /dev/null
+++ b/src/sys/tools/chroot/OWNERS
@@ -0,0 +1,3 @@
+xbhatnag@google.com
+gboone@google.com
+adamperry@google.com
diff --git a/src/sys/tools/chroot/integration_test/BUILD.gn b/src/sys/tools/chroot/integration_test/BUILD.gn
new file mode 100644
index 0000000..00f8230
--- /dev/null
+++ b/src/sys/tools/chroot/integration_test/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/components.gni")
+import("//build/rust/rustc_binary.gni")
+import("//build/rust/rustc_test.gni")
+
+rustc_test("test_bin") {
+  output_name = "chroot_test"
+  edition = "2021"
+
+  deps = [ "//src/lib/fuchsia" ]
+
+  sources = [ "src/lib.rs" ]
+}
+
+rustc_binary("writer") {
+  output_name = "test_writer"
+  edition = "2021"
+  source_root = "src/writer.rs"
+  sources = [ "src/writer.rs" ]
+}
+
+fuchsia_unittest_package("chroot-test") {
+  manifest = "meta/test.cml"
+  deps = [
+    ":test_bin",
+    ":writer",
+    "//src/sys/tools/chroot:chns",
+  ]
+}
+
+group("integration_test") {
+  testonly = true
+  deps = [ ":chroot-test" ]
+}
diff --git a/src/sys/tools/chroot/integration_test/meta/test.cml b/src/sys/tools/chroot/integration_test/meta/test.cml
new file mode 100644
index 0000000..115ccda
--- /dev/null
+++ b/src/sys/tools/chroot/integration_test/meta/test.cml
@@ -0,0 +1,19 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+{
+    include: [
+        "//src/sys/test_runners/rust/default.shard.cml",
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        binary: "bin/chroot_test",
+    },
+    use: [
+        {
+            storage: "tmp",
+            path: "/ns",
+        },
+        { protocol: "fuchsia.process.Launcher" },
+    ],
+}
diff --git a/src/sys/tools/chroot/integration_test/src/lib.rs b/src/sys/tools/chroot/integration_test/src/lib.rs
new file mode 100644
index 0000000..f2f7fc0
--- /dev/null
+++ b/src/sys/tools/chroot/integration_test/src/lib.rs
@@ -0,0 +1,15 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[fuchsia::test]
+pub fn chns() {
+    // Launch test_writer using chns
+    let status =
+        std::process::Command::new("/pkg/bin/chns").arg("/pkg/bin/test_writer").status().unwrap();
+    assert!(status.success());
+
+    // test_writer should have made a new file under /ns
+    let contents = std::fs::read_to_string("/ns/foo.txt").unwrap();
+    assert_eq!(contents, "Hippos rule!");
+}
diff --git a/src/sys/tools/chroot/integration_test/src/writer.rs b/src/sys/tools/chroot/integration_test/src/writer.rs
new file mode 100644
index 0000000..8d8feb0
--- /dev/null
+++ b/src/sys/tools/chroot/integration_test/src/writer.rs
@@ -0,0 +1,10 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub fn main() {
+    // This binary is launched with `/ns` as the root.
+    // Because `/ns` is a tmp storage capability, the root
+    // directory should be completely writable.
+    std::fs::write("/foo.txt", "Hippos rule!").unwrap();
+}
diff --git a/src/sys/tools/chroot/src/main.rs b/src/sys/tools/chroot/src/main.rs
new file mode 100644
index 0000000..29ba679
--- /dev/null
+++ b/src/sys/tools/chroot/src/main.rs
@@ -0,0 +1,113 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use anyhow::{bail, Error};
+use argh::FromArgs;
+use fidl::endpoints::Proxy;
+use fidl_fuchsia_io as fio;
+use fuchsia_async as fasync;
+use fuchsia_zircon as zx;
+use std::env::{split_paths, var_os};
+use std::ffi::{CStr, CString};
+use std::path::{Component, PathBuf};
+
+#[derive(FromArgs, Debug, PartialEq)]
+#[argh(
+    name = "chroot",
+    description = "Runs a binary after changing the root to the component's namespace/exposed/out directory",
+    note = "This binary's behavior is determined by the name it was launched as.
+    
+`chns` sets the `/ns` directory as the root for the binary
+`chexp` sets the `/exposed` directory as the root for the binary
+`chout` sets the `/out` directory as the root for the binary
+
+This binary is built to work with dash-launcher."
+)]
+
+pub struct ChrootParams {
+    #[argh(positional)]
+    /// path/name of binary
+    pub bin_path: PathBuf,
+
+    #[argh(positional)]
+    /// arguments to be passed to binary
+    pub argv: Vec<String>,
+}
+
+fn resolve_binary_path(bin_path: PathBuf) -> Result<PathBuf, Error> {
+    if bin_path.is_file() {
+        return Ok(bin_path);
+    }
+
+    let mut components: Vec<Component<'_>> = bin_path.components().collect();
+
+    if components.len() == 1 {
+        let component = components.remove(0);
+        if let Component::Normal(executable) = component {
+            if let Some(paths) = var_os("PATH") {
+                for path in split_paths(&paths) {
+                    let full_path = path.join(&executable);
+                    if full_path.is_file() {
+                        return Ok(full_path);
+                    }
+                }
+            }
+        }
+    }
+
+    bail!("'{}' does not match any known binary files", bin_path.display())
+}
+
+#[fuchsia::main(logging = false)]
+async fn main() -> Result<(), Error> {
+    let params: ChrootParams = argh::from_env();
+    let self_bin_path = std::env::args().next().unwrap();
+
+    // Get a path to the binary (use the PATH variable if needed)
+    let bin_path = resolve_binary_path(params.bin_path)?;
+    let bin_path = bin_path.display().to_string();
+
+    let job = fuchsia_runtime::job_default();
+    let options = fdio::SpawnOptions::CLONE_STDIO
+        | fdio::SpawnOptions::CLONE_ENVIRONMENT
+        | fdio::SpawnOptions::CLONE_JOB
+        | fdio::SpawnOptions::DEFAULT_LOADER;
+
+    // Construct the argv for the binary
+    let mut argv = params.argv;
+    argv.insert(0, bin_path.clone());
+    let argv: Vec<CString> = argv.into_iter().map(|a| CString::new(a).unwrap()).collect();
+    let argv_ref: Vec<&CStr> = argv.iter().map(|s| s.as_c_str()).collect();
+
+    // Create the namespace for the binary based on our name
+    let (local_path, new_path) = if self_bin_path.ends_with("chns") {
+        ("/ns", "/")
+    } else if self_bin_path.ends_with("chout") {
+        ("/out", "/")
+    } else {
+        // The /exposed directory puts protocols at the top-level.
+        ("/exposed", "/svc")
+    };
+
+    let local_dir = fuchsia_fs::open_directory_in_namespace(
+        local_path,
+        fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
+    )
+    .unwrap()
+    .into_channel()
+    .unwrap()
+    .into_zx_channel()
+    .into();
+    let new_path = CString::new(new_path).unwrap();
+    let ns_entry_action = fdio::SpawnAction::add_namespace_entry(&new_path, local_dir);
+    let mut actions = [ns_entry_action];
+
+    // Launch the binary
+    let bin_path = CString::new(bin_path).unwrap();
+    let process = fdio::spawn_etc(&job, options, &bin_path, &argv_ref, None, &mut actions).unwrap();
+
+    // Wait for it to terminate
+    let _ = fasync::OnSignals::new(&process, zx::Signals::PROCESS_TERMINATED).await;
+    Ok(())
+}
diff --git a/src/sys/tools/dash-launcher/BUILD.gn b/src/sys/tools/dash-launcher/BUILD.gn
index 34a6760..49cb1c0 100644
--- a/src/sys/tools/dash-launcher/BUILD.gn
+++ b/src/sys/tools/dash-launcher/BUILD.gn
@@ -59,6 +59,7 @@
 fuchsia_package("dash-launcher") {
   deps = [
     ":component",
+    "//src/sys/tools/chroot",
     "//third_party/sbase:bins",
     "//zircon/third_party/uapp/dash",
   ]