1use anyhow::{format_err, Error};
6use fidl::prelude::*;
7use fidl_fuchsia_ldsvc::{LoaderRequest, LoaderRequestStream};
8use futures::{TryFutureExt, TryStreamExt};
9use log::*;
10use std::sync::Arc;
11use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
12
13pub async fn load_object(
17 search_dirs: &Vec<Arc<fio::DirectoryProxy>>,
18 object_name: &str,
19) -> Result<zx::Vmo, Vec<Error>> {
20 let mut errors = vec![];
21 for dir_proxy in search_dirs {
22 match load_vmo(dir_proxy, &object_name).await {
23 Ok(b) => {
24 return Ok(b);
25 }
26 Err(e) => errors.push(e),
27 }
28 }
29 Err(errors.into())
30}
31
32pub fn start(lib_proxy: Arc<fio::DirectoryProxy>, chan: zx::Channel) {
38 start_with_multiple_dirs(vec![lib_proxy], chan);
39}
40
41pub fn start_with_multiple_dirs(lib_dirs: Vec<Arc<fio::DirectoryProxy>>, chan: zx::Channel) {
48 fasync::Task::spawn(
49 async move {
50 let mut search_dirs = lib_dirs.clone();
51 let mut stream = LoaderRequestStream::from_channel(fasync::Channel::from_channel(chan));
53 while let Some(req) = stream.try_next().await? {
54 match req {
55 LoaderRequest::Done { control_handle } => {
56 control_handle.shutdown();
57 }
58 LoaderRequest::LoadObject { object_name, responder } => {
59 match load_object(&search_dirs, &object_name).await {
60 Ok(vmo) => responder.send(zx::sys::ZX_OK, Some(vmo))?,
61 Err(e) => {
62 warn!("failed to load object: {:?}", e);
63 responder.send(zx::sys::ZX_ERR_NOT_FOUND, None)?;
64 }
65 }
66 }
67 LoaderRequest::Config { config, responder } => {
68 match parse_config_string(&lib_dirs, &config) {
69 Ok(new_search_path) => {
70 search_dirs = new_search_path;
71 responder.send(zx::sys::ZX_OK)?;
72 }
73 Err(e) => {
74 warn!("failed to parse config: {}", e);
75 responder.send(zx::sys::ZX_ERR_INVALID_ARGS)?;
76 }
77 }
78 }
79 LoaderRequest::Clone { loader, responder } => {
80 start_with_multiple_dirs(lib_dirs.clone(), loader.into_channel());
81 responder.send(zx::sys::ZX_OK)?;
82 }
83 }
84 }
85 Ok(())
86 }
87 .unwrap_or_else(|e: Error| warn!("couldn't run library loader service: {}", e)),
88 )
89 .detach();
90}
91
92pub async fn load_vmo<'a>(
98 dir: &impl fuchsia_component::directory::AsRefDirectory,
99 object_name: &'a str,
100) -> Result<zx::Vmo, Error> {
101 let file_proxy =
102 fuchsia_component::directory::open_file_async(dir, object_name, fio::RX_STAR_DIR)?;
103 let vmo = file_proxy
106 .get_backing_memory(
107 fio::VmoFlags::READ | fio::VmoFlags::EXECUTE | fio::VmoFlags::PRIVATE_CLONE,
109 )
110 .await
111 .map_err(|e| format_err!("reading object at {:?} failed: {}", object_name, e))?
112 .map_err(|status| {
113 let status = zx::Status::from_raw(status);
114 format_err!("reading object at {:?} failed: {}", object_name, status)
115 })?;
116 Ok(vmo)
117}
118
119pub fn parse_config_string(
123 lib_dirs: &Vec<Arc<fio::DirectoryProxy>>,
124 config: &str,
125) -> Result<Vec<Arc<fio::DirectoryProxy>>, Error> {
126 if config.contains("/") {
127 return Err(format_err!("'/' character found in loader service config string"));
128 }
129 let (config, search_root) = match config.strip_suffix('!') {
130 Some(config) => (config, false),
131 None => (config, true),
132 };
133 let mut search_dirs = vec![];
134 for dir_proxy in lib_dirs {
135 let sub_dir_proxy = fuchsia_fs::directory::open_directory_async(
136 dir_proxy,
137 config,
138 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
139 )?;
140 search_dirs.push(Arc::new(sub_dir_proxy));
141 }
142 if search_root {
143 search_dirs.append(&mut lib_dirs.clone());
144 }
145 Ok(search_dirs)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use fidl_fuchsia_ldsvc::LoaderMarker;
152
153 async fn list_directory<'a>(root_proxy: &'a fio::DirectoryProxy) -> Vec<String> {
154 let entries = fuchsia_fs::directory::readdir(root_proxy).await.expect("readdir failed");
155 entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>()
156 }
157
158 #[fasync::run_singlethreaded(test)]
159 async fn load_objects_test() -> Result<(), Error> {
160 let rights = fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE;
164 let mut pkg_lib = fuchsia_fs::directory::open_in_namespace("/pkg/lib", rights)?;
165 let entries = list_directory(&pkg_lib).await;
166 if let Some(name) = [
167 "asan",
168 "asan-ubsan",
169 "coverage",
170 "coverage-cts",
171 "coverage-rust",
172 "hwasan",
173 "hwasan-ubsan",
174 "profile",
175 ]
176 .iter()
177 .find(|&&name| entries.iter().any(|f| f == name))
178 {
179 pkg_lib = fuchsia_fs::directory::open_directory_async(&pkg_lib, name, rights)?;
180 }
181 let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
182 start(pkg_lib.into(), loader_service.into_channel());
183
184 for (obj_name, should_succeed) in vec![
185 ("ld.so.1", true),
187 ("libfdio.so", true),
189 ("lib/ld.so.1", false),
191 ("../lib/ld.so.1", false),
193 ("../test/component_manager_tests", false),
195 ("bin/hello_world", false),
197 ("../bin/hello_world", false),
199 ("../meta/hello_world.cm", false),
201 ] {
202 let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
203 if should_succeed {
204 assert_eq!(zx::sys::ZX_OK, res, "loading {} did not succeed", obj_name);
205 assert!(o_vmo.is_some());
206 } else {
207 assert_ne!(zx::sys::ZX_OK, res, "loading {} did not fail", obj_name);
208 assert!(o_vmo.is_none());
209 }
210 }
211 Ok(())
212 }
213
214 #[fasync::run_singlethreaded(test)]
215 async fn config_test() -> Result<(), Error> {
216 let pkg_lib = fuchsia_fs::directory::open_in_namespace(
222 "/pkg/lib/config_test/",
223 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
224 )?;
225 let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
226 start(pkg_lib.into(), loader_service.into_channel());
227
228 for (obj_name, config, expected_result) in vec![
230 ("foo", None, Some("hippos")),
232 ("bar", None, None),
234 ("baz", None, None),
236 ("baz", Some("bar!"), Some("rule")),
238 ("foo", Some("bar!"), None),
240 ("foo", Some("bar"), Some("hippos")),
242 ("baz", Some("bar"), Some("rule")),
244 ] {
245 if let Some(config) = config {
246 assert_eq!(zx::sys::ZX_OK, loader_proxy.config(config).await?);
247 }
248
249 let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
250 if let Some(expected_result) = expected_result {
251 assert_eq!(zx::sys::ZX_OK, res);
252 let mut buf = vec![0; expected_result.len()];
253 o_vmo.ok_or(format_err!("missing vmo"))?.read(&mut buf, 0)?;
254 assert_eq!(expected_result.as_bytes(), buf.as_slice());
255 } else {
256 assert_ne!(zx::sys::ZX_OK, res);
257 assert!(o_vmo.is_none());
258 }
259 }
260 Ok(())
261 }
262
263 #[fasync::run_singlethreaded(test)]
264 async fn load_objects_multiple_dir_test() -> Result<(), Error> {
265 let pkg_lib_1 = fuchsia_fs::directory::open_in_namespace(
271 "/pkg/lib/config_test/",
272 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
273 )?;
274 let pkg_lib_2 = fuchsia_fs::directory::open_in_namespace(
275 "/pkg/lib/config_test/bar",
276 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
277 )?;
278
279 let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
280 start_with_multiple_dirs(
281 vec![pkg_lib_1.into(), pkg_lib_2.into()],
282 loader_service.into_channel(),
283 );
284
285 for (obj_name, should_succeed) in vec![
286 ("foo", true),
288 ("baz", true),
290 ("bar", false),
292 ] {
293 let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
294 if should_succeed {
295 assert_eq!(zx::sys::ZX_OK, res, "loading {} did not succeed", obj_name);
296 assert!(o_vmo.is_some());
297 } else {
298 assert_ne!(zx::sys::ZX_OK, res, "loading {} did not fail", obj_name);
299 assert!(o_vmo.is_none());
300 }
301 }
302 Ok(())
303 }
304}