1use 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 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 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 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}