| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| |
| use failure::Error; |
| use fidl_fuchsia_io::{DirectoryProxy, MAX_BUF}; |
| use fuchsia_zircon as zx; |
| use std::collections::VecDeque; |
| use std::fmt; |
| use std::mem; |
| |
| #[derive(Eq, Ord, PartialOrd, PartialEq, Clone, Copy)] |
| pub enum DirentType { |
| Unknown, |
| Directory, |
| BlockDevice, |
| File, |
| Socket, |
| Service, |
| } |
| |
| impl From<u8> for DirentType { |
| fn from(dir_type: u8) -> Self { |
| match dir_type { |
| fidl_fuchsia_io::DIRENT_TYPE_DIRECTORY => DirentType::Directory, |
| fidl_fuchsia_io::DIRENT_TYPE_BLOCK_DEVICE => DirentType::BlockDevice, |
| fidl_fuchsia_io::DIRENT_TYPE_FILE => DirentType::File, |
| fidl_fuchsia_io::DIRENT_TYPE_SOCKET => DirentType::Socket, |
| fidl_fuchsia_io::DIRENT_TYPE_SERVICE => DirentType::Service, |
| _ => DirentType::Unknown, |
| } |
| } |
| } |
| |
| #[derive(Eq, Ord, PartialOrd, PartialEq)] |
| pub struct DirEntry { |
| pub name: String, |
| pub dir_type: DirentType, |
| } |
| |
| impl DirEntry { |
| fn is_dir(&self) -> bool { |
| self.dir_type == DirentType::Directory |
| } |
| |
| fn chain(&self, subentry: &DirEntry) -> DirEntry { |
| DirEntry { |
| name: format!("{}/{}", self.name, subentry.name), |
| dir_type: subentry.dir_type, |
| } |
| } |
| } |
| |
| impl fmt::Debug for DirEntry { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.write_str(&self.name) |
| } |
| } |
| |
| pub async fn readdir_recursive(dir: DirectoryProxy) -> Result<Vec<DirEntry>, Error> { |
| let mut directories: VecDeque<DirEntry> = VecDeque::new(); |
| let mut entries: Vec<DirEntry> = Vec::new(); |
| |
| // Prime directory queue with immediate descendants. |
| { |
| for entry in await!(readdir(&dir))?.into_iter() { |
| if entry.is_dir() { |
| directories.push_back(entry) |
| } else { |
| entries.push(entry) |
| } |
| } |
| } |
| |
| // Handle a single directory at a time, emitting leaf nodes and queueing up subdirectories for |
| // later iterations. |
| while let Some(entry) = directories.pop_front() { |
| let (subdir, subdir_server_end) = fidl::endpoints::create_proxy()?; |
| let flags = fidl_fuchsia_io::OPEN_FLAG_DIRECTORY | fidl_fuchsia_io::OPEN_RIGHT_READABLE; |
| dir.open(flags, 0, &entry.name, subdir_server_end)?; |
| let subdir = DirectoryProxy::new(subdir.into_channel().unwrap()); |
| |
| let subentries = await!(readdir(&subdir))?; |
| |
| // Emit empty directories as a single entry. |
| if subentries.is_empty() { |
| entries.push(entry); |
| continue; |
| } |
| |
| for subentry in subentries.into_iter() { |
| let subentry = entry.chain(&subentry); |
| if subentry.is_dir() { |
| directories.push_back(subentry) |
| } else { |
| entries.push(subentry) |
| } |
| } |
| } |
| |
| Ok(entries) |
| } |
| |
| pub async fn readdir(dir: &DirectoryProxy) -> Result<Vec<DirEntry>, Error> { |
| #[repr(packed)] |
| struct Dirent { |
| _ino: u64, |
| size: u8, |
| _type: u8, |
| } |
| |
| let mut entries = vec![]; |
| loop { |
| let (status, buf) = await!(dir.read_dirents(MAX_BUF))?; |
| zx::Status::ok(status)?; |
| |
| if buf.is_empty() { |
| break; |
| } |
| |
| // The buffer contains an arbitrary number of dirents. |
| let mut slice = buf.as_slice(); |
| while !slice.is_empty() { |
| // Read the dirent, and figure out how long the name is. |
| let (head, rest) = slice.split_at(mem::size_of::<Dirent>()); |
| |
| let entry = { |
| // Cast the dirent bytes into a `Dirent`, and extract out the size of the name and |
| // the entry type. |
| let (size, _type) = unsafe { |
| let dirent: &Dirent = mem::transmute(head.as_ptr()); |
| (dirent.size as usize, dirent._type) |
| }; |
| |
| // Advance to the next entry. |
| slice = &rest[size..]; |
| |
| DirEntry { |
| // Package resolver paths are always utf8. |
| name: String::from_utf8(rest[..size].to_vec())?, |
| dir_type: _type.into(), |
| } |
| }; |
| |
| if entry.name != "." { |
| entries.push(entry); |
| } |
| } |
| } |
| |
| entries.sort_unstable(); |
| |
| Ok(entries) |
| } |
| |
| // TODO: Add tests |
| |