blob: 3a922c9f6d5f977bc00924f1a342ec15f108fab8 [file] [log] [blame]
// Copyright 2021 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.
use {
crate::root_dir::RootDir,
anyhow::anyhow,
async_trait::async_trait,
fidl::endpoints::ServerEnd,
fidl_fuchsia_io as fio,
fuchsia_syslog::fx_log_err,
fuchsia_zircon as zx,
std::sync::Arc,
vfs::{
common::send_on_open_with_error,
directory::{
connection::io1::DerivedConnection, entry::EntryInfo,
immutable::connection::io1::ImmutableConnection, traversal_position::TraversalPosition,
},
execution_scope::ExecutionScope,
path::Path as VfsPath,
},
};
pub(crate) struct NonMetaSubdir {
root_dir: Arc<RootDir>,
// The object relative path expression of the subdir relative to the package root with a
// trailing slash appended.
path: String,
}
impl NonMetaSubdir {
pub(crate) fn new(root_dir: Arc<RootDir>, path: String) -> Self {
NonMetaSubdir { root_dir, path }
}
}
impl vfs::directory::entry::DirectoryEntry for NonMetaSubdir {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: fio::OpenFlags,
mode: u32,
path: VfsPath,
server_end: ServerEnd<fio::NodeMarker>,
) {
let flags = flags & !fio::OpenFlags::POSIX_WRITABLE;
let flags = if flags.intersects(fio::OpenFlags::POSIX_DEPRECATED) {
(flags & !fio::OpenFlags::POSIX_DEPRECATED) | fio::OpenFlags::POSIX_EXECUTABLE
} else {
flags
};
if path.is_empty() {
if flags.intersects(
fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::CREATE
| fio::OpenFlags::CREATE_IF_ABSENT
| fio::OpenFlags::TRUNCATE
| fio::OpenFlags::APPEND,
) {
let () = send_on_open_with_error(flags, server_end, zx::Status::NOT_SUPPORTED);
return;
}
let () = ImmutableConnection::create_connection(scope, self, flags, server_end);
return;
}
// vfs::path::Path::as_str() is an object relative path expression [1], except that it may:
// 1. have a trailing "/"
// 2. be exactly "."
// 3. be longer than 4,095 bytes
// The .is_empty() check above rules out "." and the following line removes the possible
// trailing "/".
// [1] https://fuchsia.dev/fuchsia-src/concepts/process/namespaces?hl=en#object_relative_path_expressions
let file_path = format!(
"{}{}",
self.path,
path.as_ref().strip_suffix("/").unwrap_or_else(|| path.as_ref())
);
if let Some(blob) = self.root_dir.non_meta_files.get(&file_path) {
let () = self
.root_dir
.blobfs
.forward_open(blob, flags, mode, server_end)
.unwrap_or_else(|e| {
fx_log_err!("Error forwarding content blob open to blobfs: {:#}", anyhow!(e))
});
return;
}
let directory_path = file_path + "/";
for k in self.root_dir.non_meta_files.keys() {
if k.starts_with(&directory_path) {
let () = Arc::new(NonMetaSubdir::new(Arc::clone(&self.root_dir), directory_path))
.open(scope, flags, mode, VfsPath::dot(), server_end);
return;
}
}
let () = send_on_open_with_error(flags, server_end, zx::Status::NOT_FOUND);
}
fn entry_info(&self) -> vfs::directory::entry::EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
}
#[async_trait]
impl vfs::directory::entry_container::Directory for NonMetaSubdir {
async fn read_dirents<'a>(
&'a self,
pos: &'a TraversalPosition,
sink: Box<(dyn vfs::directory::dirents_sink::Sink + 'static)>,
) -> Result<
(TraversalPosition, Box<(dyn vfs::directory::dirents_sink::Sealed + 'static)>),
zx::Status,
> {
crate::read_dirents(
&crate::get_dir_children(
self.root_dir.non_meta_files.keys().map(|s| s.as_str()),
&self.path,
),
pos,
sink,
)
.await
}
fn register_watcher(
self: Arc<Self>,
_: ExecutionScope,
_: fio::WatchMask,
_: vfs::directory::entry_container::DirectoryWatcher,
) -> Result<(), zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
// `register_watcher` is unsupported so no need to do anything here.
fn unregister_watcher(self: Arc<Self>, _: usize) {}
async fn get_attrs(&self) -> Result<fio::NodeAttributes, zx::Status> {
Ok(fio::NodeAttributes {
mode: fio::MODE_TYPE_DIRECTORY
| vfs::common::rights_to_posix_mode_bits(
true, // read
false, // write
true, // execute
),
id: 1,
content_size: 0,
storage_size: 0,
link_count: 1,
creation_time: 0,
modification_time: 0,
})
}
fn close(&self) -> Result<(), zx::Status> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
fuchsia_pkg_testing::{blobfs::Fake as FakeBlobfs, PackageBuilder},
futures::stream::StreamExt as _,
std::convert::TryInto as _,
vfs::directory::{entry::DirectoryEntry, entry_container::Directory},
};
struct TestEnv {
_blobfs_fake: FakeBlobfs,
}
impl TestEnv {
async fn new() -> (Self, NonMetaSubdir) {
let pkg = PackageBuilder::new("pkg")
.add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
.build()
.await
.unwrap();
let (metafar_blob, content_blobs) = pkg.contents();
let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
for content in content_blobs {
blobfs_fake.add_blob(content.merkle, content.contents);
}
let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
let sub_dir = NonMetaSubdir::new(Arc::new(root_dir), "dir0/".to_string());
(Self { _blobfs_fake: blobfs_fake }, sub_dir)
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_get_attrs() {
let (_env, sub_dir) = TestEnv::new().await;
assert_eq!(
sub_dir.get_attrs().await.unwrap(),
fio::NodeAttributes {
mode: fio::MODE_TYPE_DIRECTORY | 0o500,
id: 1,
content_size: 0,
storage_size: 0,
link_count: 1,
creation_time: 0,
modification_time: 0,
}
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_entry_info() {
let (_env, sub_dir) = TestEnv::new().await;
assert_eq!(
DirectoryEntry::entry_info(&sub_dir),
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_register_watcher_not_supported() {
let (_env, sub_dir) = TestEnv::new().await;
let (_client, server) = fidl::endpoints::create_endpoints().unwrap();
assert_eq!(
Directory::register_watcher(
Arc::new(sub_dir),
ExecutionScope::new(),
fio::WatchMask::empty(),
server.try_into().unwrap(),
),
Err(zx::Status::NOT_SUPPORTED)
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_close() {
let (_env, sub_dir) = TestEnv::new().await;
assert_eq!(Directory::close(&sub_dir), Ok(()));
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_read_dirents() {
let (_env, sub_dir) = TestEnv::new().await;
let (pos, sealed) = sub_dir
.read_dirents(&TraversalPosition::Start, Box::new(crate::tests::FakeSink::new(3)))
.await
.expect("read_dirents failed");
assert_eq!(
crate::tests::FakeSink::from_sealed(sealed).entries,
vec![
(".".to_string(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
("dir1".to_string(), EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)),
]
);
assert_eq!(pos, TraversalPosition::End);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_directory() {
let (_env, sub_dir) = TestEnv::new().await;
let sub_dir = Arc::new(sub_dir);
for path in ["dir1", "dir1/"] {
let (proxy, server_end) =
fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
Arc::clone(&sub_dir).open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE,
0,
VfsPath::validate_and_split(path).unwrap(),
server_end.into_channel().into(),
);
assert_eq!(
files_async::readdir(&proxy).await.unwrap(),
vec![files_async::DirEntry {
name: "file".to_string(),
kind: files_async::DirentKind::File
}]
);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_file() {
let (_env, sub_dir) = TestEnv::new().await;
let sub_dir = Arc::new(sub_dir);
for path in ["dir1/file", "dir1/file/"] {
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>().unwrap();
Arc::clone(&sub_dir).open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE,
0,
VfsPath::validate_and_split(path).unwrap(),
server_end.into_channel().into(),
);
assert_eq!(io_util::file::read(&proxy).await.unwrap(), b"bloblob".to_vec());
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_unsets_posix_writable() {
let (_env, sub_dir) = TestEnv::new().await;
let sub_dir = Arc::new(sub_dir);
let () = crate::verify_open_adjusts_flags(
&(sub_dir as Arc<dyn DirectoryEntry>),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::POSIX_WRITABLE,
fio::OpenFlags::RIGHT_READABLE,
)
.await;
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_converts_posix_deprecated_to_posix_executable() {
let (_env, sub_dir) = TestEnv::new().await;
let sub_dir = Arc::new(sub_dir);
let () = crate::verify_open_adjusts_flags(
&(sub_dir as Arc<dyn DirectoryEntry>),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::POSIX_DEPRECATED,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
)
.await;
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_rejects_disallowed_flags() {
let (_env, sub_dir) = TestEnv::new().await;
let sub_dir = Arc::new(sub_dir);
for forbidden_flag in [
fio::OpenFlags::RIGHT_WRITABLE,
fio::OpenFlags::CREATE,
fio::OpenFlags::CREATE_IF_ABSENT,
fio::OpenFlags::TRUNCATE,
fio::OpenFlags::APPEND,
] {
let (proxy, server_end) =
fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
DirectoryEntry::open(
Arc::clone(&sub_dir),
ExecutionScope::new(),
fio::OpenFlags::DESCRIBE | forbidden_flag,
0,
VfsPath::dot(),
server_end.into_channel().into(),
);
assert_matches!(
proxy.take_event_stream().next().await,
Some(Ok(fio::DirectoryEvent::OnOpen_{ s, info: None}))
if s == zx::Status::NOT_SUPPORTED.into_raw()
);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn directory_entry_open_self() {
let (_env, sub_dir) = TestEnv::new().await;
let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
Arc::new(sub_dir).open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE,
0,
VfsPath::dot(),
server_end.into_channel().into(),
);
assert_eq!(
files_async::readdir(&proxy).await.unwrap(),
vec![files_async::DirEntry {
name: "dir1".to_string(),
kind: files_async::DirentKind::Directory
}]
);
}
}