blob: 684ad8918b11bbce0d0cd109236bf9c7c1b90f74 [file] [log] [blame]
// Copyright 2020 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 {
fidl::endpoints::{create_endpoints, create_proxy, Proxy, ServiceMarker},
fidl_fuchsia_io as io, fidl_fuchsia_io_test as io_test, fidl_fuchsia_sys2 as fsys,
fuchsia_async as fasync, fuchsia_zircon as zx,
fuchsia_zircon::Status,
futures::StreamExt,
io_conformance::io1_request_logger_factory::Io1RequestLoggerFactory,
};
const TEST_FILE: &str = "testing.txt";
pub async fn connect_to_harness() -> io_test::Io1HarnessProxy {
// Connect to the realm to get acccess to the outgoing directory for the harness.
let (client, server) = zx::Channel::create().expect("Cannot create channel");
fuchsia_component::client::connect_channel_to_service::<fsys::RealmMarker>(server)
.expect("Cannot connect to Realm service");
let mut realm = fsys::RealmSynchronousProxy::new(client);
// fs_test is the name of the child component defined in the manifest.
let mut child_ref = fsys::ChildRef { name: "fs_test".to_string(), collection: None };
let (client, server) = zx::Channel::create().expect("Cannot create channel");
realm
.bind_child(
&mut child_ref,
fidl::endpoints::ServerEnd::<io::DirectoryMarker>::new(server),
zx::Time::INFINITE,
)
.expect("FIDL error when binding to child in Realm")
.expect("Cannot bind to test harness child in Realm");
let exposed_dir = io::DirectoryProxy::new(
fidl::AsyncChannel::from_channel(client).expect("Cannot create async channel"),
);
fuchsia_component::client::connect_to_protocol_at_dir_root::<io_test::Io1HarnessMarker>(
&exposed_dir,
)
.expect("Cannot connect to test harness protocol")
}
/// Listens for the `OnOpen` event and returns its [Status].
async fn get_open_status(node_proxy: &io::NodeProxy) -> Status {
let mut events = node_proxy.take_event_stream();
let io::NodeEvent::OnOpen_ { s, info: _ } =
events.next().await.expect("OnOpen event not received").expect("FIDL error");
Status::from_raw(s)
}
/// Helper function to open the desired node in the root folder. Only use this
/// if testing something other than the open call directly.
async fn open_node<T: ServiceMarker>(
dir: &io::DirectoryProxy,
flags: u32,
mode: u32,
path: &str,
) -> T::Proxy {
let flags = flags | io::OPEN_FLAG_DESCRIBE;
let (node_proxy, node_server) = create_proxy::<io::NodeMarker>().expect("Cannot create proxy");
dir.open(flags, mode, path, node_server).expect("Cannot open node");
assert_eq!(get_open_status(&node_proxy).await, Status::OK);
T::Proxy::from_channel(node_proxy.into_channel().expect("Cannot convert node proxy to channel"))
}
/// Creates and returns a directory with the given structure from the test harness.
fn get_directory_from_harness(
harness: &io_test::Io1HarnessProxy,
root: io_test::Directory,
) -> io::DirectoryProxy {
let (client, server) = create_proxy::<io::DirectoryMarker>().expect("Cannot create proxy");
harness.get_directory(root, server).expect("Cannot get directory from test harness");
return client;
}
/// Returns a constant representing the aggregate of all io.fidl rights that are supported by the
/// test harness.
async fn all_rights_for_harness(harness: &io_test::Io1HarnessProxy) -> u32 {
let config = harness.get_config().await.expect("Cannot get config from harness");
let mut all_rights = io::OPEN_RIGHT_READABLE | io::OPEN_RIGHT_WRITABLE;
if !config.no_exec.unwrap_or_default() {
all_rights |= io::OPEN_RIGHT_EXECUTABLE;
}
if !config.no_admin.unwrap_or_default() {
all_rights |= io::OPEN_RIGHT_ADMIN;
}
all_rights
}
/// Returns a list of flag combinations to test. Returns a vector of the aggregate of
/// every constant flag and every combination of variable flags.
/// Ex. build_flag_combinations(100, 011) would return [100, 110, 101, 111]
/// for flags expressed as binary. We exclude the no rights case as that is an
/// invalid argument in most cases. Ex. build_flag_combinations(0, 011)
/// would return [010, 001, 011] without the 000 case.
fn build_flag_combinations(constant_flags: u32, variable_flags: u32) -> Vec<u32> {
let mut vec = vec![constant_flags];
for flag in split_flags(variable_flags) {
let length = vec.len();
for i in 0..length {
vec.push(vec[i] | flag);
}
}
vec.retain(|element| *element != 0);
vec
}
/// Splits a bitset into a vector of its component bits. e.g. 1011 becomes [0001, 0010, 1000].
fn split_flags(flags: u32) -> Vec<u32> {
let mut flags = flags;
let mut bits = vec![];
while flags != 0 {
// x & -x returns the lowest bit set in x. Add it to the vec and then unset that bit.
let lowest_bit = flags & flags.wrapping_neg();
bits.push(lowest_bit);
flags ^= lowest_bit;
}
bits
}
fn root_directory(flags: u32, entries: Vec<io_test::DirectoryEntry>) -> io_test::Directory {
// Convert the simple vector of entries into the convoluted FIDL field type.
let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
entries.into_iter().map(|e| Some(Box::new(e))).collect();
io_test::Directory {
name: None,
flags: Some(flags),
entries: Some(entries),
..io_test::Directory::EMPTY
}
}
fn directory(
name: &str,
flags: u32,
entries: Vec<io_test::DirectoryEntry>,
) -> io_test::DirectoryEntry {
let mut dir = root_directory(flags, entries);
dir.name = Some(name.to_string());
io_test::DirectoryEntry::Directory(dir)
}
fn file(name: &str, flags: u32, contents: Vec<u8>) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::File(io_test::File {
name: Some(name.to_string()),
flags: Some(flags),
contents: Some(contents),
..io_test::File::EMPTY
})
}
// Example test to start up a v2 component harness to test when opening a path that goes through a
// remote mount point, the server forwards the request to the remote correctly.
#[fasync::run_singlethreaded(test)]
async fn open_remote_directory_test() {
let harness = connect_to_harness().await;
let config = harness.get_config().await.expect("Could not get config from harness");
if config.no_remote_dir.unwrap_or_default() {
return;
}
let (remote_dir_client, remote_dir_server) =
create_endpoints::<io::DirectoryMarker>().expect("Cannot create endpoints");
let remote_name = "remote_directory";
// Request an extra directory connection from the harness to use as the remote,
// and interpose the requests from the server under test to this remote.
let (logger, mut rx) = Io1RequestLoggerFactory::new();
let remote_dir_server =
logger.get_logged_directory(remote_name.to_string(), remote_dir_server).await;
let root = root_directory(io::OPEN_RIGHT_READABLE | io::OPEN_RIGHT_WRITABLE, vec![]);
harness.get_directory(root, remote_dir_server).expect("Cannot get empty remote directory");
let (test_dir_proxy, test_dir_server) =
create_proxy::<io::DirectoryMarker>().expect("Cannot create proxy");
harness
.get_directory_with_remote_directory(
remote_dir_client,
remote_name,
io::OPEN_RIGHT_READABLE | io::OPEN_RIGHT_WRITABLE,
test_dir_server,
)
.expect("Cannot get test harness directory");
let (_remote_dir_proxy, remote_dir_server) =
create_proxy::<io::NodeMarker>().expect("Cannot create proxy");
test_dir_proxy
.open(io::OPEN_RIGHT_READABLE, io::MODE_TYPE_DIRECTORY, remote_name, remote_dir_server)
.expect("Cannot open remote directory");
// Wait on an open call to the interposed remote directory.
let open_request_string = rx.next().await.expect("Local tx/rx channel was closed");
// TODO(fxbug.dev/45613):: Bare-metal testing against returned request string. We need
// to find a more ergonomic return format.
assert_eq!(open_request_string, "remote_directory flags:1, mode:16384, path:.");
}
/// Creates a directory with all rights, and checks it can be opened for all subsets of rights.
#[fasync::run_singlethreaded(test)]
async fn open_dir_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let root = root_directory(all_rights, vec![]);
let root_dir = get_directory_from_harness(&harness, root);
for dir_flags in build_flag_combinations(0, all_rights) {
let (client, server) = create_proxy::<io::NodeMarker>().expect("Cannot create proxy.");
root_dir
.open(dir_flags | io::OPEN_FLAG_DESCRIBE, io::MODE_TYPE_DIRECTORY, ".", server)
.expect("Cannot open directory");
assert_eq!(get_open_status(&client).await, Status::OK);
}
}
/// Creates a directory with no rights, and checks opening it with any rights fails.
#[fasync::run_singlethreaded(test)]
async fn open_dir_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let root = root_directory(0, vec![]);
let root_dir = get_directory_from_harness(&harness, root);
for dir_flags in build_flag_combinations(0, all_rights) {
let (client, server) = create_proxy::<io::NodeMarker>().expect("Cannot create proxy.");
root_dir
.open(dir_flags | io::OPEN_FLAG_DESCRIBE, io::MODE_TYPE_DIRECTORY, ".", server)
.expect("Cannot open directory");
assert_eq!(get_open_status(&client).await, Status::ACCESS_DENIED);
}
}
/// Opens a directory, and checks that a child directory can be opened using the same rights.
#[fasync::run_singlethreaded(test)]
async fn open_child_dir_with_same_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for dir_flags in build_flag_combinations(0, all_rights) {
let root = root_directory(all_rights, vec![directory("child", dir_flags, vec![])]);
let root_dir = get_directory_from_harness(&harness, root);
let parent_dir =
open_node::<io::DirectoryMarker>(&root_dir, dir_flags, io::MODE_TYPE_DIRECTORY, ".")
.await;
// Open child directory with same flags as parent.
let (child_dir_client, child_dir_server) =
create_proxy::<io::NodeMarker>().expect("Cannot create proxy.");
parent_dir
.open(
dir_flags | io::OPEN_FLAG_DESCRIBE,
io::MODE_TYPE_DIRECTORY,
"child",
child_dir_server,
)
.expect("Cannot open directory");
assert_eq!(get_open_status(&child_dir_client).await, Status::OK);
}
}
/// Opens a directory as readable, and checks that a child directory cannot be opened as writable.
#[fasync::run_singlethreaded(test)]
async fn open_child_dir_with_extra_rights() {
let harness = connect_to_harness().await;
let root = root_directory(
io::OPEN_RIGHT_READABLE,
vec![directory("child", io::OPEN_RIGHT_READABLE, vec![])],
);
let root_dir = get_directory_from_harness(&harness, root);
// Open parent as readable.
let parent_dir = open_node::<io::DirectoryMarker>(
&root_dir,
io::OPEN_RIGHT_READABLE,
io::MODE_TYPE_DIRECTORY,
".",
)
.await;
// Opening child as writable should fail.
let (child_dir_client, child_dir_server) =
create_proxy::<io::NodeMarker>().expect("Cannot create proxy.");
parent_dir
.open(
io::OPEN_RIGHT_WRITABLE | io::OPEN_FLAG_DESCRIBE,
io::MODE_TYPE_DIRECTORY,
"child",
child_dir_server,
)
.expect("Cannot open directory");
assert_eq!(get_open_status(&child_dir_client).await, Status::ACCESS_DENIED);
}
#[fasync::run_singlethreaded(test)]
async fn file_read_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_READABLE, all_rights) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _data) = file.read(0).await.expect("read failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let not_readable_flags = all_rights & !io::OPEN_RIGHT_READABLE;
for file_flags in build_flag_combinations(0, not_readable_flags) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _data) = file.read(0).await.expect("read failed");
assert_eq!(Status::from_raw(status), Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_at_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_READABLE, all_rights) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _data) = file.read_at(0, 0).await.expect("read_at failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_at_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let not_readable_flags = all_rights & !io::OPEN_RIGHT_READABLE;
for file_flags in build_flag_combinations(0, not_readable_flags) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _data) = file.read_at(0, 0).await.expect("read_at failed");
assert_eq!(Status::from_raw(status), Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_WRITABLE, all_rights) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _actual) = file.write("".as_bytes()).await.expect("write failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let non_writable_flags = all_rights & !io::OPEN_RIGHT_WRITABLE;
for file_flags in build_flag_combinations(0, non_writable_flags) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _actual) = file.write("".as_bytes()).await.expect("write failed");
assert_eq!(Status::from_raw(status), Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_at_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_WRITABLE, all_rights) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _actual) = file.write_at("".as_bytes(), 0).await.expect("write_at failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_at_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let non_writable_flags = all_rights & !io::OPEN_RIGHT_WRITABLE;
for file_flags in build_flag_combinations(0, non_writable_flags) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let (status, _actual) = file.write_at("".as_bytes(), 0).await.expect("write_at failed");
assert_eq!(Status::from_raw(status), Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_truncate_with_sufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_WRITABLE, all_rights) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let status = file.truncate(0).await.expect("truncate failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_truncate_with_insufficient_rights() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
let non_writable_flags = all_rights & !io::OPEN_RIGHT_WRITABLE;
for file_flags in build_flag_combinations(0, non_writable_flags) {
let root = root_directory(all_rights, vec![file(TEST_FILE, file_flags, vec![])]);
let test_dir = get_directory_from_harness(&harness, root);
let file =
open_node::<io::FileMarker>(&test_dir, file_flags, io::MODE_TYPE_FILE, TEST_FILE).await;
let status = file.truncate(0).await.expect("truncate failed");
assert_eq!(Status::from_raw(status), Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_in_subdirectory() {
let harness = connect_to_harness().await;
let all_rights = all_rights_for_harness(&harness).await;
for file_flags in build_flag_combinations(io::OPEN_RIGHT_READABLE, all_rights) {
let root = root_directory(
all_rights,
vec![directory("subdir", all_rights, vec![file("testing.txt", file_flags, vec![])])],
);
let test_dir = get_directory_from_harness(&harness, root);
let file = open_node::<io::FileMarker>(
&test_dir,
file_flags,
io::MODE_TYPE_FILE,
"subdir/testing.txt",
)
.await;
let (status, _data) = file.read(0).await.expect("Read failed");
assert_eq!(Status::from_raw(status), Status::OK);
}
}
#[cfg(test)]
mod tests {
use super::{build_flag_combinations, split_flags};
#[test]
fn test_build_flag_combinations() {
let constant_flags = 0b100;
let variable_flags = 0b011;
let generated_combinations = build_flag_combinations(constant_flags, variable_flags);
let expected_result = [0b100, 0b101, 0b110, 0b111];
assert_eq!(generated_combinations, expected_result);
}
#[test]
fn test_build_flag_combinations_without_empty_rights() {
let constant_flags = 0;
let variable_flags = 0b011;
let generated_combinations = build_flag_combinations(constant_flags, variable_flags);
let expected_result = [0b001, 0b010, 0b011];
assert_eq!(generated_combinations, expected_result);
}
#[test]
fn test_split_flags() {
assert_eq!(split_flags(0), vec![]);
assert_eq!(split_flags(0b001), vec![0b001]);
assert_eq!(split_flags(0b101), vec![0b001, 0b100]);
assert_eq!(split_flags(0b111), vec![0b001, 0b010, 0b100]);
}
}