[dash-launcher] Load libraries from multiple packages

`library_loader` now supports multiple library directories.

`dash-launcher` now supplies libraries from two sources:
* `/pkg/lib` of the explored component
* `/pkg/lib` from the launcher namespace

This ensures that binaries packaged with the launcher
can run successfully.

Test: fx test library_loader
Change-Id: Ifcc9e8d785c417e275143b1314c9251e7196219a
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/693791
Reviewed-by: Shai Barack <shayba@google.com>
Reviewed-by: Gary Boone <gboone@google.com>
Commit-Queue: Xyan Bhatnagar <xbhatnag@google.com>
diff --git a/src/sys/lib/library_loader/src/lib.rs b/src/sys/lib/library_loader/src/lib.rs
index cd250c4..48ad1ea 100644
--- a/src/sys/lib/library_loader/src/lib.rs
+++ b/src/sys/lib/library_loader/src/lib.rs
@@ -39,9 +39,19 @@
 /// `lib_proxy` must have been opened with at minimum OPEN_RIGHT_READABLE and OPEN_RIGHT_EXECUTABLE
 /// rights.
 pub fn start(lib_proxy: Arc<fio::DirectoryProxy>, chan: zx::Channel) {
+    start_with_multiple_dirs(vec![lib_proxy], chan);
+}
+
+/// start_with_multiple_dirs will expose the `fuchsia.ldsvc.Loader` service over the given channel,
+/// providing VMO buffers of requested library object names from any of the library directories in
+/// `lib_dirs`.
+///
+/// Each library directory must have been opened with at minimum OPEN_RIGHT_READABLE and
+/// OPEN_RIGHT_EXECUTABLE rights.
+pub fn start_with_multiple_dirs(lib_dirs: Vec<Arc<fio::DirectoryProxy>>, chan: zx::Channel) {
     fasync::Task::spawn(
         async move {
-            let mut search_dirs = vec![lib_proxy.clone()];
+            let mut search_dirs = lib_dirs.clone();
             // Wait for requests
             let mut stream =
                 LoaderRequestStream::from_channel(fasync::Channel::from_channel(chan)?);
@@ -60,7 +70,7 @@
                         }
                     }
                     LoaderRequest::Config { config, responder } => {
-                        match parse_config_string(&lib_proxy, &config) {
+                        match parse_config_string(&lib_dirs, &config) {
                             Ok(new_search_path) => {
                                 search_dirs = new_search_path;
                                 responder.send(zx::sys::ZX_OK)?;
@@ -72,7 +82,7 @@
                         }
                     }
                     LoaderRequest::Clone { loader, responder } => {
-                        start(lib_proxy.clone(), loader.into_channel());
+                        start_with_multiple_dirs(lib_dirs.clone(), loader.into_channel());
                         responder.send(zx::sys::ZX_OK)?;
                     }
                 }
@@ -116,27 +126,37 @@
 /// `//docs/concepts/booting/program_loading.md` for a description of the format. Returns the set
 /// of directories which should be searched for objects.
 pub fn parse_config_string(
-    dir_proxy: &Arc<fio::DirectoryProxy>,
+    lib_dirs: &Vec<Arc<fio::DirectoryProxy>>,
     config: &str,
 ) -> Result<Vec<Arc<fio::DirectoryProxy>>, Error> {
     if config.contains("/") {
         return Err(format_err!("'/' character found in loader service config string"));
     }
+    let mut search_dirs = vec![];
     if Some('!') == config.chars().last() {
-        let sub_dir_proxy = fuchsia_fs::open_directory(
-            dir_proxy,
-            &Path::new(&config[..config.len() - 1]),
-            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
-        )?;
-        Ok(vec![sub_dir_proxy.into()])
+        // Only search the subdirs.
+        for dir_proxy in lib_dirs {
+            let sub_dir_proxy = fuchsia_fs::open_directory(
+                dir_proxy,
+                &Path::new(&config[..config.len() - 1]),
+                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
+            )?;
+            search_dirs.push(Arc::new(sub_dir_proxy));
+        }
     } else {
-        let sub_dir_proxy = fuchsia_fs::open_directory(
-            dir_proxy,
-            &Path::new(config),
-            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
-        )?;
-        Ok(vec![sub_dir_proxy.into(), dir_proxy.clone()])
+        // Search the subdirs and the root dirs.
+        for dir_proxy in lib_dirs {
+            let sub_dir_proxy = fuchsia_fs::open_directory(
+                dir_proxy,
+                &Path::new(config),
+                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
+            )?;
+            search_dirs.push(Arc::new(sub_dir_proxy));
+        }
+
+        search_dirs.append(&mut lib_dirs.clone());
     }
+    Ok(search_dirs)
 }
 
 #[cfg(test)]
@@ -256,4 +276,47 @@
         }
         Ok(())
     }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn load_objects_multiple_dir_test() -> Result<(), Error> {
+        // This /pkg/lib/config_test/ directory is added by the build rules for this test package,
+        // since we need a directory that supports OPEN_RIGHT_EXECUTABLE. It contains a file 'foo'
+        // which contains 'hippos' and a file 'bar/baz' (that is, baz in a subdirectory bar) which
+        // contains 'rule'.
+        // TODO(fxbug.dev/37534): Use a synthetic /pkg/lib in this test so it doesn't depend on the
+        // package layout once Rust vfs supports OPEN_RIGHT_EXECUTABLE
+        let pkg_lib_1 = fuchsia_fs::open_directory_in_namespace(
+            "/pkg/lib/config_test/",
+            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
+        )?;
+        let pkg_lib_2 = fuchsia_fs::open_directory_in_namespace(
+            "/pkg/lib/config_test/bar",
+            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
+        )?;
+
+        let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>()?;
+        start_with_multiple_dirs(
+            vec![pkg_lib_1.into(), pkg_lib_2.into()],
+            loader_service.into_channel(),
+        );
+
+        for (obj_name, should_succeed) in vec![
+            // Should be able to access foo from dir #1
+            ("foo", true),
+            // Should be able to access baz from dir #2
+            ("baz", true),
+            // Should not be able to access bar (it's a directory)
+            ("bar", false),
+        ] {
+            let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
+            if should_succeed {
+                assert_eq!(zx::sys::ZX_OK, res, "loading {} did not succeed", obj_name);
+                assert!(o_vmo.is_some());
+            } else {
+                assert_ne!(zx::sys::ZX_OK, res, "loading {} did not fail", obj_name);
+                assert!(o_vmo.is_none());
+            }
+        }
+        Ok(())
+    }
 }
