Skip to main content

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::{Error, Result, anyhow, format_err};
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::{DirEntry, open_directory_async, open_file_async, readdir};
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        let filename = PathBuf::from(filename);
311        let mut path_iter = filename.iter().peekable();
312        let path_element = path_iter.next().ok_or_else(|| format_err!("path is empty"))?;
313        match path_iter.peek() {
314            Some(_) => {
315                let sub_directory = self.open_dir_readonly(path_element)?;
316                let sub_path = path_iter.collect::<PathBuf>();
317                let sub_path_str =
318                    sub_path.to_str().ok_or_else(|| format_err!("path is not valid utf8"))?;
319                sub_directory.exists(sub_path_str).await
320            }
321            None => match self.entry_names().await {
322                Ok(entries) => Ok(entries.iter().any(|s| s.as_str() == path_element)),
323                Err(e) => Err(format_err!(
324                    "could not check if `{}` exists in `{}`: {}",
325                    filename.display(),
326                    self.path.as_path().display(),
327                    e
328                )),
329            },
330        }
331    }
332
333    async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>> {
334        let entries = self.entries().await?;
335
336        entries
337            .into_iter()
338            .find(|e| e.name == filename)
339            .map(|e| {
340                match e.kind {
341                    // TODO(https://fxbug.dev/42077929): Update component_manager vfs to assign proper DirentType when installing the directory tree.
342                    fio::DirentType::Directory | fio::DirentType::Unknown => {
343                        Ok(Some(DirentKind::Directory))
344                    }
345                    fio::DirentType::File => Ok(Some(DirentKind::File)),
346                    _ => {
347                        return Err(anyhow!(
348                            "Unsupported entry type for file {}: {:?}",
349                            &filename,
350                            e.kind,
351                        ));
352                    }
353                }
354            })
355            .unwrap_or(Ok(None))
356    }
357
358    async fn remove(&self, filename: &str) -> Result<()> {
359        let filename = PathBuf::from(filename);
360        let mut path_iter = filename.iter().peekable();
361        let path_element = path_iter.next().ok_or_else(|| {
362            format_err!(
363                "could not delete `{}` from `{}: path is empty",
364                filename.display(),
365                self.path.as_path().display(),
366            )
367        })?;
368        match path_iter.peek() {
369            Some(_) => {
370                let sub_directory = self.open_dir_readwrite(path_element)?;
371                let sub_path = path_iter.collect::<PathBuf>();
372                let sub_path_str = sub_path.to_str().ok_or_else(|| {
373                    format_err!(
374                        "could not delete `{}` from `{}: path is not valid utf8",
375                        filename.display(),
376                        self.path.as_path().display(),
377                    )
378                })?;
379                sub_directory.remove(sub_path_str).await
380            }
381            None => {
382                let options = fio::UnlinkOptions::default();
383                let path_str =
384                    path_element.to_str().ok_or_else(|| format_err!("path is not valid utf8"))?;
385                match self.proxy.unlink(path_str, &options).await {
386                    Ok(r) => match r {
387                        Ok(()) => Ok(()),
388                        Err(e) => Err(format_err!(
389                            "could not delete `{}` from `{}`: {}",
390                            path_element.display(),
391                            self.path.as_path().display(),
392                            e
393                        )),
394                    },
395                    Err(e) => Err(format_err!(
396                        "proxy error while deleting `{}` from `{}`: {}",
397                        path_element.display(),
398                        self.path.as_path().display(),
399                        e
400                    )),
401                }
402            }
403        }
404    }
405
406    async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()> {
407        let path = self.path.join(relative_path.as_ref());
408        let relative_path = match relative_path.as_ref().to_str() {
409            Some(relative_path) => relative_path,
410            None => return Err(format_err!("relative path is not valid unicode")),
411        };
412
413        let file = match open_file_async(
414            &self.proxy,
415            relative_path,
416            fio::PERM_WRITABLE | fio::Flags::FLAG_MAYBE_CREATE,
417        ) {
418            Ok(proxy) => proxy,
419            Err(e) => {
420                return Err(format_err!(
421                    "could not open file `{}`: {}",
422                    path.as_path().display(),
423                    e
424                ));
425            }
426        };
427
428        let () = file
429            .resize(0)
430            .await
431            .map_err(|e| {
432                format_err!("could not truncate file `{}`: {}", path.as_path().display(), e)
433            })?
434            .map_err(Status::from_raw)
435            .map_err(|status| {
436                format_err!("could not truncate file `{}`: {}", path.as_path().display(), status)
437            })?;
438
439        match write(&file, data).await {
440            Ok(()) => {}
441            Err(e) => {
442                return Err(format_err!(
443                    "could not write to file `{}`: {}",
444                    path.as_path().display(),
445                    e
446                ));
447            }
448        }
449
450        match close(file).await {
451            Ok(()) => Ok(()),
452            Err(e) => {
453                Err(format_err!("could not close file `{}`: {}", path.as_path().display(), e))
454            }
455        }
456    }
457
458    async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64> {
459        let path = self.path.join(relative_path.as_ref());
460        let relative_path = match relative_path.as_ref().to_str() {
461            Some(relative_path) => relative_path,
462            None => return Err(format_err!("relative path is not valid unicode")),
463        };
464
465        let file = match open_file_async(&self.proxy, relative_path, fio::PERM_READABLE) {
466            Ok(proxy) => proxy,
467            Err(e) => {
468                return Err(format_err!(
469                    "could not open file `{}`: {}",
470                    path.as_path().display(),
471                    e
472                ));
473            }
474        };
475        let (_, immutable_attributes) = file
476            .get_attributes(fio::NodeAttributesQuery::STORAGE_SIZE)
477            .await
478            .map_err(|e| {
479                format_err!("Unexpected FIDL error during file attribute retrieval: {}", e)
480            })?
481            .unwrap();
482        Ok(immutable_attributes.storage_size.unwrap_or_default())
483    }
484
485    async fn entry_names(&self) -> Result<Vec<String>> {
486        match self.entries().await {
487            Ok(entries) => Ok(entries.into_iter().map(|e| e.name).collect()),
488            Err(e) => Err(format_err!(
489                "could not get entry names of `{}`: {}",
490                self.path.as_path().display(),
491                e
492            )),
493        }
494    }
495
496    fn clone(&self) -> Result<Self> {
497        let proxy = open_directory_async(&self.proxy, ".", fio::PERM_READABLE)?;
498        Ok(Self { path: self.path.clone(), proxy, readdir_mutex: Mutex::new(()) })
499    }
500}