1use 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 fn open_dir_readonly<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
28
29 fn open_dir_readwrite<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
31
32 fn create_dir<P: AsRef<Path> + Send>(&self, relative_path: P, readwrite: bool) -> Result<Self>;
34
35 fn clone(&self) -> Result<Self>;
37
38 async fn read_file<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<String>;
40
41 async fn read_file_bytes<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Vec<u8>>;
43
44 async fn exists(&self, filename: &str) -> Result<bool>;
47
48 async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>>;
51
52 async fn remove(&self, relative_path: &str) -> Result<()>;
54
55 async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()>;
58
59 async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64>;
61
62 async fn entry_names(&self) -> Result<Vec<String>>;
64}
65
66#[derive(Debug)]
68pub struct LocalDirectory {
69 path: PathBuf,
70}
71
72impl LocalDirectory {
73 pub fn new() -> Self {
76 LocalDirectory { path: PathBuf::new() }
77 }
78
79 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#[derive(Debug)]
175pub struct RemoteDirectory {
176 path: PathBuf,
177 proxy: fio::DirectoryProxy,
178 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 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}