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