blob: 7e484c131db716a0aa55d3a1a14c9aa3ba54df7d [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_proxy, ClientEnd, ProtocolMarker, Proxy},
AsHandleRef,
},
fidl_fuchsia_io as fio, fidl_fuchsia_io_test as io_test, fidl_fuchsia_mem,
fuchsia_async::{self as fasync, DurationExt, TimeoutExt},
fuchsia_zircon as zx,
futures::StreamExt,
io_conformance_util::{flags::build_flag_combinations, test_harness::TestHarness},
libc,
std::convert::TryInto,
};
const TEST_FILE: &str = "testing.txt";
const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes {
mode: 0,
id: 0,
content_size: 0,
storage_size: 0,
link_count: 0,
creation_time: 0,
modification_time: 0,
};
/// Listens for the `OnOpen` event and returns its [Status].
async fn get_open_status(node_proxy: &fio::NodeProxy) -> zx::Status {
let mut events = node_proxy.take_event_stream();
if let Some(result) = events.next().await {
match result.expect("FIDL error") {
fio::NodeEvent::OnOpen_ { s, info: _ } => zx::Status::from_raw(s),
fio::NodeEvent::OnConnectionInfo { .. } => zx::Status::OK,
}
} else {
zx::Status::PEER_CLOSED
}
}
async fn assert_on_open_not_received(node_proxy: &fio::NodeProxy) {
let mut events = node_proxy.take_event_stream();
// Wait at most 200ms for an OnOpen event to appear.
let event =
events.next().on_timeout(zx::Duration::from_millis(200).after_now(), || Option::None).await;
assert!(event.is_none(), "Unexpected OnOpen event received");
}
/// Converts a generic `NodeProxy` to either a file or directory proxy.
fn convert_node_proxy<T: ProtocolMarker>(proxy: fio::NodeProxy) -> T::Proxy {
T::Proxy::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel"))
}
/// Helper function to open the desired node in the root folder.
/// Asserts that open_node_status succeeds.
async fn open_node<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
mode: u32,
path: &str,
) -> T::Proxy {
open_node_status::<T>(dir, flags, mode, path)
.await
.expect(&format!("open_node_status failed for {}", path))
}
/// Helper function to open the desired node in the root folder.
async fn open_node_status<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
mode: u32,
path: &str,
) -> Result<T::Proxy, zx::Status> {
let flags = flags | fio::OpenFlags::DESCRIBE;
let (node_proxy, node_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open(flags, mode, path, node_server).expect("Cannot open node");
let status = get_open_status(&node_proxy).await;
if status != zx::Status::OK {
Err(status)
} else {
Ok(convert_node_proxy::<T>(node_proxy))
}
}
/// Returns the specified node flags from the given NodeProxy.
async fn get_node_flags(node_proxy: &fio::NodeProxy) -> fio::OpenFlags {
let (_, node_flags) = node_proxy.get_flags().await.expect("Failed to get node flags!");
return node_flags;
}
/// Helper function to open a file with the given flags. Only use this if testing something other
/// than the open call directly.
async fn open_file_with_flags(
parent_dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> fio::FileProxy {
open_node::<fio::FileMarker>(&parent_dir, flags, fio::MODE_TYPE_FILE, path).await
}
/// Helper function to open a sub-directory with the given flags. Only use this if testing
/// something other than the open call directly.
async fn open_dir_with_flags(
parent_dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> fio::DirectoryProxy {
open_node::<fio::DirectoryMarker>(&parent_dir, flags, fio::MODE_TYPE_DIRECTORY, path).await
}
/// Helper function to open a sub-directory as readable and writable. Only use this if testing
/// something other than the open call directly.
async fn open_rw_dir(parent_dir: &fio::DirectoryProxy, path: &str) -> fio::DirectoryProxy {
open_dir_with_flags(
parent_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
path,
)
.await
}
/// Helper function to call `get_token` on a directory. Only use this if testing something
/// other than the `get_token` call directly.
async fn get_token(dir: &fio::DirectoryProxy) -> fidl::Handle {
let (status, token) = dir.get_token().await.expect("get_token failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
token.expect("handle missing")
}
/// Helper function to read a file and return its contents. Only use this if testing something other
/// than the read call directly.
async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> {
let file = open_node::<fio::FileMarker>(
dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
path,
)
.await;
file.read(100).await.expect("read failed").map_err(zx::Status::from_raw).expect("read error")
}
/// Attempts to open the given file, and checks the status is `NOT_FOUND`.
async fn assert_file_not_found(dir: &fio::DirectoryProxy, path: &str) {
let (file_proxy, file_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open(
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_FILE,
path,
file_server,
)
.expect("Cannot open file");
assert_eq!(get_open_status(&file_proxy).await, zx::Status::NOT_FOUND);
}
/// Returns the .name field from a given DirectoryEntry, otherwise panics.
fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String {
use io_test::DirectoryEntry;
match dir_entry {
DirectoryEntry::Directory(entry) => entry.name.as_ref(),
DirectoryEntry::RemoteDirectory(entry) => entry.name.as_ref(),
DirectoryEntry::File(entry) => entry.name.as_ref(),
DirectoryEntry::VmoFile(entry) => entry.name.as_ref(),
DirectoryEntry::ExecutableFile(entry) => entry.name.as_ref(),
}
.expect("DirectoryEntry name is None!")
.clone()
}
/// Asserts that the given `vmo_rights` align with the `vmo_flags` passed to a get_buffer call.
/// We check that the returned rights align with and do not exceed those in the given flags, that
/// we have at least basic VMO rights, and that the flags align with the expected sharing mode.
fn validate_vmo_rights(buffer: &fidl_fuchsia_mem::Buffer, vmo_flags: fio::VmoFlags) {
let vmo_rights: zx::Rights = buffer.vmo.basic_info().expect("failed to get VMO info").rights;
// Ensure that we have at least some basic rights.
assert!(vmo_rights.contains(zx::Rights::BASIC));
assert!(vmo_rights.contains(zx::Rights::MAP));
assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY));
// Ensure the returned rights match and do not exceed those we requested in `vmo_flags`.
assert!(vmo_rights.contains(zx::Rights::READ) == vmo_flags.contains(fio::VmoFlags::READ));
assert!(vmo_rights.contains(zx::Rights::WRITE) == vmo_flags.contains(fio::VmoFlags::WRITE));
assert!(vmo_rights.contains(zx::Rights::EXECUTE) == vmo_flags.contains(fio::VmoFlags::EXECUTE));
// Make sure we get SET_PROPERTY if we specified a private copy.
if vmo_flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
}
}
/// Creates a directory with the given DirectoryEntry, opening the file with the given
/// file flags, and returning a Buffer object initialized with the given vmo_flags.
async fn create_file_and_get_buffer(
dir_entry: io_test::DirectoryEntry,
test_harness: &TestHarness,
file_flags: fio::OpenFlags,
vmo_flags: fio::VmoFlags,
) -> Result<(fidl_fuchsia_mem::Buffer, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> {
let file_path = get_directory_entry_name(&dir_entry);
let root = root_directory(vec![dir_entry]);
let dir_proxy = test_harness.get_directory(root, file_flags);
let file_proxy = open_node_status::<fio::FileMarker>(
&dir_proxy,
file_flags,
fio::MODE_TYPE_FILE,
&file_path,
)
.await?;
let vmo = file_proxy
.get_backing_memory(vmo_flags)
.await
.expect("Get buffer failed")
.map_err(zx::Status::from_raw)?;
let size = vmo.get_content_size()?;
Ok((fidl_fuchsia_mem::Buffer { vmo, size }, (dir_proxy, file_proxy)))
}
fn root_directory(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, entries: Some(entries), ..io_test::Directory::EMPTY }
}
fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
let mut dir = root_directory(entries);
dir.name = Some(name.to_string());
io_test::DirectoryEntry::Directory(dir)
}
fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
remote_dir.into_channel().unwrap().into_zx_channel(),
);
io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
name: Some(name.to_string()),
remote_client: Some(remote_client),
..io_test::RemoteDirectory::EMPTY
})
}
fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::File(io_test::File {
name: Some(name.to_string()),
contents: Some(contents),
..io_test::File::EMPTY
})
}
fn vmo_file(name: &str, contents: &[u8]) -> io_test::DirectoryEntry {
let size = contents.len() as u64;
let vmo = zx::Vmo::create(size).expect("Cannot create VMO");
vmo.write(contents, 0).expect("Cannot write to VMO");
let range = fidl_fuchsia_mem::Range { vmo, offset: 0, size };
io_test::DirectoryEntry::VmoFile(io_test::VmoFile {
name: Some(name.to_string()),
buffer: Some(range),
..io_test::VmoFile::EMPTY
})
}
fn executable_file(name: &str) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile {
name: Some(name.to_string()),
..io_test::ExecutableFile::EMPTY
})
}
// Validate allowed rights for Directory objects.
#[fasync::run_singlethreaded(test)]
async fn validate_directory_rights() {
let harness = TestHarness::new().await;
// Create a test directory and ensure we can open it with all supported rights.
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let _root_dir = harness.get_directory(
root,
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE,
);
}
// Validate allowed rights for File objects (ensures writable files cannot be opened as executable).
#[fasync::run_singlethreaded(test)]
async fn validate_file_rights() {
let harness = TestHarness::new().await;
// Create a test directory with a single File object, and ensure the directory has all rights.
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
// Opening as READABLE must succeed.
open_node::<fio::NodeMarker>(&root_dir, fio::OpenFlags::RIGHT_READABLE, 0, TEST_FILE).await;
if harness.config.mutable_file.unwrap_or_default() {
// Opening as WRITABLE must succeed.
open_node::<fio::NodeMarker>(&root_dir, fio::OpenFlags::RIGHT_WRITABLE, 0, TEST_FILE).await;
// Opening as EXECUTABLE must fail (W^X).
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_EXECUTABLE,
0,
TEST_FILE,
)
.await
.expect_err("open succeeded");
} else {
// If files are immutable, check that opening as WRITABLE results in access denied.
// All other combinations are valid in this case.
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_WRITABLE,
0,
TEST_FILE
)
.await
.expect_err("open succeeded"),
zx::Status::ACCESS_DENIED
);
}
}
// Validate allowed rights for VmoFile objects (ensures cannot be opened as executable).
#[fasync::run_singlethreaded(test)]
async fn validate_vmo_file_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_vmo_file.unwrap_or_default() {
return;
}
// Create a test directory with a VmoFile object, and ensure the directory has all rights.
let root = root_directory(vec![vmo_file(TEST_FILE, TEST_FILE_CONTENTS)]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
// Opening with READ/WRITE should succeed.
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
0,
TEST_FILE,
)
.await;
// Opening with EXECUTE must fail to ensure W^X enforcement.
assert!(matches!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_EXECUTABLE,
0,
TEST_FILE
)
.await
.expect_err("open succeeded"),
zx::Status::ACCESS_DENIED | zx::Status::NOT_SUPPORTED
));
}
// Validate allowed rights for ExecutableFile objects (ensures cannot be opened as writable).
#[fasync::run_singlethreaded(test)]
async fn validate_executable_file_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_executable_file.unwrap_or_default() {
return;
}
// Create a test directory with an ExecutableFile object, and ensure the directory has all rights.
let root = root_directory(vec![executable_file(TEST_FILE)]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
// Opening with READABLE/EXECUTABLE should succeed.
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
0,
TEST_FILE,
)
.await;
// Opening with WRITABLE must fail to ensure W^X enforcement.
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_WRITABLE,
0,
TEST_FILE
)
.await
.expect_err("open succeeded"),
zx::Status::ACCESS_DENIED
);
}
/// Creates a directory with a remote mount inside of it, and checks that the remote can be opened.
#[fasync::run_singlethreaded(test)]
async fn open_remote_directory_test() {
let harness = TestHarness::new().await;
if !harness.config.supports_remote_dir.unwrap_or_default() {
return;
}
let remote_name = "remote_directory";
let remote_mount = root_directory(vec![]);
let remote_client = harness.get_directory(
remote_mount,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
);
// Create a directory with the remote directory inside of it.
let root = root_directory(vec![remote_directory(remote_name, remote_client)]);
let root_dir = harness
.get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
open_node::<fio::DirectoryMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
fio::MODE_TYPE_DIRECTORY,
remote_name,
)
.await;
}
/// Creates a directory with a remote mount containing a file inside of it, and checks that the
/// file can be opened through the remote.
#[fasync::run_singlethreaded(test)]
async fn open_remote_file_test() {
let harness = TestHarness::new().await;
if !harness.config.supports_remote_dir.unwrap_or_default() {
return;
}
let remote_name = "remote_directory";
let remote_dir = root_directory(vec![file(TEST_FILE, vec![])]);
let remote_client = harness.get_directory(remote_dir, fio::OpenFlags::RIGHT_READABLE);
// Create a directory with the remote directory inside of it.
let root = root_directory(vec![remote_directory(remote_name, remote_client)]);
let root_dir = harness
.get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
// Test opening file by opening the remote directory first and then opening the file.
let remote_dir_proxy = open_node::<fio::DirectoryMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
remote_name,
)
.await;
open_node::<fio::NodeMarker>(&remote_dir_proxy, fio::OpenFlags::RIGHT_READABLE, 0, TEST_FILE)
.await;
// Test opening file directly though local directory by crossing remote automatically.
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
[remote_name, "/", TEST_FILE].join("").as_str(),
)
.await;
}
/// Ensure specifying OPEN_FLAG_POSIX_DEPRECATED cannot cause rights escalation (fxbug.dev/40862).
/// The test sets up the following hierarchy of nodes:
///
/// ------------------ RW --------------------------
/// | root_dir_proxy | ---> | root_dir_server |
/// ------------------ (a) | - /mount_point | RWX ---------------------
/// | (remote_proxy) | ---> | remote_dir |
/// -------------------------- (b) ---------------------
///
/// To validate the right escalation issue has been resolved, we call Open() on the test_dir_proxy
/// passing in OPEN_FLAG_POSIX_DEPRECATED, which if handled correctly, should result in opening
/// remote_dir as RW (and NOT RWX, which can occur if the deprecated POSIX flag is passed directly
/// to the remote instead of only OPEN_FLAG_POSIX_WRITABLE/EXECUTABLE).
/// TODO(fxbug.dev/81185): Replace OPEN_FLAG_POSIX_DEPRECATED with new set of equivalent flags.
#[fasync::run_singlethreaded(test)]
async fn open_remote_directory_right_escalation_test() {
let harness = TestHarness::new().await;
if !harness.config.supports_remote_dir.unwrap_or_default() {
return;
}
let mount_point = "mount_point";
// Use the test harness to serve a directory with RWX permissions.
let remote_dir = root_directory(vec![]);
let remote_proxy = harness.get_directory(
remote_dir,
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE,
);
// Mount the remote directory through root, and ensure that the connection only has RW
// RW permissions (which is thus a sub-set of the permissions the remote_proxy has).
let root = root_directory(vec![remote_directory(mount_point, remote_proxy)]);
let root_proxy = harness
.get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
// Create a new proxy/server for opening the remote node through test_dir_proxy.
// Here we pass the POSIX flag, which should only expand to the maximum set of
// rights available along the open chain.
let (node_proxy, node_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
root_proxy
.open(
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::POSIX_DEPRECATED,
fio::MODE_TYPE_DIRECTORY,
mount_point,
node_server,
)
.expect("Cannot open remote directory");
// Since the root node only has RW permissions, and even though the remote has RWX,
// we should only get RW permissions back.
let (_, node_flags) = node_proxy.get_flags().await.unwrap();
assert_eq!(node_flags, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
}
/// 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 = TestHarness::new().await;
let root = root_directory(vec![]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
for dir_flags in harness.dir_rights.valid_combos() {
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
root_dir
.open(dir_flags | fio::OpenFlags::DESCRIBE, fio::MODE_TYPE_DIRECTORY, ".", server)
.expect("Cannot open directory");
assert_eq!(get_open_status(&client).await, zx::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 = TestHarness::new().await;
let root = root_directory(vec![]);
let root_dir = harness.get_directory(root, fio::OpenFlags::empty());
for dir_flags in harness.dir_rights.valid_combos() {
if dir_flags.is_empty() {
continue;
}
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
root_dir
.open(dir_flags | fio::OpenFlags::DESCRIBE, fio::MODE_TYPE_DIRECTORY, ".", server)
.expect("Cannot open directory");
assert_eq!(get_open_status(&client).await, zx::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 = TestHarness::new().await;
for dir_flags in harness.dir_rights.valid_combos() {
let root = root_directory(vec![directory("child", vec![])]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
let parent_dir =
open_node::<fio::DirectoryMarker>(&root_dir, dir_flags, fio::MODE_TYPE_DIRECTORY, ".")
.await;
// Open child directory with same flags as parent.
let (child_dir_client, child_dir_server) =
create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
parent_dir
.open(
dir_flags | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_DIRECTORY,
"child",
child_dir_server,
)
.expect("Cannot open directory");
assert_eq!(get_open_status(&child_dir_client).await, zx::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 = TestHarness::new().await;
let root = root_directory(vec![directory("child", vec![])]);
let root_dir = harness.get_directory(root, fio::OpenFlags::RIGHT_READABLE);
// Open parent as readable.
let parent_dir = open_node::<fio::DirectoryMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
".",
)
.await;
// Opening child as writable should fail.
let (child_dir_client, child_dir_server) =
create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
parent_dir
.open(
fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_DIRECTORY,
"child",
child_dir_server,
)
.expect("Cannot open directory");
assert_eq!(get_open_status(&child_dir_client).await, zx::Status::ACCESS_DENIED);
}
/// Creates a child directory and opens it with OPEN_FLAG_POSIX_WRITABLE/EXECUTABLE, ensuring that
/// the requested rights are expanded to only those which the parent directory connection has.
#[fasync::run_singlethreaded(test)]
async fn open_child_dir_with_posix_flags() {
let harness = TestHarness::new().await;
for dir_flags in harness.dir_rights.valid_combos() {
let root = root_directory(vec![directory("child", vec![])]);
let root_dir = harness.get_directory(root, dir_flags);
let readable = dir_flags & fio::OpenFlags::RIGHT_READABLE;
let parent_dir =
open_node::<fio::DirectoryMarker>(&root_dir, dir_flags, fio::MODE_TYPE_DIRECTORY, ".")
.await;
let (child_dir_client, child_dir_server) =
create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
parent_dir
.open(
readable
| fio::OpenFlags::POSIX_WRITABLE
| fio::OpenFlags::POSIX_EXECUTABLE
| fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_DIRECTORY,
"child",
child_dir_server,
)
.expect("Cannot open directory");
assert_eq!(
get_open_status(&child_dir_client).await,
zx::Status::OK,
"Failed to open directory, flags = {:?}",
dir_flags
);
// Ensure expanded rights do not exceed those of the parent directory connection.
assert_eq!(get_node_flags(&child_dir_client).await & dir_flags, dir_flags);
}
}
#[fasync::run_singlethreaded(test)]
async fn open_dir_without_describe_flag() {
let harness = TestHarness::new().await;
let root = root_directory(vec![]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
for dir_flags in harness.dir_rights.valid_combos() {
assert_eq!(dir_flags & fio::OpenFlags::DESCRIBE, fio::OpenFlags::empty());
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
root_dir
.open(dir_flags, fio::MODE_TYPE_DIRECTORY, ".", server)
.expect("Cannot open directory");
assert_on_open_not_received(&client).await;
}
}
#[fasync::run_singlethreaded(test)]
async fn open_file_without_describe_flag() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos() {
assert_eq!(file_flags & fio::OpenFlags::DESCRIBE, fio::OpenFlags::empty());
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
test_dir
.open(file_flags, fio::MODE_TYPE_FILE, TEST_FILE, server)
.expect("Cannot open file");
assert_on_open_not_received(&client).await;
}
}
/// Ensures that opening a file with more rights than the directory connection fails
/// with Status::ACCESS_DENIED.
#[fasync::run_singlethreaded(test)]
async fn open_file_with_extra_rights() {
let harness = TestHarness::new().await;
// Combinations to test of the form (directory flags, [file flag combinations]).
// All file flags should have more rights than those of the directory flags.
let test_right_combinations = [
(fio::OpenFlags::empty(), harness.file_rights.valid_combos()),
(
fio::OpenFlags::RIGHT_READABLE,
harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE),
),
(
fio::OpenFlags::RIGHT_WRITABLE,
harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_READABLE),
),
];
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
for (dir_flags, file_flag_combos) in test_right_combinations.iter() {
let dir_proxy =
open_node::<fio::DirectoryMarker>(&root_dir, *dir_flags, fio::MODE_TYPE_DIRECTORY, ".")
.await;
for file_flags in file_flag_combos {
if file_flags.is_empty() {
continue; // The rights in file_flags must *exceed* those in dir_flags.
}
// Ensure the combination is valid (e.g. that file_flags is requesting more rights
// than those in dir_flags).
assert!(
(*file_flags & harness.dir_rights.all()) != (*dir_flags & harness.dir_rights.all()),
"Invalid test: file rights must exceed dir! (flags: dir = {:?}, file = {:?})",
*dir_flags,
*file_flags
);
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
dir_proxy
.open(
*file_flags | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_FILE,
TEST_FILE,
server,
)
.expect("Cannot open file");
assert_eq!(
get_open_status(&client).await,
zx::Status::ACCESS_DENIED,
"Opened a file with more rights than the directory! (flags: dir = {:?}, file = {:?})",
*dir_flags,
*file_flags
);
}
}
}
/// Checks that open fails with ZX_ERR_BAD_PATH when it should.
#[fasync::run_singlethreaded(test)]
async fn open_path() {
let harness = TestHarness::new().await;
if !harness.config.conformant_path_handling.unwrap_or_default() {
return;
}
let root = root_directory(vec![directory("dir", vec![])]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
// Valid paths:
for path in [".", "/", "/dir/"] {
open_node::<fio::NodeMarker>(&root_dir, fio::OpenFlags::RIGHT_READABLE, 0, path).await;
}
// Invalid paths:
for path in [
"", "//", "///", "////", "./", "/dir//", "//dir//", "/dir//", "/dir/../", "/dir/..",
"/dir/./", "/dir/.", "/./", "./dir",
] {
assert_eq!(
open_node_status::<fio::NodeMarker>(&root_dir, fio::OpenFlags::RIGHT_READABLE, 0, path)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS,
"path: {}",
path,
);
}
}
/// Check that a trailing flash with OPEN_FLAG_NOT_DIRECTORY returns ZX_ERR_INVALID_ARGS.
#[fasync::run_singlethreaded(test)]
async fn open_trailing_slash_with_not_directory() {
let harness = TestHarness::new().await;
let root = root_directory(vec![]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
0,
"foo/"
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
}
/// Checks that mode is ignored when opening existing nodes.
#[fasync::run_singlethreaded(test)]
async fn open_flags_and_mode() {
let harness = TestHarness::new().await;
let root = root_directory(vec![file(TEST_FILE, vec![]), directory("dir", vec![])]);
let root_dir = harness.get_directory(root, harness.dir_rights.all());
// mode should be ignored when opening an existing object.
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
TEST_FILE,
)
.await;
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
"dir",
)
.await;
open_node::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
libc::S_IRWXU,
"dir",
)
.await;
// MODE_TYPE_DIRECTORY is incompatible with OPEN_FLAG_NOT_DIRECTORY
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
fio::MODE_TYPE_DIRECTORY,
"foo"
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
// MODE_TYPE_FILE is incompatible with a path that specifies a directory
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
"foo/"
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
// MODE_TYPE_FILE is incompatible with OPEN_FLAG_DIRECTORY
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
fio::MODE_TYPE_FILE,
"foo"
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
// Can't open . with OPEN_FLAG_NOT_DIRECTORY
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
0,
"."
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
// Can't have OPEN_FLAG_DIRECTORY and OPEN_FLAG_NOT_DIRECTORY
assert_eq!(
open_node_status::<fio::NodeMarker>(
&root_dir,
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::DIRECTORY
| fio::OpenFlags::NOT_DIRECTORY,
0,
"."
)
.await
.expect_err("open succeeded"),
zx::Status::INVALID_ARGS
);
}
#[fasync::run_singlethreaded(test)]
async fn create_file_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_create.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
// Re-open directory with the flags being tested.
let dir = open_dir_with_flags(&test_dir, dir_flags, ".").await;
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
dir.open(
dir_flags | fio::OpenFlags::CREATE | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_FILE,
TEST_FILE,
server,
)
.expect("Cannot open file");
assert_eq!(get_open_status(&client).await, zx::Status::OK);
assert_eq!(read_file(&test_dir, TEST_FILE).await, &[]);
}
}
#[fasync::run_singlethreaded(test)]
async fn create_file_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_create.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
// Re-open directory with the flags being tested.
let dir = open_dir_with_flags(&test_dir, dir_flags, ".").await;
let (client, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy.");
dir.open(
dir_flags | fio::OpenFlags::CREATE | fio::OpenFlags::DESCRIBE,
fio::MODE_TYPE_FILE,
TEST_FILE,
server,
)
.expect("Cannot open file");
assert_eq!(get_open_status(&client).await, zx::Status::ACCESS_DENIED);
assert_file_not_found(&test_dir, TEST_FILE).await;
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_with_sufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_READABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let _data: Vec<u8> = file
.read(0)
.await
.expect("read failed")
.map_err(zx::Status::from_raw)
.expect("read error");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_with_insufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_READABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let result = file.read(0).await.expect("read failed").map_err(zx::Status::from_raw);
assert_eq!(result, Err(zx::Status::BAD_HANDLE))
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_at_with_sufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_READABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let _: Vec<u8> = file
.read_at(0, 0)
.await
.expect("read_at failed")
.map_err(zx::Status::from_raw)
.expect("read_at error");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_at_with_insufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_READABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let result =
file.read_at(0, 0).await.expect("read_at failed").map_err(zx::Status::from_raw);
assert_eq!(result, Err(zx::Status::BAD_HANDLE))
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_with_sufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let _: u64 = file
.write("".as_bytes())
.await
.expect("write failed")
.map_err(zx::Status::from_raw)
.expect("write error");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_with_insufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let result =
file.write("".as_bytes()).await.expect("write failed").map_err(zx::Status::from_raw);
assert_eq!(result, Err(zx::Status::BAD_HANDLE))
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_at_with_sufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let _: u64 = file
.write_at("".as_bytes(), 0)
.await
.expect("write_at failed")
.map_err(zx::Status::from_raw)
.expect("write_at error");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_write_at_with_insufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let result = file
.write_at("".as_bytes(), 0)
.await
.expect("write_at failed")
.map_err(zx::Status::from_raw);
assert_eq!(result, Err(zx::Status::BAD_HANDLE));
}
}
#[fasync::run_singlethreaded(test)]
async fn file_resize_with_sufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
file.resize(0)
.await
.expect("resize failed")
.map_err(zx::Status::from_raw)
.expect("resize error")
}
}
#[fasync::run_singlethreaded(test)]
async fn file_resize_with_insufficient_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file =
open_node::<fio::FileMarker>(&test_dir, file_flags, fio::MODE_TYPE_FILE, TEST_FILE)
.await;
let result = file.resize(0).await.expect("resize failed").map_err(zx::Status::from_raw);
assert_eq!(result, Err(zx::Status::BAD_HANDLE));
}
}
#[fasync::run_singlethreaded(test)]
async fn file_read_in_subdirectory() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_READABLE) {
let root = root_directory(vec![directory("subdir", vec![file("testing.txt", vec![])])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_node::<fio::FileMarker>(
&test_dir,
file_flags,
fio::MODE_TYPE_FILE,
"subdir/testing.txt",
)
.await;
let _data: Vec<u8> = file
.read(0)
.await
.expect("read failed")
.map_err(zx::Status::from_raw)
.expect("read error");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_readable_buffer_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default() {
return;
}
for file_flags in harness.vmo_file_rights.valid_combos_with(fio::OpenFlags::RIGHT_READABLE) {
// Should be able to get a readable VMO in default, exact, and private sharing modes.
for sharing_mode in
[fio::VmoFlags::empty(), fio::VmoFlags::SHARED_BUFFER, fio::VmoFlags::PRIVATE_CLONE]
{
let file = vmo_file(TEST_FILE, TEST_FILE_CONTENTS);
let (buffer, _) = create_file_and_get_buffer(
file,
&harness,
file_flags,
fio::VmoFlags::READ | sharing_mode,
)
.await
.expect("Failed to create file and obtain buffer");
// Ensure that the returned VMO's rights are consistent with the expected flags.
validate_vmo_rights(&buffer, fio::VmoFlags::READ);
// Check contents of buffer.
let mut data = vec![0; buffer.size as usize];
buffer.vmo.read(&mut data, 0).expect("VMO read failed");
assert_eq!(&data, TEST_FILE_CONTENTS);
}
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_readable_buffer_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default() {
return;
}
for file_flags in harness.vmo_file_rights.valid_combos_without(fio::OpenFlags::RIGHT_READABLE) {
let file = vmo_file(TEST_FILE, TEST_FILE_CONTENTS);
assert_eq!(
create_file_and_get_buffer(file, &harness, file_flags, fio::VmoFlags::READ)
.await
.expect_err("Error was expected"),
zx::Status::ACCESS_DENIED
);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_writable_buffer_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default() {
return;
}
// Writable VMOs currently require private sharing mode.
const VMO_FLAGS: fio::VmoFlags =
fio::VmoFlags::empty().union(fio::VmoFlags::WRITE).union(fio::VmoFlags::PRIVATE_CLONE);
for file_flags in harness.vmo_file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let file = vmo_file(TEST_FILE, TEST_FILE_CONTENTS);
let (buffer, _) = create_file_and_get_buffer(file, &harness, file_flags, VMO_FLAGS)
.await
.expect("Failed to create file and obtain buffer");
// Ensure that the returned VMO's rights are consistent with the expected flags.
validate_vmo_rights(&buffer, VMO_FLAGS);
// Ensure that we can actually write to the VMO.
buffer.vmo.write("bbbbb".as_bytes(), 0).expect("vmo write failed");
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_writable_buffer_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default() {
return;
}
const VMO_FLAGS: fio::VmoFlags =
fio::VmoFlags::empty().union(fio::VmoFlags::WRITE).union(fio::VmoFlags::PRIVATE_CLONE);
for file_flags in harness.vmo_file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let file = vmo_file(TEST_FILE, TEST_FILE_CONTENTS);
assert_eq!(
create_file_and_get_buffer(file, &harness, file_flags, VMO_FLAGS)
.await
.expect_err("Error was expected"),
zx::Status::ACCESS_DENIED
);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_executable_buffer_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default()
|| !harness.config.supports_executable_file.unwrap_or_default()
{
return;
}
// We should be able to get an executable VMO in default, exact, and private sharing modes.
// Note that the fuchsia.io interface requires the connection to have OPEN_RIGHT_READABLE in
// addition to OPEN_RIGHT_EXECUTABLE if passing VmoFlags::EXECUTE to the GetBuffer method.
for sharing_mode in
[fio::VmoFlags::empty(), fio::VmoFlags::SHARED_BUFFER, fio::VmoFlags::PRIVATE_CLONE]
{
let file = executable_file(TEST_FILE);
let vmo_flags = fio::VmoFlags::READ | fio::VmoFlags::EXECUTE | sharing_mode;
let (buffer, _) = create_file_and_get_buffer(
file,
&harness,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
vmo_flags,
)
.await
.expect("Failed to create file and obtain buffer");
// Ensure that the returned VMO's rights are consistent with the expected flags.
validate_vmo_rights(&buffer, vmo_flags);
}
}
#[fasync::run_singlethreaded(test)]
async fn file_get_executable_buffer_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default()
|| !harness.config.supports_executable_file.unwrap_or_default()
{
return;
}
// We should fail to get the buffer if the connection lacks execute rights.
for file_flags in
harness.executable_file_rights.valid_combos_without(fio::OpenFlags::RIGHT_EXECUTABLE)
{
let file = executable_file(TEST_FILE);
assert_eq!(
create_file_and_get_buffer(file, &harness, file_flags, fio::VmoFlags::EXECUTE)
.await
.expect_err("Error was expected"),
zx::Status::ACCESS_DENIED
);
}
// The fuchsia.io interface additionally specifies that GetBuffer should fail if VmoFlags::EXECUTE
// is specified but connection lacks OPEN_RIGHT_READABLE.
for file_flags in
harness.executable_file_rights.valid_combos_without(fio::OpenFlags::RIGHT_READABLE)
{
let file = executable_file(TEST_FILE);
assert_eq!(
create_file_and_get_buffer(file, &harness, file_flags, fio::VmoFlags::EXECUTE)
.await
.expect_err("Error was expected"),
zx::Status::ACCESS_DENIED
);
}
}
// Ensure that passing VmoFlags::SHARED_BUFFER to GetBuffer returns the same KOID as the backing VMO.
#[fasync::run_singlethreaded(test)]
async fn file_get_buffer_exact_same_koid() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_buffer.unwrap_or_default() {
return;
}
let size = TEST_FILE_CONTENTS.len() as u64;
let vmo = zx::Vmo::create(size).expect("Cannot create VMO");
let original_koid = vmo.get_koid();
let vmofile_object = io_test::DirectoryEntry::VmoFile(io_test::VmoFile {
name: Some(TEST_FILE.to_string()),
buffer: Some(fidl_fuchsia_mem::Range { vmo, offset: 0, size }),
..io_test::VmoFile::EMPTY
});
let (buffer, _) = create_file_and_get_buffer(
vmofile_object,
&harness,
fio::OpenFlags::RIGHT_READABLE,
fio::VmoFlags::READ | fio::VmoFlags::SHARED_BUFFER,
)
.await
.expect("Failed to create file and obtain buffer");
assert_eq!(original_koid, buffer.vmo.get_koid());
}
#[fasync::run_singlethreaded(test)]
async fn directory_describe() {
let harness = TestHarness::new().await;
let root = root_directory(vec![]);
let test_dir = harness.get_directory(root, fio::OpenFlags::empty());
let node_info = test_dir.describe().await.expect("describe failed");
assert!(matches!(node_info, fio::NodeInfo::Directory { .. }));
}
#[fasync::run_singlethreaded(test)]
async fn file_describe() {
let harness = TestHarness::new().await;
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, fio::OpenFlags::RIGHT_READABLE);
let file = open_node::<fio::FileMarker>(
&test_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
TEST_FILE,
)
.await;
let node_info = file.describe().await.expect("describe failed");
// The node_info can be either File or Vmofile type.
assert!(matches!(node_info, fio::NodeInfo::File { .. } | fio::NodeInfo::Vmofile { .. }));
}
#[fasync::run_singlethreaded(test)]
async fn vmo_file_describe() {
let harness = TestHarness::new().await;
if !harness.config.supports_vmo_file.unwrap_or_default() {
return;
}
let root = root_directory(vec![vmo_file(TEST_FILE, TEST_FILE_CONTENTS)]);
let test_dir = harness
.get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
let file = open_node::<fio::FileMarker>(
&test_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
TEST_FILE,
)
.await;
let node_info = file.describe().await.expect("describe failed");
if let fio::NodeInfo::Vmofile { 0: vmo_file_obj } = node_info {
assert_eq!(vmo_file_obj.offset, 0);
assert_eq!(vmo_file_obj.length as usize, TEST_FILE_CONTENTS.len());
// Ensure the permissions we get from describe do not exceed those of the connection.
let vmo_rights: zx::Rights =
vmo_file_obj.vmo.basic_info().expect("failed to get VMO info").rights;
assert!(vmo_rights.contains(zx::Rights::READ));
assert!(!vmo_rights.contains(zx::Rights::WRITE));
assert!(!vmo_rights.contains(zx::Rights::EXECUTE));
} else {
panic!("Expected VmoFile, got {:?} instead!", node_info);
}
}
#[fasync::run_singlethreaded(test)]
async fn vmo_file_write_to_limits() {
let harness = TestHarness::new().await;
if !harness.config.supports_vmo_file.unwrap_or_default() {
return;
}
let root = root_directory(vec![vmo_file(TEST_FILE, TEST_FILE_CONTENTS)]);
let test_dir = harness
.get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
let file = open_node::<fio::FileMarker>(
&test_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
fio::MODE_TYPE_FILE,
TEST_FILE,
)
.await;
file.describe().await.expect("describe failed");
let data = vec![0u8];
file.write_at(&data[..], TEST_FILE_CONTENTS.len().try_into().unwrap())
.await
.expect("fidl call failed")
.expect_err("Write at end of file limit should fail");
// An empty write should still succeed even if it targets an out-of-bounds offset.
file.write_at(&[], TEST_FILE_CONTENTS.len().try_into().unwrap())
.await
.expect("fidl call failed")
.expect("Zero-byte write at end of file limit should succeed");
}
#[fasync::run_singlethreaded(test)]
async fn get_token_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_token.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![]);
let test_dir = harness.get_directory(root, dir_flags);
let (status, _handle) = test_dir.get_token().await.expect("get_token failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Handle is tested in other test cases.
}
}
#[fasync::run_singlethreaded(test)]
async fn get_token_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_get_token.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![]);
let test_dir = harness.get_directory(root, dir_flags);
let (status, _handle) = test_dir.get_token().await.expect("get_token failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn rename_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_rename.unwrap_or_default()
|| !harness.config.supports_get_token.unwrap_or_default()
{
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![
directory("src", vec![file("old.txt", contents.to_vec())]),
directory("dest", vec![]),
]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let dest_dir = open_rw_dir(&test_dir, "dest").await;
let dest_token = get_token(&dest_dir).await;
// Rename src/old.txt -> dest/new.txt.
let status = src_dir
.rename("old.txt", zx::Event::from(dest_token), "new.txt")
.await
.expect("rename failed");
assert!(status.is_ok());
// Check dest/new.txt was created and has correct contents.
assert_eq!(read_file(&test_dir, "dest/new.txt").await, contents);
// Check src/old.txt no longer exists.
assert_file_not_found(&test_dir, "src/old.txt").await;
}
}
#[fasync::run_singlethreaded(test)]
async fn rename_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_rename.unwrap_or_default()
|| !harness.config.supports_get_token.unwrap_or_default()
{
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![
directory("src", vec![file("old.txt", contents.to_vec())]),
directory("dest", vec![]),
]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let dest_dir = open_rw_dir(&test_dir, "dest").await;
let dest_token = get_token(&dest_dir).await;
// Try renaming src/old.txt -> dest/new.txt.
let status = src_dir
.rename("old.txt", zx::Event::from(dest_token), "new.txt")
.await
.expect("rename failed");
assert!(status.is_err());
assert_eq!(status.err().unwrap(), zx::Status::BAD_HANDLE.into_raw());
}
}
#[fasync::run_singlethreaded(test)]
async fn rename_with_slash_in_path_fails() {
let harness = TestHarness::new().await;
if !harness.config.supports_rename.unwrap_or_default()
|| !harness.config.supports_get_token.unwrap_or_default()
{
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![
directory("src", vec![file("old.txt", contents.to_vec())]),
directory("dest", vec![]),
]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let dest_dir = open_rw_dir(&test_dir, "dest").await;
// Including a slash in the src or dest path should fail.
let status = test_dir
.rename("src/old.txt", zx::Event::from(get_token(&dest_dir).await), "new.txt")
.await
.expect("rename failed");
assert!(status.is_err());
assert_eq!(status.err().unwrap(), zx::Status::INVALID_ARGS.into_raw());
let status = src_dir
.rename("old.txt", zx::Event::from(get_token(&dest_dir).await), "nested/new.txt")
.await
.expect("rename failed");
assert!(status.is_err());
assert_eq!(status.err().unwrap(), zx::Status::INVALID_ARGS.into_raw());
}
}
#[fasync::run_singlethreaded(test)]
async fn link_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_link.unwrap_or_default()
|| !harness.config.supports_get_token.unwrap_or_default()
{
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.dir_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![
directory("src", vec![file("old.txt", contents.to_vec())]),
directory("dest", vec![]),
]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let dest_dir = open_rw_dir(&test_dir, "dest").await;
let dest_token = get_token(&dest_dir).await;
// Link src/old.txt -> dest/new.txt.
let status = src_dir.link("old.txt", dest_token, "new.txt").await.expect("link failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Check dest/new.txt was created and has correct contents.
assert_eq!(read_file(&test_dir, "dest/new.txt").await, contents);
// Check src/old.txt still exists.
assert_eq!(read_file(&test_dir, "src/old.txt").await, contents);
}
}
#[fasync::run_singlethreaded(test)]
async fn link_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_link.unwrap_or_default()
|| !harness.config.supports_get_token.unwrap_or_default()
{
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.dir_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![
directory("src", vec![file("old.txt", contents.to_vec())]),
directory("dest", vec![]),
]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let dest_dir = open_rw_dir(&test_dir, "dest").await;
let dest_token = get_token(&dest_dir).await;
// Link src/old.txt -> dest/new.txt.
let status = src_dir.link("old.txt", dest_token, "new.txt").await.expect("link failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::BAD_HANDLE);
// Check dest/new.txt was not created.
assert_file_not_found(&test_dir, "dest/new.txt").await;
// Check src/old.txt still exists.
assert_eq!(read_file(&test_dir, "src/old.txt").await, contents);
}
}
#[fasync::run_singlethreaded(test)]
async fn unlink_file_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_unlink.unwrap_or_default() {
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness
.dir_rights
.valid_combos_with(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE)
{
let root =
root_directory(vec![directory("src", vec![file("file.txt", contents.to_vec())])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
let file = open_node::<fio::FileMarker>(
&src_dir,
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_FILE,
"file.txt",
)
.await;
src_dir
.unlink("file.txt", fio::UnlinkOptions::EMPTY)
.await
.expect("unlink fidl failed")
.expect("unlink failed");
// Check file is gone.
assert_file_not_found(&test_dir, "src/file.txt").await;
// Ensure file connection remains usable.
let read_result = file
.read(contents.len() as u64)
.await
.expect("read failed")
.map_err(zx::Status::from_raw)
.expect("read error");
assert_eq!(read_result, contents);
}
}
#[fasync::run_singlethreaded(test)]
async fn unlink_file_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_unlink.unwrap_or_default() {
return;
}
let contents = "abcdef".as_bytes();
for dir_flags in harness.dir_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root =
root_directory(vec![directory("src", vec![file("file.txt", contents.to_vec())])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let src_dir = open_dir_with_flags(&test_dir, dir_flags, "src").await;
assert_eq!(
src_dir
.unlink("file.txt", fio::UnlinkOptions::EMPTY)
.await
.expect("unlink fidl failed")
.expect_err("unlink succeeded"),
zx::sys::ZX_ERR_BAD_HANDLE
);
// Check file still exists.
assert_eq!(read_file(&test_dir, "src/file.txt").await, contents);
}
}
#[fasync::run_singlethreaded(test)]
async fn unlink_directory_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_unlink.unwrap_or_default() {
return;
}
for dir_flags in harness.dir_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![directory("src", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
// Re-open dir with flags being tested.
let dir = open_dir_with_flags(&test_dir, dir_flags, ".").await;
dir.unlink("src", fio::UnlinkOptions::EMPTY)
.await
.expect("unlink fidl failed")
.expect("unlink failed");
}
}
#[fasync::run_singlethreaded(test)]
async fn unlink_directory_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_unlink.unwrap_or_default() {
return;
}
for dir_flags in harness.dir_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![directory("src", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
// Re-open dir with flags being tested.
let dir = open_dir_with_flags(&test_dir, dir_flags, ".").await;
assert_eq!(
dir.unlink("src", fio::UnlinkOptions::EMPTY)
.await
.expect("unlink fidl failed")
.expect_err("unlink succeeded"),
zx::sys::ZX_ERR_BAD_HANDLE
);
}
}
#[fasync::run_singlethreaded(test)]
async fn unlink_must_be_directory() {
let harness = TestHarness::new().await;
if !harness.config.supports_unlink.unwrap_or_default() {
return;
}
let root = root_directory(vec![directory("dir", vec![]), file("file", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let must_be_directory = fio::UnlinkOptions {
flags: Some(fio::UnlinkFlags::MUST_BE_DIRECTORY),
..fio::UnlinkOptions::EMPTY
};
test_dir
.unlink("dir", must_be_directory.clone())
.await
.expect("unlink fidl failed")
.expect("unlink dir failed");
assert_eq!(
test_dir
.unlink("file", must_be_directory)
.await
.expect("unlink fidl failed")
.expect_err("unlink file succeeded"),
zx::sys::ZX_ERR_NOT_DIR
);
}
#[fasync::run_singlethreaded(test)]
async fn clone_file_with_same_or_fewer_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos() {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_file_with_flags(&test_dir, file_flags, TEST_FILE).await;
// Clone using every subset of flags.
for clone_flags in build_flag_combinations(0, file_flags.bits()) {
let clone_flags = fio::OpenFlags::from_bits_truncate(clone_flags);
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
file.clone(clone_flags | fio::OpenFlags::DESCRIBE, server).expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::OK);
// Check flags of cloned connection are correct.
let proxy = convert_node_proxy::<fio::FileMarker>(proxy);
let (status, flags) = proxy.get_flags().await.expect("get_flags failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
assert_eq!(flags, clone_flags);
}
}
}
#[fasync::run_singlethreaded(test)]
async fn clone_file_with_same_rights_flag() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos() {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_file_with_flags(&test_dir, file_flags, TEST_FILE).await;
// Clone using CLONE_FLAG_SAME_RIGHTS.
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
file.clone(fio::OpenFlags::CLONE_SAME_RIGHTS | fio::OpenFlags::DESCRIBE, server)
.expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::OK);
// Check flags of cloned connection are correct.
let proxy = convert_node_proxy::<fio::FileMarker>(proxy);
let (status, flags) = proxy.get_flags().await.expect("get_flags failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
assert_eq!(flags, file_flags);
}
}
#[fasync::run_singlethreaded(test)]
async fn clone_file_with_additional_rights() {
let harness = TestHarness::new().await;
for file_flags in harness.file_rights.valid_combos() {
let root = root_directory(vec![file(TEST_FILE, vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_file_with_flags(&test_dir, file_flags, TEST_FILE).await;
// Clone using every superset of flags, should fail.
for clone_flags in
build_flag_combinations(file_flags.bits(), harness.dir_rights.all().bits())
{
let clone_flags = fio::OpenFlags::from_bits_truncate(clone_flags);
if clone_flags == file_flags {
continue;
}
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
file.clone(clone_flags | fio::OpenFlags::DESCRIBE, server).expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::ACCESS_DENIED);
}
}
}
#[fasync::run_singlethreaded(test)]
async fn clone_directory_with_same_or_fewer_rights() {
let harness = TestHarness::new().await;
for dir_flags in harness.dir_rights.valid_combos() {
let root = root_directory(vec![directory("dir", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let dir = open_dir_with_flags(&test_dir, dir_flags, "dir").await;
// Clone using every subset of flags.
for clone_flags in build_flag_combinations(0, dir_flags.bits()) {
let clone_flags = fio::OpenFlags::from_bits_truncate(clone_flags);
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
dir.clone(clone_flags | fio::OpenFlags::DESCRIBE, server).expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::OK);
// Check flags of cloned connection are correct.
let (status, flags) = proxy.get_flags().await.expect("get_flags failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
assert_eq!(flags, clone_flags);
}
}
}
#[fasync::run_singlethreaded(test)]
async fn clone_directory_with_same_rights_flag() {
let harness = TestHarness::new().await;
for dir_flags in harness.dir_rights.valid_combos() {
let root = root_directory(vec![directory("dir", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let dir = open_dir_with_flags(&test_dir, dir_flags, "dir").await;
// Clone using CLONE_FLAG_SAME_RIGHTS.
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
dir.clone(fio::OpenFlags::CLONE_SAME_RIGHTS | fio::OpenFlags::DESCRIBE, server)
.expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::OK);
// Check flags of cloned connection are correct.
let proxy = convert_node_proxy::<fio::DirectoryMarker>(proxy);
let (status, flags) = proxy.get_flags().await.expect("get_flags failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
assert_eq!(flags, dir_flags);
}
}
#[fasync::run_singlethreaded(test)]
async fn clone_directory_with_additional_rights() {
let harness = TestHarness::new().await;
for dir_flags in harness.dir_rights.valid_combos() {
let root = root_directory(vec![directory("dir", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let dir = open_dir_with_flags(&test_dir, dir_flags, "dir").await;
// Clone using every superset of flags, should fail.
for clone_flags in
build_flag_combinations(dir_flags.bits(), harness.dir_rights.all().bits())
{
let clone_flags = fio::OpenFlags::from_bits_truncate(clone_flags);
if clone_flags == dir_flags {
continue;
}
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("create_proxy failed");
dir.clone(clone_flags | fio::OpenFlags::DESCRIBE, server).expect("clone failed");
let status = get_open_status(&proxy).await;
assert_eq!(status, zx::Status::ACCESS_DENIED);
}
}
}
#[fasync::run_singlethreaded(test)]
async fn set_attr_file_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_set_attr.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file("file", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_file_with_flags(&test_dir, dir_flags, "file").await;
let (status, old_attr) = file.get_attr().await.expect("get_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Set CREATION_TIME flag, but not MODIFICATION_TIME.
let status = file
.set_attr(
fio::NodeAttributeFlags::CREATION_TIME,
&mut fio::NodeAttributes {
creation_time: 111,
modification_time: 222,
..EMPTY_NODE_ATTRS
},
)
.await
.expect("set_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
let (status, new_attr) = file.get_attr().await.expect("get_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Check that only creation_time was updated.
let expected = fio::NodeAttributes { creation_time: 111, ..old_attr };
assert_eq!(new_attr, expected);
}
}
#[fasync::run_singlethreaded(test)]
async fn set_attr_file_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_set_attr.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![file("file", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let file = open_file_with_flags(&test_dir, dir_flags, "file").await;
let status = file
.set_attr(
fio::NodeAttributeFlags::CREATION_TIME,
&mut fio::NodeAttributes {
creation_time: 111,
modification_time: 222,
..EMPTY_NODE_ATTRS
},
)
.await
.expect("set_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::BAD_HANDLE);
}
}
#[fasync::run_singlethreaded(test)]
async fn set_attr_directory_with_sufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_set_attr.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_with(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![directory("dir", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let dir = open_dir_with_flags(&test_dir, dir_flags, "dir").await;
let (status, old_attr) = dir.get_attr().await.expect("get_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Set CREATION_TIME flag, but not MODIFICATION_TIME.
let status = dir
.set_attr(
fio::NodeAttributeFlags::CREATION_TIME,
&mut fio::NodeAttributes {
creation_time: 111,
modification_time: 222,
..EMPTY_NODE_ATTRS
},
)
.await
.expect("set_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
let (status, new_attr) = dir.get_attr().await.expect("get_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
// Check that only creation_time was updated.
let expected = fio::NodeAttributes { creation_time: 111, ..old_attr };
assert_eq!(new_attr, expected);
}
}
#[fasync::run_singlethreaded(test)]
async fn set_attr_directory_with_insufficient_rights() {
let harness = TestHarness::new().await;
if !harness.config.supports_set_attr.unwrap_or_default() {
return;
}
for dir_flags in harness.file_rights.valid_combos_without(fio::OpenFlags::RIGHT_WRITABLE) {
let root = root_directory(vec![directory("dir", vec![])]);
let test_dir = harness.get_directory(root, harness.dir_rights.all());
let dir = open_dir_with_flags(&test_dir, dir_flags, "dir").await;
let status = dir
.set_attr(
fio::NodeAttributeFlags::CREATION_TIME,
&mut fio::NodeAttributes {
creation_time: 111,
modification_time: 222,
..EMPTY_NODE_ATTRS
},
)
.await
.expect("set_attr failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::BAD_HANDLE);
}
}