[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",
]