blob: 226b75b380789002ec5c1e64b5054fe45c065923 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
object_handle::ObjectHandle,
object_store::{
filesystem::SyncOptions, round_down, transaction::Options, StoreObjectHandle, Timestamp,
},
server::{directory::FxDirectory, errors::map_to_status, node::FxNode, volume::FxVolume},
},
anyhow::Error,
async_trait::async_trait,
fidl::endpoints::ServerEnd,
fidl_fuchsia_io::{self as fio, NodeAttributes, NodeMarker},
fidl_fuchsia_mem::Buffer,
fuchsia_zircon::Status,
std::{
any::Any,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
},
storage_device::buffer::MutableBufferRef,
vfs::{
common::send_on_open_with_error,
directory::entry::{DirectoryEntry, EntryInfo},
execution_scope::ExecutionScope,
file::{
connection::{self, io1::FileConnection},
File, SharingMode,
},
filesystem::Filesystem,
path::Path,
},
};
/// FxFile represents an open connection to a file.
pub struct FxFile {
handle: StoreObjectHandle<FxVolume>,
open_count: AtomicUsize,
}
impl FxFile {
pub fn new(handle: StoreObjectHandle<FxVolume>) -> Self {
Self { handle, open_count: AtomicUsize::new(0) }
}
pub fn open_count(&self) -> usize {
self.open_count.load(Ordering::Relaxed)
}
async fn write_or_append(
&self,
offset: Option<u64>,
content: &[u8],
) -> Result<(u64, u64), Error> {
// We must create the transaction first so that we lock the size in the case that this is
// append.
let mut transaction = self.handle.new_transaction().await?;
let offset = offset.unwrap_or_else(|| self.handle.get_size());
let start = round_down(offset, self.handle.block_size());
let align = (offset - start) as usize;
let mut buf = self.handle.allocate_buffer(align + content.len());
buf.as_mut_slice()[align..].copy_from_slice(content);
self.handle.txn_write(&mut transaction, offset, buf.subslice(align..)).await?;
transaction.commit().await;
Ok((content.len() as u64, offset + content.len() as u64))
}
}
impl Drop for FxFile {
fn drop(&mut self) {
self.handle.owner().cache().remove(self.object_id());
}
}
impl FxNode for FxFile {
fn object_id(&self) -> u64 {
self.handle.object_id()
}
fn parent(&self) -> Option<Arc<FxDirectory>> {
unreachable!(); // Add a parent back-reference if needed.
}
fn set_parent(&self, _parent: Arc<FxDirectory>) {
// NOP
}
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static> {
self
}
fn try_into_directory_entry(self: Arc<Self>) -> Option<Arc<dyn DirectoryEntry>> {
Some(self)
}
}
impl DirectoryEntry for FxFile {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: u32,
mode: u32,
path: Path,
server_end: ServerEnd<NodeMarker>,
) {
if !path.is_empty() {
send_on_open_with_error(flags, server_end, Status::NOT_FILE);
return;
}
// Since close decrements open_count, we need to increment it here as we create OpenFile
// since it will call close when dropped.
self.open_count.fetch_add(1, Ordering::Relaxed);
FileConnection::<FxFile>::create_connection(
// Note readable/writable do not override what's set in flags, they merely tell the
// FileConnection that it's valid to open the file readable/writable.
scope.clone(),
connection::util::OpenFile::new(self, scope),
flags,
mode,
server_end,
/*readable=*/ true,
/*writable=*/ true,
);
}
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(self.object_id(), fio::DIRENT_TYPE_FILE)
}
fn can_hardlink(&self) -> bool {
true
}
}
#[async_trait]
impl File for FxFile {
async fn open(&self, _flags: u32) -> Result<(), Status> {
Ok(())
}
async fn read_at(&self, offset: u64, buffer: MutableBufferRef<'_>) -> Result<u64, Status> {
let bytes_read = self.handle.read(offset, buffer).await.map_err(map_to_status)?;
Ok(bytes_read as u64)
}
async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
self.write_or_append(Some(offset), content)
.await
.map(|(done, _)| done)
.map_err(map_to_status)
}
async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
self.write_or_append(None, content).await.map_err(map_to_status)
}
async fn truncate(&self, length: u64) -> Result<(), Status> {
// It's safe to skip the space checks even if we're growing the file here because it won't
// actually use any data on disk (either for data or metadata).
let mut transaction = self
.handle
.new_transaction_with_options(Options { skip_space_checks: true, ..Default::default() })
.await
.map_err(map_to_status)?;
self.handle.truncate(&mut transaction, length).await.map_err(map_to_status)?;
transaction.commit().await;
Ok(())
}
async fn get_buffer(&self, _mode: SharingMode, _flags: u32) -> Result<Option<Buffer>, Status> {
log::error!("get_buffer not implemented");
Err(Status::NOT_SUPPORTED)
}
async fn get_size(&self) -> Result<u64, Status> {
Ok(self.handle.get_size())
}
async fn get_attrs(&self) -> Result<NodeAttributes, Status> {
let props = self.handle.get_properties().await.map_err(map_to_status)?;
// TODO(jfsulliv): This assumes that we always get the data attribute at index 0 of
// |attribute_sizes|.
Ok(NodeAttributes {
mode: 0u32, // TODO(jfsulliv): Mode bits
id: self.handle.object_id(),
content_size: props.data_attribute_size,
storage_size: props.allocated_size,
link_count: props.refs,
creation_time: props.creation_time.as_nanos(),
modification_time: props.modification_time.as_nanos(),
})
}
async fn set_attrs(
&self,
flags: u32,
attrs: NodeAttributes,
may_defer: bool,
) -> Result<(), Status> {
let crtime = if flags & fidl_fuchsia_io::NODE_ATTRIBUTE_FLAG_CREATION_TIME > 0 {
Some(Timestamp::from_nanos(attrs.creation_time))
} else {
None
};
let mtime = if flags & fidl_fuchsia_io::NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME > 0 {
Some(Timestamp::from_nanos(attrs.modification_time))
} else {
None
};
if let (None, None) = (crtime.as_ref(), mtime.as_ref()) {
return Ok(());
}
let mut transaction = if may_defer {
None
} else {
Some(
self.handle
.new_transaction_with_options(Options {
skip_space_checks: true,
..Default::default()
})
.await
.map_err(map_to_status)?,
)
};
self.handle
.update_timestamps(transaction.as_mut(), crtime, mtime)
.await
.map_err(map_to_status)?;
if let Some(t) = transaction {
t.commit().await;
}
Ok(())
}
async fn close(&self) -> Result<(), Status> {
assert!(self.open_count.fetch_sub(1, Ordering::Relaxed) > 0);
Ok(())
}
async fn sync(&self) -> Result<(), Status> {
// TODO(csuter): at the moment, this doesn't send a flush to the device, which doesn't
// match minfs.
self.handle.store().filesystem().sync(SyncOptions::default()).await.map_err(map_to_status)
}
fn get_filesystem(&self) -> &dyn Filesystem {
self.handle.owner().as_ref()
}
}
#[cfg(test)]
mod tests {
use {
crate::{
object_handle::INVALID_OBJECT_ID,
server::testing::{close_file_checked, open_file_checked, TestFixture},
},
fidl_fuchsia_io::{
self as fio, SeekOrigin, MODE_TYPE_FILE, OPEN_FLAG_APPEND, OPEN_FLAG_CREATE,
OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_async as fasync,
fuchsia_zircon::Status,
io_util::{read_file_bytes, write_file_bytes},
storage_device::{fake_device::FakeDevice, DeviceHolder},
};
#[fasync::run_singlethreaded(test)]
async fn test_empty_file() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file =
open_file_checked(&root, OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE, MODE_TYPE_FILE, "foo")
.await;
let (status, buf) = file.read(fio::MAX_BUF).await.expect("FIDL call failed");
Status::ok(status).expect("read failed");
assert!(buf.is_empty());
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
// TODO(jfsulliv): Check mode
assert_ne!(attrs.id, INVALID_OBJECT_ID);
assert_eq!(attrs.content_size, 0u64);
assert_eq!(attrs.storage_size, 0u64);
assert_eq!(attrs.link_count, 1u64);
assert_ne!(attrs.creation_time, 0u64);
assert_ne!(attrs.modification_time, 0u64);
assert_eq!(attrs.creation_time, attrs.modification_time);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_set_attrs() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let (status, initial_attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
let crtime = initial_attrs.creation_time ^ 1u64;
let mtime = initial_attrs.modification_time ^ 1u64;
let mut attrs = initial_attrs.clone();
attrs.creation_time = crtime;
attrs.modification_time = mtime;
let status = file
.set_attr(fidl_fuchsia_io::NODE_ATTRIBUTE_FLAG_CREATION_TIME, &mut attrs)
.await
.expect("FIDL call failed");
Status::ok(status).expect("set_attr failed");
let mut expected_attrs = initial_attrs.clone();
expected_attrs.creation_time = crtime; // Only crtime is updated so far.
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
assert_eq!(expected_attrs, attrs);
let mut attrs = initial_attrs.clone();
attrs.creation_time = 0u64; // This should be ignored since we don't set the flag.
attrs.modification_time = mtime;
let status = file
.set_attr(fidl_fuchsia_io::NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME, &mut attrs)
.await
.expect("FIDL call failed");
Status::ok(status).expect("set_attr failed");
let mut expected_attrs = initial_attrs.clone();
expected_attrs.creation_time = crtime;
expected_attrs.modification_time = mtime;
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
assert_eq!(expected_attrs, attrs);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_write_read() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let inputs = vec!["hello, ", "world!"];
let expected_output = "hello, world!";
for input in inputs {
let (status, bytes_written) = file.write(input.as_bytes()).await.expect("write failed");
Status::ok(status).expect("File write was successful");
assert_eq!(bytes_written as usize, input.as_bytes().len());
}
let (status, buf) = file.read_at(fio::MAX_BUF, 0).await.expect("read_at failed");
Status::ok(status).expect("File read was successful");
assert_eq!(buf.len(), expected_output.as_bytes().len());
assert!(buf.iter().eq(expected_output.as_bytes().iter()));
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
assert_eq!(attrs.content_size, expected_output.as_bytes().len() as u64);
assert_eq!(attrs.storage_size, 512u64);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_writes_persist() {
let mut device = DeviceHolder::new(FakeDevice::new(8192, 512));
for i in 0..2 {
let fixture = TestFixture::open(device, /*format=*/ i == 0).await;
let root = fixture.root();
let flags = if i == 0 {
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE
} else {
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE
};
let file = open_file_checked(&root, flags, MODE_TYPE_FILE, "foo").await;
if i == 0 {
let (status, _) =
file.write(&vec![0xaa as u8; 8192]).await.expect("FIDL call failed");
Status::ok(status).expect("File write was successful");
} else {
let (status, buf) = file.read(8192).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert_eq!(buf, vec![0xaa as u8; 8192]);
}
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
assert_eq!(attrs.content_size, 8192u64);
assert_eq!(attrs.storage_size, 8192u64);
close_file_checked(file).await;
device = fixture.close().await;
}
}
#[fasync::run_singlethreaded(test)]
async fn test_append() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let inputs = vec!["hello, ", "world!"];
let expected_output = "hello, world!";
for input in inputs {
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE | OPEN_FLAG_APPEND,
MODE_TYPE_FILE,
"foo",
)
.await;
let (status, bytes_written) =
file.write(input.as_bytes()).await.expect("FIDL call failed");
Status::ok(status).expect("File write was successful");
assert_eq!(bytes_written as usize, input.as_bytes().len());
}
let file = open_file_checked(&root, OPEN_RIGHT_READABLE, MODE_TYPE_FILE, "foo").await;
let (status, buf) = file.read_at(fio::MAX_BUF, 0).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert_eq!(buf.len(), expected_output.as_bytes().len());
assert!(buf.iter().eq(expected_output.as_bytes().iter()));
let (status, attrs) = file.get_attr().await.expect("FIDL call failed");
Status::ok(status).expect("get_attr failed");
assert_eq!(attrs.content_size, expected_output.as_bytes().len() as u64);
assert_eq!(attrs.storage_size, 512u64);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_seek() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let input = "hello, world!";
let (status, _bytes_written) =
file.write(input.as_bytes()).await.expect("FIDL call failed");
Status::ok(status).expect("File write was successful");
{
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("FIDL call failed");
assert_eq!(offset, 0);
Status::ok(status).expect("seek was successful");
let (status, buf) = file.read(5).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert!(buf.iter().eq("hello".as_bytes().into_iter()));
}
{
let (status, offset) =
file.seek(2, SeekOrigin::Current).await.expect("FIDL call failed");
assert_eq!(offset, 7);
Status::ok(status).expect("seek was successful");
let (status, buf) = file.read(5).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert!(buf.iter().eq("world".as_bytes().into_iter()));
}
{
let (status, offset) =
file.seek(-5, SeekOrigin::Current).await.expect("FIDL call failed");
assert_eq!(offset, 7);
Status::ok(status).expect("seek was successful");
let (status, buf) = file.read(5).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert!(buf.iter().eq("world".as_bytes().into_iter()));
}
{
let (status, offset) = file.seek(-1, SeekOrigin::End).await.expect("FIDL call failed");
assert_eq!(offset, 12);
Status::ok(status).expect("seek was successful");
let (status, buf) = file.read(1).await.expect("FIDL call failed");
Status::ok(status).expect("File read was successful");
assert!(buf.iter().eq("!".as_bytes().into_iter()));
}
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_truncate_extend() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let input = "hello, world!";
let len: usize = 16 * 1024;
let (status, _bytes_written) =
file.write(input.as_bytes()).await.expect("FIDL call failed");
Status::ok(status).expect("File write was successful");
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("FIDL call failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let status = file.truncate(len as u64).await.expect("FIDL call failed");
Status::ok(status).expect("File truncate was successful");
let mut expected_buf = vec![0 as u8; len];
expected_buf[..input.as_bytes().len()].copy_from_slice(input.as_bytes());
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), len);
assert_eq!(buf, expected_buf);
// Write something at the end of the gap.
expected_buf[len - 1..].copy_from_slice("a".as_bytes());
let (status, _bytes_written) =
file.write_at("a".as_bytes(), (len - 1) as u64).await.expect("FIDL call failed");
Status::ok(status).expect("File write was successful");
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("FIDL call failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), len);
assert_eq!(buf, expected_buf);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_truncate_shrink() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let len: usize = 2 * 1024;
let input = {
let mut v = vec![0 as u8; len];
for i in 0..v.len() {
v[i] = ('a' as u8) + (i % 13) as u8;
}
v
};
let short_len: usize = 513;
write_file_bytes(&file, &input).await.expect("File write was successful");
let status = file.truncate(short_len as u64).await.expect("truncate failed");
Status::ok(status).expect("File truncate was successful");
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("FIDL call failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), short_len);
assert_eq!(buf, input[..short_len]);
// Re-truncate to the original length and verify the data's zeroed.
let status = file.truncate(len as u64).await.expect("FIDL call failed");
Status::ok(status).expect("File truncate was successful");
let expected_buf = {
let mut v = vec![0 as u8; len];
v[..short_len].copy_from_slice(&input[..short_len]);
v
};
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("seek failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), len);
assert_eq!(buf, expected_buf);
close_file_checked(file).await;
fixture.close().await;
}
#[fasync::run_singlethreaded(test)]
async fn test_truncate_shrink_repeated() {
let fixture = TestFixture::new().await;
let root = fixture.root();
let file = open_file_checked(
&root,
OPEN_FLAG_CREATE | OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_FILE,
"foo",
)
.await;
let orig_len: usize = 4 * 1024;
let mut len = orig_len;
let input = {
let mut v = vec![0 as u8; len];
for i in 0..v.len() {
v[i] = ('a' as u8) + (i % 13) as u8;
}
v
};
let short_len: usize = 513;
write_file_bytes(&file, &input).await.expect("File write was successful");
while len > short_len {
let to_truncate = std::cmp::min(len - short_len, 512);
len -= to_truncate;
let status = file.truncate(len as u64).await.expect("FIDL call failed");
Status::ok(status).expect("File truncate was successful");
len -= to_truncate;
}
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("truncate failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), short_len);
assert_eq!(buf, input[..short_len]);
// Re-truncate to the original length and verify the data's zeroed.
let status = file.truncate(orig_len as u64).await.expect("FIDL call failed");
Status::ok(status).expect("File truncate was successful");
let expected_buf = {
let mut v = vec![0 as u8; orig_len];
v[..short_len].copy_from_slice(&input[..short_len]);
v
};
let (status, offset) = file.seek(0, SeekOrigin::Start).await.expect("seek failed");
assert_eq!(offset, 0);
Status::ok(status).expect("Seek was successful");
let buf = read_file_bytes(&file).await.expect("File read was successful");
assert_eq!(buf.len(), orig_len);
assert_eq!(buf, expected_buf);
close_file_checked(file).await;
fixture.close().await;
}
}