diff --git a/src/sys/test_runners/lib_loader_cache/src/loader_cache.rs b/src/sys/test_runners/lib_loader_cache/src/loader_cache.rs
index 4a51168..983a7f5 100644
--- a/src/sys/test_runners/lib_loader_cache/src/loader_cache.rs
+++ b/src/sys/test_runners/lib_loader_cache/src/loader_cache.rs
@@ -119,7 +119,7 @@
                     }
                     LoaderRequest::Config { config, responder } => {
                         match library_loader::parse_config_string(
-                            &lib_loader_cache.lib_proxy,
+                            &vec![lib_loader_cache.lib_proxy.clone()],
                             &config,
                         ) {
                             Ok(new_search_path) => {
diff --git a/src/sys/tools/dash-launcher/src/launch.rs b/src/sys/tools/dash-launcher/src/launch.rs
index 8229475..a1fd588 100644
--- a/src/sys/tools/dash-launcher/src/launch.rs
+++ b/src/sys/tools/dash-launcher/src/launch.rs
@@ -190,6 +190,18 @@
     Ok((stdin, stdout, stderr))
 }
 
+fn get_lib_from_launcher_namespace() -> Result<fio::DirectoryProxy, LauncherError> {
+    let (lib_dir, server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
+    fuchsia_fs::node::connect_in_namespace(
+        "/pkg/lib",
+        fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
+        server.into_channel(),
+    )
+    .map_err(|_| LauncherError::Internal)?;
+
+    Ok(lib_dir)
+}
+
 fn create_dash_handles(
     job: &zx::Job,
     stdin: zx::Handle,
@@ -219,14 +231,20 @@
         id: HandleId::new(HandleType::DefaultJob, 0).as_raw(),
     };
 
-    let ldsvc = if let Some(lib_dir) = lib_dir {
-        let (ldsvc, server_end) = zx::Channel::create().map_err(|_| LauncherError::Internal)?;
-        library_loader::start(Arc::new(lib_dir), server_end);
-        ldsvc.into_handle()
-    } else {
-        // Use the default loader in the dash-launcher process
-        fuchsia_runtime::loader_svc().map_err(|_| LauncherError::Internal)?
-    };
+    // Create a library loader that uses the component's /pkg/lib dir first and
+    // falls back to the launcher's /pkg/lib dir if needed.
+    let mut lib_dirs = vec![];
+    if let Some(lib_dir) = lib_dir {
+        lib_dirs.push(Arc::new(lib_dir));
+    }
+
+    let launcher_lib_dir = get_lib_from_launcher_namespace()?;
+    lib_dirs.push(Arc::new(launcher_lib_dir));
+
+    let (ldsvc, server_end) = zx::Channel::create().map_err(|_| LauncherError::Internal)?;
+    let ldsvc = ldsvc.into_handle();
+    library_loader::start_with_multiple_dirs(lib_dirs, server_end);
+
     let ldsvc_handle =
         HandleInfo { handle: ldsvc, id: HandleId::new(HandleType::LdsvcLoader, 0).as_raw() };