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