blob: ca6642043833c3e42d04638d142ebba4a9b5c6cc [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::mm::vmo::round_up_to_system_page_size;
use crate::mm::{ProtectionFlags, PAGE_SIZE, VMEX_RESOURCE};
use crate::signals::{send_standard_signal, SignalInfo};
use crate::task::CurrentTask;
use crate::vfs::buffers::{InputBuffer, OutputBuffer};
use crate::vfs::{
anon_fs, fs_node_impl_not_dir, fs_node_impl_xattr_delegate, AppendLockGuard, DirEntry,
FallocMode, FileHandle, FileObject, FileOps, FsNode, FsNodeInfo, FsNodeOps, FsString,
MemoryXattrStorage, MountInfo, NamespaceNode, MAX_LFS_FILESIZE,
};
use fidl::HandleBased;
use fuchsia_zircon as zx;
use starnix_logging::{impossible_error, track_stub};
use starnix_sync::{DeviceOpen, FileOpsCore, LockBefore, Locked};
use starnix_uapi::errors::Errno;
use starnix_uapi::file_mode::mode;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::resource_limits::Resource;
use starnix_uapi::seal_flags::SealFlags;
use starnix_uapi::signals::SIGXFSZ;
use starnix_uapi::{errno, error};
use std::sync::Arc;
pub struct VmoFileNode {
/// The memory that backs this file.
vmo: Arc<zx::Vmo>,
xattrs: MemoryXattrStorage,
}
impl VmoFileNode {
/// Create a new writable file node based on a blank VMO.
pub fn new() -> Result<Self, Errno> {
let vmo =
zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0).map_err(|_| errno!(ENOMEM))?;
Ok(Self { vmo: Arc::new(vmo), xattrs: MemoryXattrStorage::default() })
}
/// Create a new file node based on an existing VMO.
/// Attempts to open the file for writing will fail unless [`vmo`] has both
/// the `WRITE` and `RESIZE` rights.
pub fn from_vmo(vmo: Arc<zx::Vmo>) -> Self {
Self { vmo, xattrs: MemoryXattrStorage::default() }
}
}
impl FsNodeOps for VmoFileNode {
fs_node_impl_not_dir!();
fs_node_impl_xattr_delegate!(self, self.xattrs);
fn initial_info(&self, info: &mut FsNodeInfo) {
if let Some(size) = self.vmo.get_content_size().ok() {
info.size = size as usize;
}
}
fn create_file_ops(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
node: &FsNode,
_current_task: &CurrentTask,
flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
if flags.contains(OpenFlags::TRUNC) {
// Truncating to zero length must pass the shrink seal check.
node.write_guard_state.lock().check_no_seal(SealFlags::SHRINK)?;
}
// Produce a VMO handle with rights reduced to those requested in |flags|.
// TODO(b/319240806): Accumulate required rights, rather than starting from `DEFAULT`.
let mut desired_rights = zx::Rights::VMO_DEFAULT | zx::Rights::RESIZE;
if !flags.can_read() {
desired_rights.remove(zx::Rights::READ);
}
if !flags.can_write() {
desired_rights.remove(zx::Rights::WRITE | zx::Rights::RESIZE);
}
let scoped_vmo =
Arc::new(self.vmo.duplicate_handle(desired_rights).map_err(|_e| errno!(EIO))?);
let file_object = VmoFileObject::new(scoped_vmo);
Ok(Box::new(file_object))
}
fn truncate(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_guard: &AppendLockGuard<'_>,
node: &FsNode,
_current_task: &CurrentTask,
length: u64,
) -> Result<(), Errno> {
let length = length as usize;
node.update_info(|info| {
if info.size == length {
// The file size remains unaffected.
return Ok(());
}
// We must hold the lock till the end of the operation to guarantee that
// there is no change to the seals.
let state = node.write_guard_state.lock();
if info.size > length {
// A decrease in file size must pass the shrink seal check.
state.check_no_seal(SealFlags::SHRINK)?;
} else {
// An increase in file size must pass the grow seal check.
state.check_no_seal(SealFlags::GROW)?;
}
let vmo_size = update_vmo_file_size(&self.vmo, info, length)?;
info.size = length;
// Zero unused parts of the VMO.
if vmo_size > length {
self.vmo
.op_range(zx::VmoOp::ZERO, length as u64, (vmo_size - length) as u64)
.map_err(impossible_error)?;
}
Ok(())
})
}
fn allocate(
&self,
_locked: &mut Locked<'_, FileOpsCore>,
_guard: &AppendLockGuard<'_>,
node: &FsNode,
_current_task: &CurrentTask,
mode: FallocMode,
offset: u64,
length: u64,
) -> Result<(), Errno> {
match mode {
FallocMode::PunchHole => {
// Lock `info()` before acquiring the `write_guard_state` lock to ensure consistent
// lock ordering.
let info = node.info();
// Check write seal. Hold the lock to ensure seals don't change.
let state = node.write_guard_state.lock();
state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
let mut end = offset.checked_add(length).ok_or_else(|| errno!(EINVAL))? as usize;
let vmo_size = info.blksize * info.blocks;
if offset as usize >= vmo_size {
return Ok(());
}
// If punching hole at the end of the file then zero all the
// way to the end of the VMO to avoid keeping any pages for the tail.
if end >= info.size {
end = vmo_size;
}
self.vmo
.op_range(zx::VmoOp::ZERO, offset, end as u64 - offset)
.map_err(impossible_error)?;
Ok(())
}
FallocMode::Allocate { keep_size } => {
node.update_info(|info| {
let new_size = (offset + length) as usize;
if new_size > info.size {
// Check GROW seal (even with `keep_size=true`). Hold the lock to ensure
// seals don't change.
let state = node.write_guard_state.lock();
state.check_no_seal(SealFlags::GROW)?;
update_vmo_file_size(&self.vmo, info, new_size)?;
if !keep_size {
info.size = new_size;
}
}
Ok(())
})
}
_ => error!(EOPNOTSUPP),
}
}
}
pub struct VmoFileObject {
pub vmo: Arc<zx::Vmo>,
}
impl VmoFileObject {
/// Create a file object based on a VMO.
pub fn new(vmo: Arc<zx::Vmo>) -> Self {
VmoFileObject { vmo }
}
}
impl VmoFileObject {
pub fn read(
vmo: &zx::Vmo,
file: &FileObject,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
let actual = {
let info = file.node().info();
let file_length = info.size;
let want_read = data.available();
if offset < file_length {
let to_read =
if file_length < offset + want_read { file_length - offset } else { want_read };
let buf =
vmo.read_to_vec(offset as u64, to_read as u64).map_err(|_| errno!(EIO))?;
drop(info);
data.write_all(&buf[..])?;
to_read
} else {
0
}
};
Ok(actual)
}
pub fn write(
vmo: &zx::Vmo,
file: &FileObject,
current_task: &CurrentTask,
offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
let mut want_write = data.available();
let buf = data.peek_all()?;
file.node().update_info(|info| {
let mut write_end = offset + want_write;
let mut update_content_size = false;
// We must hold the lock till the end of the operation to guarantee that
// there is no change to the seals.
let state = file.name.entry.node.write_guard_state.lock();
// Non-zero writes must pass the write seal check.
if want_write != 0 {
state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
}
// Writing past the file size
if write_end > info.size {
// The grow seal check failed.
if let Err(e) = state.check_no_seal(SealFlags::GROW) {
if offset >= info.size {
// Write starts outside the file.
// Forbid because nothing can be written without growing.
return Err(e);
} else if info.size == info.storage_size() {
// Write starts inside file and EOF page does not need to grow.
// End write at EOF.
write_end = info.size;
want_write = write_end - offset;
} else {
// Write starts inside file and EOF page needs to grow.
let eof_page_start = info.storage_size() - (*PAGE_SIZE as usize);
if offset >= eof_page_start {
// Write starts in EOF page.
// Forbid because EOF page cannot grow.
return Err(e);
}
// End write at page before EOF.
write_end = eof_page_start;
want_write = write_end - offset;
}
}
}
// Check against the FSIZE limt
let fsize_limit = current_task.thread_group.get_rlimit(Resource::FSIZE) as usize;
if write_end > fsize_limit {
if offset >= fsize_limit {
// Write starts beyond the FSIZE limt.
send_standard_signal(current_task, SignalInfo::default(SIGXFSZ));
return error!(EFBIG);
}
// End write at FSIZE limit.
write_end = fsize_limit;
want_write = write_end - offset;
}
if write_end > info.size {
if write_end > info.storage_size() {
update_vmo_file_size(vmo, info, write_end)?;
}
update_content_size = true;
}
vmo.write(&buf[..want_write], offset as u64).map_err(|_| errno!(EIO))?;
if update_content_size {
info.size = write_end;
}
data.advance(want_write)?;
Ok(want_write)
})
}
pub fn get_vmo(
vmo: &Arc<zx::Vmo>,
_file: &FileObject,
_current_task: &CurrentTask,
prot: ProtectionFlags,
) -> Result<Arc<zx::Vmo>, Errno> {
let mut vmo = Arc::clone(vmo);
if prot.contains(ProtectionFlags::EXEC) {
vmo = Arc::new(
vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
.map_err(impossible_error)?
.replace_as_executable(&VMEX_RESOURCE)
.map_err(impossible_error)?,
);
}
Ok(vmo)
}
}
#[macro_export]
macro_rules! fileops_impl_vmo {
($self:ident, $vmo:expr) => {
$crate::fileops_impl_seekable!();
fn read(
&$self,
_locked: &mut starnix_sync::Locked<'_, starnix_sync::FileOpsCore>,
file: &$crate::vfs::FileObject,
_current_task: &$crate::task::CurrentTask,
offset: usize,
data: &mut dyn $crate::vfs::buffers::OutputBuffer,
) -> Result<usize, starnix_uapi::errors::Errno> {
$crate::vfs::VmoFileObject::read($vmo, file, offset, data)
}
fn write(
&$self,
_locked: &mut starnix_sync::Locked<'_, starnix_sync::WriteOps>,
file: &$crate::vfs::FileObject,
current_task: &$crate::task::CurrentTask,
offset: usize,
data: &mut dyn $crate::vfs::buffers::InputBuffer,
) -> Result<usize, starnix_uapi::errors::Errno> {
$crate::vfs::VmoFileObject::write($vmo, file, current_task, offset, data)
}
fn get_vmo(
&$self,
file: &FileObject,
current_task: &CurrentTask,
_length: Option<usize>,
prot: $crate::mm::ProtectionFlags,
) -> Result<Arc<fuchsia_zircon::Vmo>, starnix_uapi::errors::Errno> {
$crate::vfs::VmoFileObject::get_vmo($vmo, file, current_task, prot)
}
}
}
pub(crate) use fileops_impl_vmo;
impl FileOps for VmoFileObject {
fileops_impl_vmo!(self, &self.vmo);
fn readahead(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_length: usize,
) -> Result<(), Errno> {
track_stub!(TODO("https://fxbug.dev/42082608"), "paged VMO readahead");
Ok(())
}
}
pub fn new_memfd<L>(
locked: &mut Locked<'_, L>,
current_task: &CurrentTask,
mut name: FsString,
seals: SealFlags,
flags: OpenFlags,
) -> Result<FileHandle, Errno>
where
L: LockBefore<FileOpsCore>,
L: LockBefore<DeviceOpen>,
{
let fs = anon_fs(current_task.kernel());
let node = fs.create_node(
current_task,
VmoFileNode::new()?,
FsNodeInfo::new_factory(mode!(IFREG, 0o600), current_task.as_fscred()),
);
node.write_guard_state.lock().enable_sealing(seals);
let ops = node.open(locked, current_task, &MountInfo::detached(), flags, false)?;
// In /proc/[pid]/fd, the target of this memfd's symbolic link is "/memfd:[name]".
let mut local_name = FsString::from("/memfd:");
local_name.append(&mut name);
let name = NamespaceNode::new_anonymous(DirEntry::new(node, None, local_name));
FileObject::new(ops, name, flags)
}
/// Sets VMO size to `min_size` rounded to whole pages. Returns the new size of the VMO in bytes.
fn update_vmo_file_size(
vmo: &zx::Vmo,
node_info: &mut FsNodeInfo,
requested_size: usize,
) -> Result<usize, Errno> {
assert!(requested_size <= MAX_LFS_FILESIZE);
let size = round_up_to_system_page_size(requested_size)?;
vmo.set_size(size as u64).map_err(|status| match status {
zx::Status::NO_MEMORY => errno!(ENOMEM),
zx::Status::OUT_OF_RANGE => errno!(ENOMEM),
_ => impossible_error(status),
})?;
node_info.blocks = size / node_info.blksize;
Ok(size)
}