component_debug/
io.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// TODO(https://fxbug.dev/42144933): Migrate to the new library that substitutes io_utils and fuchsia_fs::directory.
6// Ask for host-side support on the new library (fxr/467217).
7
8use anyhow::{anyhow, format_err, Error, Result};
9use async_trait::async_trait;
10use futures::lock::Mutex;
11use std::path::{Path, PathBuf};
12use std::{env, fs};
13use zx_status::Status;
14
15#[cfg(feature = "fdomain")]
16use fuchsia_fs_fdomain as fuchsia_fs;
17
18use flex_client::ProxyHasDomain;
19use flex_fuchsia_io as fio;
20use fuchsia_fs::directory::{open_directory_async, open_file_async, readdir, DirEntry};
21use fuchsia_fs::file::{close, read, read_to_string, write};
22
23pub enum DirentKind {
24    File,
25    Directory,
26}
27
28#[async_trait]
29pub trait Directory: Sized {
30    /// Attempts to open the directory at `relative_path` with read-only rights.
31    fn open_dir_readonly<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
32
33    /// Attempts to open the directory at `relative_path` with read/write rights.
34    fn open_dir_readwrite<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
35
36    /// Attempts to create a directory at `relative_path` with read right (if `readwrite` is false) or read/write rights (if `readwrite` is true).
37    fn create_dir<P: AsRef<Path> + Send>(&self, relative_path: P, readwrite: bool) -> Result<Self>;
38
39    /// Return a copy of self.
40    fn clone(&self) -> Result<Self>;
41
42    /// Returns the contents of the file at `relative_path` as a string.
43    async fn read_file<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<String>;
44
45    /// Returns the contents of the file at `relative_path` as bytes.
46    async fn read_file_bytes<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Vec<u8>>;
47
48    /// Returns true if an entry called `filename` exists in this directory. `filename` must be
49    /// a plain file name, not a relative path.
50    async fn exists(&self, filename: &str) -> Result<bool>;
51
52    /// Returns the type of entry specified by `filename`, or None if no entry by that name
53    /// is found. `filename` must be a plan file name, not a relative path.
54    async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>>;
55
56    /// Deletes the file at `relative_path`.
57    async fn remove(&self, relative_path: &str) -> Result<()>;
58
59    /// Writes `data` to a file at `relative_path`. Overwrites if the file already
60    /// exists.
61    async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()>;
62
63    /// Returns the size of the file at `relative_path` in bytes.
64    async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64>;
65
66    /// Returns a list of directory entry names as strings.
67    async fn entry_names(&self) -> Result<Vec<String>>;
68}
69
70/// A convenience wrapper over a local directory.
71#[derive(Debug)]
72pub struct LocalDirectory {
73    path: PathBuf,
74}
75
76impl LocalDirectory {
77    /// Returns a new local directory wrapper with no base path component. All operations will
78    /// be on paths relative to the environment used by std::fs.
79    pub fn new() -> Self {
80        LocalDirectory { path: PathBuf::new() }
81    }
82
83    /// Returns a new local directory such that the methods on `Self` will
84    /// operate as expected on `path`:
85    ///
86    ///     - if `path` is absolute, returns a directory at "/"
87    ///     - if `path` is relative, returns a directory at `cwd`
88    pub fn for_path(path: &PathBuf) -> Self {
89        if path.is_absolute() {
90            LocalDirectory { path: "/".into() }
91        } else {
92            LocalDirectory { path: env::current_dir().unwrap() }
93        }
94    }
95}
96
97#[async_trait]
98impl Directory for LocalDirectory {
99    fn open_dir_readonly<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self> {
100        let full_path = self.path.join(relative_path);
101        Ok(LocalDirectory { path: full_path })
102    }
103
104    fn open_dir_readwrite<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self> {
105        self.open_dir_readonly(relative_path)
106    }
107
108    fn create_dir<P: AsRef<Path> + Send>(
109        &self,
110        relative_path: P,
111        _readwrite: bool,
112    ) -> Result<Self> {
113        let full_path = self.path.join(relative_path);
114        fs::create_dir(full_path.clone())?;
115        Ok(LocalDirectory { path: full_path })
116    }
117
118    fn clone(&self) -> Result<Self> {
119        Ok(LocalDirectory { path: self.path.clone() })
120    }
121
122    async fn read_file<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<String> {
123        let full_path = self.path.join(relative_path);
124        fs::read_to_string(full_path).map_err(Into::into)
125    }
126
127    async fn read_file_bytes<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Vec<u8>> {
128        let full_path = self.path.join(relative_path);
129        fs::read(full_path).map_err(Into::into)
130    }
131
132    async fn exists(&self, filename: &str) -> Result<bool> {
133        Ok(self.path.join(filename).exists())
134    }
135
136    async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>> {
137        let full_path = self.path.join(filename);
138        if !full_path.exists() {
139            return Ok(None);
140        }
141        let metadata = fs::metadata(full_path)?;
142        if metadata.is_file() {
143            Ok(Some(DirentKind::File))
144        } else if metadata.is_dir() {
145            Ok(Some(DirentKind::Directory))
146        } else {
147            Err(anyhow!("Unsupported entry type: {:?}", metadata.file_type()))
148        }
149    }
150
151    async fn remove(&self, relative_path: &str) -> Result<()> {
152        let full_path = self.path.join(relative_path);
153        fs::remove_file(full_path).map_err(Into::into)
154    }
155
156    async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()> {
157        let full_path = self.path.join(relative_path);
158        fs::write(full_path, data).map_err(Into::into)
159    }
160
161    async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64> {
162        let full_path = self.path.join(relative_path);
163        let metadata = fs::metadata(full_path)?;
164        if metadata.is_file() {
165            Ok(metadata.len())
166        } else {
167            Err(anyhow!("Cannot get size of non-file"))
168        }
169    }
170
171    async fn entry_names(&self) -> Result<Vec<String>> {
172        let paths = fs::read_dir(self.path.clone())?;
173        Ok(paths.into_iter().map(|p| p.unwrap().file_name().into_string().unwrap()).collect())
174    }
175}
176
177/// A convenience wrapper over a FIDL DirectoryProxy.
178#[derive(Debug)]
179pub struct RemoteDirectory {
180    path: PathBuf,
181    proxy: fio::DirectoryProxy,
182    // The `fuchsia.io.RemoteDirectory` protocol is stateful in readdir, and the associated `fuchsia_fs::directory`
183    // library used for enumerating the directory has no mechanism for synchronization of readdir
184    // operations, as such this mutex must be held throughout directory enumeration in order to
185    // avoid race conditions from concurrent rewinds and reads.
186    readdir_mutex: Mutex<()>,
187}
188
189impl RemoteDirectory {
190    #[cfg(target_os = "fuchsia")]
191    pub fn from_namespace<P: AsRef<Path> + Send>(path: P) -> Result<Self> {
192        let path_str = path
193            .as_ref()
194            .as_os_str()
195            .to_str()
196            .ok_or_else(|| format_err!("could not convert path to string"))?;
197        let proxy = fuchsia_fs::directory::open_in_namespace(path_str, fio::PERM_READABLE)?;
198        let path = path.as_ref().to_path_buf();
199        Ok(Self { path, proxy, readdir_mutex: Mutex::new(()) })
200    }
201
202    pub fn from_proxy(proxy: fio::DirectoryProxy) -> Self {
203        let path = PathBuf::from(".");
204        Self { path, proxy, readdir_mutex: Mutex::new(()) }
205    }
206
207    pub fn clone_proxy(&self) -> Result<fio::DirectoryProxy> {
208        let (cloned_proxy, clone_server) =
209            self.proxy.domain().create_proxy::<fio::DirectoryMarker>();
210        self.proxy.clone(clone_server.into_channel().into())?;
211        Ok(cloned_proxy)
212    }
213
214    async fn entries(&self) -> Result<Vec<DirEntry>, Error> {
215        let _lock = self.readdir_mutex.lock().await;
216        match readdir(&self.proxy).await {
217            Ok(entries) => Ok(entries),
218            Err(e) => Err(format_err!(
219                "could not get entries of `{}`: {}",
220                self.path.as_path().display(),
221                e
222            )),
223        }
224    }
225
226    fn open_dir<P: AsRef<Path> + Send>(&self, relative_path: P, flags: fio::Flags) -> Result<Self> {
227        let path = self.path.join(relative_path.as_ref());
228        let relative_path = match relative_path.as_ref().to_str() {
229            Some(relative_path) => relative_path,
230            None => return Err(format_err!("could not convert relative path to &str")),
231        };
232        match open_directory_async(&self.proxy, relative_path, flags) {
233            Ok(proxy) => Ok(Self { path, proxy, readdir_mutex: Mutex::new(()) }),
234            Err(e) => Err(format_err!("could not open dir `{}`: {}", path.as_path().display(), e)),
235        }
236    }
237}
238
239#[async_trait]
240impl Directory for RemoteDirectory {
241    fn open_dir_readonly<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self> {
242        self.open_dir(relative_path, fio::PERM_READABLE)
243    }
244
245    fn open_dir_readwrite<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self> {
246        self.open_dir(relative_path, fio::PERM_READABLE | fio::PERM_WRITABLE)
247    }
248
249    fn create_dir<P: AsRef<Path> + Send>(&self, relative_path: P, readwrite: bool) -> Result<Self> {
250        let mut flags = fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE;
251        if readwrite {
252            flags = flags | fio::PERM_WRITABLE;
253        }
254        self.open_dir(relative_path, flags)
255    }
256
257    async fn read_file<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<String> {
258        let path = self.path.join(relative_path.as_ref());
259        let relative_path = match relative_path.as_ref().to_str() {
260            Some(relative_path) => relative_path,
261            None => return Err(format_err!("relative path is not valid unicode")),
262        };
263
264        let proxy = match open_file_async(&self.proxy, relative_path, fio::PERM_READABLE) {
265            Ok(proxy) => proxy,
266            Err(e) => {
267                return Err(format_err!(
268                    "could not open file `{}`: {}",
269                    path.as_path().display(),
270                    e
271                ))
272            }
273        };
274
275        match read_to_string(&proxy).await {
276            Ok(data) => Ok(data),
277            Err(e) => Err(format_err!(
278                "could not read file `{}` as string: {}",
279                path.as_path().display(),
280                e
281            )),
282        }
283    }
284
285    async fn read_file_bytes<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Vec<u8>> {
286        let path = self.path.join(relative_path.as_ref());
287        let relative_path = match relative_path.as_ref().to_str() {
288            Some(relative_path) => relative_path,
289            None => return Err(format_err!("relative path is not valid unicode")),
290        };
291
292        let proxy = match open_file_async(&self.proxy, relative_path, fio::PERM_READABLE) {
293            Ok(proxy) => proxy,
294            Err(e) => {
295                return Err(format_err!(
296                    "could not open file `{}`: {}",
297                    path.as_path().display(),
298                    e
299                ))
300            }
301        };
302
303        match read(&proxy).await {
304            Ok(data) => Ok(data),
305            Err(e) => Err(format_err!("could not read file `{}`: {}", path.as_path().display(), e)),
306        }
307    }
308
309    async fn exists(&self, filename: &str) -> Result<bool> {
310        match self.entry_names().await {
311            Ok(entries) => Ok(entries.iter().any(|s| s == filename)),
312            Err(e) => Err(format_err!(
313                "could not check if `{}` exists in `{}`: {}",
314                filename,
315                self.path.as_path().display(),
316                e
317            )),
318        }
319    }
320
321    async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>> {
322        let entries = self.entries().await?;
323
324        entries
325            .into_iter()
326            .find(|e| e.name == filename)
327            .map(|e| {
328                match e.kind {
329                    // TODO(https://fxbug.dev/42077929): Update component_manager vfs to assign proper DirentType when installing the directory tree.
330                    fio::DirentType::Directory | fio::DirentType::Unknown => {
331                        Ok(Some(DirentKind::Directory))
332                    }
333                    fio::DirentType::File => Ok(Some(DirentKind::File)),
334                    _ => {
335                        return Err(anyhow!(
336                            "Unsupported entry type for file {}: {:?}",
337                            &filename,
338                            e.kind,
339                        ));
340                    }
341                }
342            })
343            .unwrap_or(Ok(None))
344    }
345
346    async fn remove(&self, filename: &str) -> Result<()> {
347        let options = fio::UnlinkOptions::default();
348        match self.proxy.unlink(filename, &options).await {
349            Ok(r) => match r {
350                Ok(()) => Ok(()),
351                Err(e) => Err(format_err!(
352                    "could not delete `{}` from `{}`: {}",
353                    filename,
354                    self.path.as_path().display(),
355                    e
356                )),
357            },
358            Err(e) => Err(format_err!(
359                "proxy error while deleting `{}` from `{}`: {}",
360                filename,
361                self.path.as_path().display(),
362                e
363            )),
364        }
365    }
366
367    async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()> {
368        let path = self.path.join(relative_path.as_ref());
369        let relative_path = match relative_path.as_ref().to_str() {
370            Some(relative_path) => relative_path,
371            None => return Err(format_err!("relative path is not valid unicode")),
372        };
373
374        let file = match open_file_async(
375            &self.proxy,
376            relative_path,
377            fio::PERM_WRITABLE | fio::Flags::FLAG_MAYBE_CREATE,
378        ) {
379            Ok(proxy) => proxy,
380            Err(e) => {
381                return Err(format_err!(
382                    "could not open file `{}`: {}",
383                    path.as_path().display(),
384                    e
385                ))
386            }
387        };
388
389        let () = file
390            .resize(0)
391            .await
392            .map_err(|e| {
393                format_err!("could not truncate file `{}`: {}", path.as_path().display(), e)
394            })?
395            .map_err(Status::from_raw)
396            .map_err(|status| {
397                format_err!("could not truncate file `{}`: {}", path.as_path().display(), status)
398            })?;
399
400        match write(&file, data).await {
401            Ok(()) => {}
402            Err(e) => {
403                return Err(format_err!(
404                    "could not write to file `{}`: {}",
405                    path.as_path().display(),
406                    e
407                ))
408            }
409        }
410
411        match close(file).await {
412            Ok(()) => Ok(()),
413            Err(e) => {
414                Err(format_err!("could not close file `{}`: {}", path.as_path().display(), e))
415            }
416        }
417    }
418
419    async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64> {
420        let path = self.path.join(relative_path.as_ref());
421        let relative_path = match relative_path.as_ref().to_str() {
422            Some(relative_path) => relative_path,
423            None => return Err(format_err!("relative path is not valid unicode")),
424        };
425
426        let file = match open_file_async(&self.proxy, relative_path, fio::PERM_READABLE) {
427            Ok(proxy) => proxy,
428            Err(e) => {
429                return Err(format_err!(
430                    "could not open file `{}`: {}",
431                    path.as_path().display(),
432                    e
433                ))
434            }
435        };
436
437        match file.get_attr().await {
438            Ok((raw_status_code, attr)) => {
439                Status::ok(raw_status_code)?;
440                Ok(attr.storage_size)
441            }
442            Err(e) => {
443                Err(format_err!("Unexpected FIDL error during file attribute retrieval: {}", e))
444            }
445        }
446    }
447
448    async fn entry_names(&self) -> Result<Vec<String>> {
449        match self.entries().await {
450            Ok(entries) => Ok(entries.into_iter().map(|e| e.name).collect()),
451            Err(e) => Err(format_err!(
452                "could not get entry names of `{}`: {}",
453                self.path.as_path().display(),
454                e
455            )),
456        }
457    }
458
459    fn clone(&self) -> Result<Self> {
460        let proxy = open_directory_async(&self.proxy, ".", fio::PERM_READABLE)?;
461        Ok(Self { path: self.path.clone(), proxy, readdir_mutex: Mutex::new(()) })
462    }
463}