blob: 3be2133b400a147efbe2e459dbbbb5d5a106fd99 [file] [log] [blame]
// Copyright 2023 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 {
anyhow::{Error, Result},
blobfs_ramdisk::BlobfsRamdisk,
fidl::endpoints,
fidl_fuchsia_component_sandbox as fsandbox, fidl_fuchsia_io as fio,
fidl_fuchsia_pkg_test::*,
fidl_fuchsia_testing_harness::{OperationError, RealmProxy_RequestStream},
fuchsia_component::{client::connect_to_protocol, server::ServiceFs},
fuchsia_pkg_testing::{Package, PackageBuilder, SystemImageBuilder},
fuchsia_zircon as zx,
futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt},
realm_proxy::service::serve_with_proxy,
tracing::{error, info},
};
// When this feature is enabled, the pkgdir tests will start Fxblob.
#[cfg(feature = "use_fxblob")]
static BLOB_IMPLEMENTATION: blobfs_ramdisk::Implementation = blobfs_ramdisk::Implementation::Fxblob;
// When this feature is not enabled, the pkgdir tests will start cpp Blobfs.
#[cfg(not(feature = "use_fxblob"))]
static BLOB_IMPLEMENTATION: blobfs_ramdisk::Implementation =
blobfs_ramdisk::Implementation::CppBlobfs;
enum IncomingService {
RealmProxy(RealmProxy_RequestStream),
RealmFactory(RealmFactoryRequestStream),
}
#[fuchsia::main(logging = true)]
async fn main() -> Result<(), Error> {
info!("starting");
// Spin up a blobfs and install the test package.
let test_package = make_test_package().await;
let system_image_package =
SystemImageBuilder::new().static_packages(&[&test_package]).build().await;
let blobfs = BlobfsRamdisk::builder()
.implementation(BLOB_IMPLEMENTATION)
.start()
.await
.expect("started blobfs");
test_package.write_to_blobfs(&blobfs).await;
system_image_package.write_to_blobfs(&blobfs).await;
let blobfs_client = blobfs.client();
let (client, server) = endpoints::create_proxy()?;
package_directory::serve(
vfs::execution_scope::ExecutionScope::new(),
blobfs_client,
*test_package.hash(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
server,
)
.await?;
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingService::RealmProxy);
fs.dir("svc").add_fidl_service(IncomingService::RealmFactory);
fs.take_and_serve_directory_handle()?;
fs.for_each_concurrent(None, move |req| match req {
IncomingService::RealmProxy(stream) => {
let realm_proxy = PkgdirTestRealmProxy::new(Clone::clone(&client));
serve_with_proxy(realm_proxy, stream)
.unwrap_or_else(|e| error!("handling realm_proxy request stream{:?}", e))
.boxed()
}
IncomingService::RealmFactory(stream) => serve_factory(Clone::clone(&client), stream)
.unwrap_or_else(|e: crate::Error| error!("handling realm_proxy request stream{:?}", e))
.boxed(),
})
.await;
Ok(())
}
async fn serve_factory(
directory: fio::DirectoryProxy,
mut stream: RealmFactoryRequestStream,
) -> Result<(), crate::Error> {
let factory = connect_to_protocol::<fsandbox::FactoryMarker>().unwrap();
while let Ok(Some(request)) = stream.try_next().await {
match request {
RealmFactoryRequest::CreateRealm { options: _, dictionary, responder } => {
let (client_end, server_end) = endpoints::create_endpoints();
if let Err(e) = directory.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server_end) {
error!("{:?}", e);
let _ = responder.send(Err(OperationError::Failed));
continue;
}
// TODO(https://fxbug.dev/329496030): We have to use this factory method instead
// of creating a Capability::Directory directly - see the bug for details.
let (my_dictionary_proxy, server) = endpoints::create_proxy().unwrap();
let () = factory.create_dictionary(server).await?;
let value =
factory.create_directory(client_end.into_channel().into()).await.unwrap();
my_dictionary_proxy.insert("pkg", value).await?.unwrap();
my_dictionary_proxy.clone2(dictionary.into_channel().into())?;
responder.send(Ok(()))?;
}
RealmFactoryRequest::_UnknownMethod { .. } => unreachable!(),
}
}
Ok(())
}
/// Constructs a test package to be used in the integration tests.
async fn make_test_package() -> Package {
let exceeds_max_buf_contents = repeat_by_n('a', (fio::MAX_BUF + 1).try_into().unwrap());
// Each file's contents is the file's path as bytes for testing simplicity.
let mut builder = PackageBuilder::new("test-package")
.add_resource_at("file", "file".as_bytes())
.add_resource_at("meta/file", "meta/file".as_bytes())
// For use in testing Directory.Open calls with segmented paths.
.add_resource_at("dir/file", "dir/file".as_bytes())
.add_resource_at("dir/dir/file", "dir/dir/file".as_bytes())
.add_resource_at("dir/dir/dir/file", "dir/dir/dir/file".as_bytes())
.add_resource_at("meta/dir/file", "meta/dir/file".as_bytes())
.add_resource_at("meta/dir/dir/file", "meta/dir/dir/file".as_bytes())
.add_resource_at("meta/dir/dir/dir/file", "meta/dir/dir/dir/file".as_bytes())
// For use in testing File.Read calls where the file contents exceeds MAX_BUF.
.add_resource_at("exceeds_max_buf", exceeds_max_buf_contents.as_bytes())
.add_resource_at("meta/exceeds_max_buf", exceeds_max_buf_contents.as_bytes());
// For use in testing File.GetBackingMemory on different file sizes.
let file_sizes = [0, 1, 4095, 4096, 4097];
for size in &file_sizes {
builder = builder
.add_resource_at(
format!("file_{}", size),
make_file_contents(*size).collect::<Vec<u8>>().as_slice(),
)
.add_resource_at(
format!("meta/file_{}", size),
make_file_contents(*size).collect::<Vec<u8>>().as_slice(),
);
}
// Make directory nodes of each kind (root dir, non-meta subdir, meta dir, meta subdir)
// that overflow the fuchsia.io/Directory.ReadDirents buffer.
for base in ["", "dir_overflow_readdirents/", "meta/", "meta/dir_overflow_readdirents/"] {
// In the integration tests, we'll want to be able to test calling ReadDirents on a
// directory. Since ReadDirents returns `MAX_BUF` bytes worth of directory entries, we need
// to have test coverage for the "overflow" case where the directory has more than
// `MAX_BUF` bytes worth of directory entries.
//
// Through math, we determine that we can achieve this overflow with 31 files whose names
// are length `MAX_FILENAME`. Here is this math:
/*
ReadDirents -> vector<uint8>:MAX_BUF
MAX_BUF = 8192
struct dirent {
// Describes the inode of the entry.
uint64 ino;
// Describes the length of the dirent name in bytes.
uint8 size;
// Describes the type of the entry. Aligned with the
// POSIX d_type values. Use `DIRENT_TYPE_*` constants.
uint8 type;
// Unterminated name of entry.
char name[0];
}
sizeof(dirent) if name is MAX_FILENAME = 255 bytes long = 8 + 1 + 1 + 255 = 265 bytes
8192 / 265 ~= 30.9
=> 31 directory entries of maximum size will trigger overflow
*/
for seed in ('a'..='z').chain('A'..='E') {
builder = builder.add_resource_at(
format!("{}{}", base, repeat_by_n(seed, fio::MAX_FILENAME.try_into().unwrap())),
&b""[..],
)
}
}
builder.build().await.expect("build package")
}
fn repeat_by_n(seed: char, n: usize) -> String {
std::iter::repeat(seed).take(n).collect()
}
fn make_file_contents(size: usize) -> impl Iterator<Item = u8> {
b"ABCD".iter().copied().cycle().take(size)
}
// A [RealmProxy] implementation that returns clones of the package directory
// for testing. It only responds to requests to connect to fuchsia.io.Directory.
// Any other protocol connection is rejected with [OperationError::Unsupported].
pub struct PkgdirTestRealmProxy {
directory: fio::DirectoryProxy,
}
impl PkgdirTestRealmProxy {
pub fn new(directory: fio::DirectoryProxy) -> Self {
Self { directory }
}
}
impl realm_proxy::service::RealmProxy for PkgdirTestRealmProxy {
fn connect_to_named_protocol(
&mut self,
protocol: &str,
server_end: zx::Channel,
) -> Result<(), OperationError> {
if protocol != "fuchsia.io.Directory" {
error!("this test realm proxy only serves the fuchsia.io.Directory protocol");
return Err(OperationError::Unsupported);
}
self.directory.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server_end.into()).map_err(|e| {
error!("{:?}", e);
OperationError::Failed
})
}
fn open_service(&self, _service: &str, _server_end: zx::Channel) -> Result<(), OperationError> {
Err(OperationError::Unsupported)
}
fn connect_to_service_instance(
&self,
_service: &str,
_instance: &str,
_server_end: zx::Channel,
) -> Result<(), OperationError> {
Err(OperationError::Unsupported)
}
}