blob: 83a1bf60ef14533d85f1c8225728599230db53a0 [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 {
assert_matches::assert_matches,
async_trait::async_trait,
fidl::endpoints::{create_endpoints, ServerEnd},
fidl_fuchsia_io as fio, fuchsia_async as fasync,
fuchsia_zircon::{self as zx, HandleBased, Status},
fxfs_testing::TestFixture,
std::sync::Arc,
syncio::{
zxio, zxio_node_attr_has_t, zxio_node_attributes_t, OpenOptions, SeekOrigin, XattrSetMode,
Zxio,
},
vfs::{
directory::entry::{DirectoryEntry, EntryInfo},
execution_scope::ExecutionScope,
file::{FidlIoConnection, File, FileIo, FileOptions, SyncMode},
node::Node,
path::Path,
pseudo_directory,
symlink::Symlink,
ToObjectRequest,
},
};
#[fuchsia::test]
async fn test_symlink() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
let symlink_zxio =
dir_zxio.create_symlink("symlink", b"target").expect("create_symlink failed");
assert_eq!(symlink_zxio.read_link().expect("read_link failed"), b"target");
// Test some error cases
assert_eq!(
dir_zxio.create_symlink("symlink", b"target").expect_err("create symlink succeeded"),
zx::Status::ALREADY_EXISTS
);
assert_eq!(
dir_zxio.create_symlink("a/b", b"target").expect_err("create symlink succeeded"),
zx::Status::INVALID_ARGS
);
assert_eq!(
dir_zxio
.create_symlink("symlink2", &vec![65; 300])
.expect_err("create symlink succeeded"),
zx::Status::BAD_PATH
);
let symlink_zxio =
dir_zxio.open(fio::OpenFlags::RIGHT_READABLE, "symlink").expect("open failed");
assert_eq!(symlink_zxio.read_link().expect("read_link failed"), b"target");
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_read_link_error() {
struct ErrorSymlink;
#[async_trait]
impl Symlink for ErrorSymlink {
async fn read_target(&self) -> Result<Vec<u8>, zx::Status> {
Err(zx::Status::IO)
}
}
#[async_trait]
impl Node for ErrorSymlink {
async fn get_attributes(
&self,
_requested_attributes: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, zx::Status> {
unreachable!();
}
async fn get_attrs(&self) -> Result<fio::NodeAttributes, zx::Status> {
unreachable!();
}
}
let dir = pseudo_directory! {
"error_symlink" => Arc::new(ErrorSymlink),
};
let (dir_client, dir_server) = create_endpoints();
let scope = ExecutionScope::new();
dir.open(
scope,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
Path::dot(),
dir_server,
);
fasync::unblock(|| {
let dir_zxio =
Zxio::create(dir_client.into_channel().into_handle()).expect("create failed");
assert_eq!(
dir_zxio
.open(fio::OpenFlags::RIGHT_READABLE, "error_symlink")
.expect_err("open succeeded"),
zx::Status::IO
);
})
.await;
}
#[fuchsia::test]
async fn test_xattr_dir() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.open(
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::DIRECTORY
| fio::OpenFlags::CREATE,
fio::ModeType::empty(),
"foo",
dir_server.into(),
)
.expect("open failed");
fasync::unblock(|| {
let foo_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
assert_matches!(foo_zxio.xattr_get(b"security.selinux"), Err(zx::Status::NOT_FOUND));
foo_zxio.xattr_set(b"security.selinux", b"bar", XattrSetMode::Set).unwrap();
assert_eq!(foo_zxio.xattr_get(b"security.selinux").unwrap(), b"bar");
{
let names = foo_zxio.xattr_list().unwrap();
assert_eq!(names, vec![b"security.selinux".to_owned()]);
}
foo_zxio.xattr_remove(b"security.selinux").unwrap();
assert_matches!(foo_zxio.xattr_get(b"security.selinux"), Err(zx::Status::NOT_FOUND));
{
let names = foo_zxio.xattr_list().unwrap();
assert_eq!(names, Vec::<Vec<u8>>::new());
}
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_xattr_dir_multiple_attributes() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.open(
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::DIRECTORY
| fio::OpenFlags::CREATE,
fio::ModeType::empty(),
"foo",
dir_server.into(),
)
.expect("open failed");
fasync::unblock(|| {
let foo_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
let names = &[
b"security.selinux".to_vec(),
b"user.sha".to_vec(),
b"fuchsia.merkle".to_vec(),
b"starnix.mode".to_vec(),
b"invalid in linux but fine for fuchsia!".to_vec(),
];
let mut sorted_names = names.to_vec();
sorted_names.sort();
let values = &[
b"important security attribute".to_vec(),
b"abc1234".to_vec(),
b"fffffffffff".to_vec(),
b"drwxrwxrwx".to_vec(),
b"\0\0 nulls are fine in the value \0\0\0".to_vec(),
];
for (name, value) in names.iter().zip(values.iter()) {
foo_zxio.xattr_set(&name, &value, XattrSetMode::Set).unwrap();
}
let mut listed_names = foo_zxio.xattr_list().unwrap();
listed_names.sort();
// Sort the two lists, because there isn't any guaranteed order they will come back from the
// server.
assert_eq!(listed_names, sorted_names);
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_xattr_symlink() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
let symlink_zxio =
dir_zxio.create_symlink("symlink", b"target").expect("create_symlink failed");
assert_eq!(symlink_zxio.read_link().expect("read_link failed"), b"target");
assert_matches!(symlink_zxio.xattr_get(b"security.selinux"), Err(zx::Status::NOT_FOUND));
symlink_zxio.xattr_set(b"security.selinux", b"bar", XattrSetMode::Set).unwrap();
assert_eq!(symlink_zxio.xattr_get(b"security.selinux").unwrap(), b"bar");
{
let names = symlink_zxio.xattr_list().unwrap();
assert_eq!(names, vec![b"security.selinux".to_owned()]);
}
symlink_zxio.xattr_remove(b"security.selinux").unwrap();
assert_matches!(symlink_zxio.xattr_get(b"security.selinux"), Err(zx::Status::NOT_FOUND));
{
let names = symlink_zxio.xattr_list().unwrap();
assert_eq!(names, Vec::<Vec<u8>>::new());
}
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_xattr_file_large_attribute() {
let fixture = TestFixture::new().await;
let (foo_client, foo_server) = zx::Channel::create();
fixture
.root()
.open(
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::CREATE,
fio::ModeType::empty(),
"foo",
foo_server.into(),
)
.expect("open failed");
fasync::unblock(|| {
let foo_zxio = Zxio::create(foo_client.into_handle()).expect("create failed");
let value_len = fio::MAX_INLINE_ATTRIBUTE_VALUE as usize + 64;
let value = std::iter::repeat(0xff).take(value_len).collect::<Vec<u8>>();
foo_zxio.xattr_set(b"user.big_attribute", &value, XattrSetMode::Set).unwrap();
assert_eq!(foo_zxio.xattr_get(b"user.big_attribute").unwrap(), value);
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_open2() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
// Create a test directory.
let test_dir = dir_zxio
.open2(
"test_dir",
OpenOptions { mode: fio::OpenMode::AlwaysCreate, ..OpenOptions::directory(None) },
None,
)
.expect("open2 failed");
// Now create a file in that directory.
let test_file = test_dir
.open2(
"test_file",
OpenOptions {
mode: fio::OpenMode::AlwaysCreate,
..OpenOptions::file(fio::FileProtocolFlags::empty())
},
None,
)
.expect("open2 failed");
// Write something to the file.
const CONTENT: &[u8] = b"hello";
test_file.write(CONTENT).expect("write failed");
// Open the directory without specifying any protocols and ask for attributes.
let mut attr = zxio_node_attributes_t {
has: zxio_node_attr_has_t {
protocols: true,
abilities: true,
id: true,
link_count: true,
..Default::default()
},
..Default::default()
};
let test_dir = dir_zxio
.open2("test_dir", OpenOptions::default(), Some(&mut attr))
.expect("open2 failed");
assert_eq!(attr.protocols, zxio::ZXIO_NODE_PROTOCOL_DIRECTORY);
assert_eq!(
attr.abilities,
(fio::Operations::ENUMERATE
| fio::Operations::TRAVERSE
| fio::Operations::MODIFY_DIRECTORY
| fio::Operations::GET_ATTRIBUTES
| fio::Operations::UPDATE_ATTRIBUTES)
.bits()
);
// Fxfs will always return a non-zero ID.
assert_ne!(attr.id, 0);
assert_eq!(attr.link_count, 2);
// And now the file, and ask for attributes.
let mut attr = zxio_node_attributes_t {
has: zxio_node_attr_has_t {
protocols: true,
abilities: true,
id: true,
content_size: true,
..Default::default()
},
..Default::default()
};
let _test_file = test_dir
.open2("test_file", OpenOptions::default(), Some(&mut attr))
.expect("open2 failed");
assert_eq!(attr.protocols, zxio::ZXIO_NODE_PROTOCOL_FILE);
assert_eq!(
attr.abilities,
(fio::Operations::READ_BYTES
| fio::Operations::WRITE_BYTES
| fio::Operations::GET_ATTRIBUTES
| fio::Operations::UPDATE_ATTRIBUTES)
.bits()
);
// Fxfs will always return a non-zero ID.
assert_ne!(attr.id, 0);
assert_eq!(attr.content_size, 5);
// Create and open a symlink.
test_dir.create_symlink("symlink", b"target").expect("create_symlink failed");
let symlink =
test_dir.open2("symlink", OpenOptions::default(), None).expect("open2 failed");
assert_eq!(symlink.read_link().expect("read_link failed"), b"target");
// Test file protocol flags.
let test_file = test_dir
.open2("test_file", OpenOptions::file(fio::FileProtocolFlags::APPEND), None)
.expect("open2 failed");
const APPEND_CONTENT: &[u8] = b" there";
test_file.write(APPEND_CONTENT).expect("write failed");
test_file.seek(SeekOrigin::Start, 0).expect("seek failed");
let mut buf = [0; 20];
assert_eq!(
test_file.read(&mut buf).expect("read failed"),
CONTENT.len() + APPEND_CONTENT.len()
);
assert_eq!(&buf[..CONTENT.len()], CONTENT);
assert_eq!(&buf[CONTENT.len()..CONTENT.len() + APPEND_CONTENT.len()], APPEND_CONTENT);
// TODO(fxbug.dev/127341): Test create attributes.
// TODO(fxbug.dev/125830): Test for POSIX attributes.
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_open2_rights() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
// Create and write to a file.
let test_file = dir_zxio
.open2(
"test_file",
OpenOptions {
mode: fio::OpenMode::AlwaysCreate,
..OpenOptions::file(fio::FileProtocolFlags::empty())
},
None,
)
.expect("open2 failed");
// Write something to the file.
const CONTENT: &[u8] = b"hello";
test_file.write(CONTENT).expect("write failed");
// Check rights
let test_file = dir_zxio
.open2(
"test_file",
OpenOptions { rights: fio::Operations::READ_BYTES, ..Default::default() },
None,
)
.expect("open2 failed");
let mut buf = [0; 20];
assert_eq!(test_file.read(&mut buf).expect("read failed"), CONTENT.len());
assert_eq!(&buf[..CONTENT.len()], CONTENT);
// Make sure we can't write to the file.
assert_eq!(test_file.write(&buf).expect_err("write succeeded"), zx::Status::BAD_HANDLE);
// Check rights inherited from directory.
let dir = dir_zxio
.open2(
".",
OpenOptions { rights: fio::Operations::READ_BYTES, ..Default::default() },
None,
)
.expect("open2 failed");
let test_file = dir.open2("test_file", OpenOptions::default(), None).expect("open2 failed");
assert_eq!(test_file.read(&mut buf).expect("read failed"), CONTENT.len());
assert_eq!(&buf[..CONTENT.len()], CONTENT);
// Make sure we can't write to the file.
assert_eq!(test_file.write(&buf).expect_err("write succeeded"), zx::Status::BAD_HANDLE);
// Optional rights on directories.
let test_dir = dir_zxio
.open2(
".",
OpenOptions {
rights: fio::Operations::READ_BYTES,
..OpenOptions::directory(Some(fio::Operations::WRITE_BYTES))
},
None,
)
.expect("open2 failed");
// Now make sure we can open and write to the test file.
let test_file =
test_dir.open2("test_file", OpenOptions::default(), None).expect("open2 failed");
assert_eq!(test_file.read(&mut buf).expect("read failed"), CONTENT.len());
assert_eq!(&buf[..CONTENT.len()], CONTENT);
// This time we should be able to write to the file.
test_file.write(b"foo").expect("write failed");
})
.await;
fixture.close().await;
}
#[fuchsia::test]
async fn test_open2_node() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
dir_zxio
.open_node(".", fio::NodeProtocolFlags::MUST_BE_DIRECTORY, None)
.expect("open_node failed");
// Create a file.
let test_file = dir_zxio
.open2(
"test_file",
OpenOptions {
mode: fio::OpenMode::AlwaysCreate,
..OpenOptions::file(fio::FileProtocolFlags::empty())
},
None,
)
.expect("open2 failed");
// Write something to the file.
const CONTENT: &[u8] = b"hello";
test_file.write(CONTENT).expect("write failed");
assert_eq!(
dir_zxio
.open_node("test_file", fio::NodeProtocolFlags::MUST_BE_DIRECTORY, None,)
.expect_err("open_node succeeded"),
zx::Status::NOT_DIR
);
// Check we can get attributes.
let mut attr = zxio_node_attributes_t {
has: zxio_node_attr_has_t { protocols: true, content_size: true, ..Default::default() },
..Default::default()
};
dir_zxio
.open_node("test_file", fio::NodeProtocolFlags::empty(), Some(&mut attr))
.expect("open_node failed");
assert_eq!(attr.protocols, zxio::ZXIO_NODE_PROTOCOL_FILE);
assert_eq!(attr.content_size, 5);
})
.await;
fixture.close().await;
}
// TODO(fxbug.dev/293943124): once fxfs supports allocate, use the test fixture.
struct AllocateFile {
res: Result<(), Status>,
}
impl AllocateFile {
fn new(res: Result<(), Status>) -> Arc<Self> {
Arc::new(AllocateFile { res })
}
}
impl DirectoryEntry for AllocateFile {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: fio::OpenFlags,
_path: Path,
server_end: ServerEnd<fio::NodeMarker>,
) {
flags.to_object_request(server_end).handle(|object_request| {
object_request.spawn_connection(
scope.clone(),
self.clone(),
flags,
FidlIoConnection::create,
)
});
}
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)
}
}
#[async_trait]
impl FileIo for AllocateFile {
async fn read_at(&self, _offset: u64, _buffer: &mut [u8]) -> Result<u64, Status> {
unimplemented!()
}
async fn write_at(&self, _offset: u64, _content: &[u8]) -> Result<u64, Status> {
unimplemented!()
}
async fn append(&self, _content: &[u8]) -> Result<(u64, u64), Status> {
unimplemented!()
}
}
#[async_trait]
impl vfs::node::Node for AllocateFile {
async fn get_attrs(&self) -> Result<fio::NodeAttributes, Status> {
unimplemented!()
}
async fn get_attributes(
&self,
_query: fio::NodeAttributesQuery,
) -> Result<fio::NodeAttributes2, Status> {
unimplemented!()
}
}
#[async_trait]
impl File for AllocateFile {
fn writable(&self) -> bool {
true
}
async fn open_file(&self, _options: &FileOptions) -> Result<(), Status> {
Ok(())
}
async fn truncate(&self, _length: u64) -> Result<(), Status> {
unimplemented!()
}
async fn get_backing_memory(&self, _flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
unimplemented!()
}
async fn get_size(&self) -> Result<u64, Status> {
unimplemented!()
}
async fn set_attrs(
&self,
_flags: fio::NodeAttributeFlags,
_attrs: fio::NodeAttributes,
) -> Result<(), Status> {
unimplemented!()
}
async fn update_attributes(
&self,
_attributes: fio::MutableNodeAttributes,
) -> Result<(), Status> {
unimplemented!()
}
async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
Ok(())
}
async fn allocate(
&self,
_offset: u64,
_length: u64,
_mode: fio::AllocateMode,
) -> Result<(), Status> {
self.res
}
}
#[fuchsia::test]
async fn test_allocate_file() {
let dir = pseudo_directory! {
"foo" => AllocateFile::new(Ok(())),
};
let (dir_client, dir_server) = create_endpoints();
let scope = ExecutionScope::new();
dir.open(
scope,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
Path::dot(),
dir_server,
);
fasync::unblock(|| {
let dir_zxio =
Zxio::create(dir_client.into_channel().into_handle()).expect("create failed");
let foo_zxio = dir_zxio
.open(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, "foo")
.expect("open failed");
foo_zxio.allocate(0, 10, syncio::AllocateMode::empty()).unwrap();
})
.await;
}
#[fuchsia::test]
async fn test_allocate_file_not_sup() {
// For now we just tell it what error to send back, but this is mimicking the first pass at
// allocate which won't support any options.
let dir = pseudo_directory! {
"foo" => AllocateFile::new(Err(Status::NOT_SUPPORTED)),
};
let (dir_client, dir_server) = create_endpoints();
let scope = ExecutionScope::new();
dir.open(
scope,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
Path::dot(),
dir_server,
);
fasync::unblock(|| {
let dir_zxio =
Zxio::create(dir_client.into_channel().into_handle()).expect("create failed");
let foo_zxio = dir_zxio
.open(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, "foo")
.expect("open failed");
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::COLLAPSE_RANGE),
Err(zx::Status::NOT_SUPPORTED)
);
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::KEEP_SIZE),
Err(zx::Status::NOT_SUPPORTED)
);
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::INSERT_RANGE),
Err(zx::Status::NOT_SUPPORTED)
);
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::PUNCH_HOLE),
Err(zx::Status::NOT_SUPPORTED)
);
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::UNSHARE_RANGE),
Err(zx::Status::NOT_SUPPORTED)
);
assert_matches!(
foo_zxio.allocate(0, 10, syncio::AllocateMode::ZERO_RANGE),
Err(zx::Status::NOT_SUPPORTED)
);
})
.await;
}
#[fuchsia::test]
async fn test_get_set_attributes_node() {
let fixture = TestFixture::new().await;
let (dir_client, dir_server) = zx::Channel::create();
fixture
.root()
.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, dir_server.into())
.expect("clone failed");
fasync::unblock(|| {
let dir_zxio = Zxio::create(dir_client.into_handle()).expect("create failed");
dir_zxio
.open_node(".", fio::NodeProtocolFlags::MUST_BE_DIRECTORY, None)
.expect("open_node failed");
// Create a file.
let test_file = dir_zxio
.open2(
"test_file",
OpenOptions {
mode: fio::OpenMode::AlwaysCreate,
..OpenOptions::file(fio::FileProtocolFlags::empty())
},
None,
)
.expect("open2 failed");
let attr = zxio_node_attributes_t {
gid: 111,
access_time: 222,
modification_time: 333,
has: zxio_node_attr_has_t {
gid: true,
access_time: true,
modification_time: true,
..Default::default()
},
..Default::default()
};
test_file.attr_set(&attr).expect("attr_set failed");
let query = zxio_node_attr_has_t {
gid: true,
access_time: true,
modification_time: true,
..Default::default()
};
let attributes = test_file.attr_get(query).expect("attr_get failed");
assert_eq!(attributes.gid, 111);
assert_eq!(attributes.access_time, 222);
assert_eq!(attributes.modification_time, 333);
})
.await;
fixture.close().await;
}