1use 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 fn open_dir_readonly<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
32
33 fn open_dir_readwrite<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Self>;
35
36 fn create_dir<P: AsRef<Path> + Send>(&self, relative_path: P, readwrite: bool) -> Result<Self>;
38
39 fn clone(&self) -> Result<Self>;
41
42 async fn read_file<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<String>;
44
45 async fn read_file_bytes<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<Vec<u8>>;
47
48 async fn exists(&self, filename: &str) -> Result<bool>;
51
52 async fn entry_type(&self, filename: &str) -> Result<Option<DirentKind>>;
55
56 async fn remove(&self, relative_path: &str) -> Result<()>;
58
59 async fn write_file<P: AsRef<Path> + Send>(&self, relative_path: P, data: &[u8]) -> Result<()>;
62
63 async fn get_file_size<P: AsRef<Path> + Send>(&self, relative_path: P) -> Result<u64>;
65
66 async fn entry_names(&self) -> Result<Vec<String>>;
68}
69
70#[derive(Debug)]
72pub struct LocalDirectory {
73 path: PathBuf,
74}
75
76impl LocalDirectory {
77 pub fn new() -> Self {
80 LocalDirectory { path: PathBuf::new() }
81 }
82
83 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#[derive(Debug)]
179pub struct RemoteDirectory {
180 path: PathBuf,
181 proxy: fio::DirectoryProxy,
182 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 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}