blob: 37b5882d42e6be7b01132f6c38cc04ca3a52ffb3 [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 std::sync::Arc;
use std::sync::Weak;
use super::directory_file::MemoryDirectoryFile;
use super::*;
use crate::lock::{Mutex, MutexGuard};
use crate::task::Kernel;
use crate::types::*;
pub struct TmpFs(());
impl FileSystemOps for Arc<TmpFs> {
fn rename(
&self,
_fs: &FileSystem,
old_parent: &FsNodeHandle,
_old_name: &FsStr,
new_parent: &FsNodeHandle,
_new_name: &FsStr,
renamed: &FsNodeHandle,
replaced: Option<&FsNodeHandle>,
) -> Result<(), Errno> {
fn child_count(node: &FsNodeHandle) -> MutexGuard<'_, u32> {
// The following cast are safe, unless something is seriously wrong:
// - The filesystem should not be asked to rename node that it doesn't handle.
// - Parents in a rename operation need to be directories.
// - TmpfsDirectory is the ops for directories in this filesystem.
node.downcast_ops::<TmpfsDirectory>().unwrap().child_count.lock()
}
if let Some(replaced) = replaced {
if replaced.is_dir() {
if !renamed.is_dir() {
return error!(EISDIR);
}
// Ensures that replaces is empty.
if *child_count(replaced) != 0 {
return error!(ENOTEMPTY);
}
}
}
*child_count(old_parent) -= 1;
*child_count(new_parent) += 1;
if renamed.is_dir() {
old_parent.info_write().link_count -= 1;
new_parent.info_write().link_count += 1;
}
// Fix the wrong changes to new_parent due to the fact that the target element has
// been replaced instead of added.
if let Some(replaced) = replaced {
if replaced.is_dir() {
new_parent.info_write().link_count -= 1;
}
*child_count(new_parent) -= 1;
}
Ok(())
}
}
impl TmpFs {
pub fn new(kernel: &Arc<Kernel>) -> FileSystemHandle {
let fs = FileSystem::new_with_permanent_entries(Arc::new(TmpFs(())));
fs.set_root(TmpfsDirectory::new(Arc::downgrade(kernel)));
fs
}
}
pub struct TmpfsDirectory {
xattrs: MemoryXattrStorage,
child_count: Mutex<u32>,
kernel: Weak<Kernel>,
}
impl TmpfsDirectory {
pub fn new(kernel: Weak<Kernel>) -> Self {
Self { xattrs: MemoryXattrStorage::default(), child_count: Mutex::new(0), kernel: kernel }
}
fn create_node(
&self,
parent: &FsNode,
node: Box<dyn FsNodeOps>,
mode: FileMode,
) -> Result<FsNodeHandle, Errno> {
let node = parent.fs().create_node(node, mode);
if self.kernel.upgrade().expect("Kernel should exist.").selinux_enabled() {
let _ = node.set_xattr(b"security.selinux", b"u:object_r:tmpfs:s0", XattrOp::Create);
}
Ok(node)
}
}
impl FsNodeOps for TmpfsDirectory {
fs_node_impl_xattr_delegate!(self, self.xattrs);
fn open(&self, _node: &FsNode, _flags: OpenFlags) -> Result<Box<dyn FileOps>, Errno> {
Ok(Box::new(MemoryDirectoryFile::new()))
}
fn lookup(&self, _node: &FsNode, _name: &FsStr) -> Result<FsNodeHandle, Errno> {
error!(ENOENT)
}
fn mkdir(&self, node: &FsNode, _name: &FsStr) -> Result<FsNodeHandle, Errno> {
node.info_write().link_count += 1;
*self.child_count.lock() += 1;
self.create_node(node, Box::new(TmpfsDirectory::new(self.kernel.clone())), FileMode::IFDIR)
}
fn mknod(&self, node: &FsNode, _name: &FsStr, mode: FileMode) -> Result<FsNodeHandle, Errno> {
let ops: Box<dyn FsNodeOps> = match mode.fmt() {
FileMode::IFREG => Box::new(VmoFileNode::new()?),
FileMode::IFIFO => Box::new(TmpfsSpecialNode::new()),
FileMode::IFBLK => Box::new(TmpfsSpecialNode::new()),
FileMode::IFCHR => Box::new(TmpfsSpecialNode::new()),
FileMode::IFSOCK => Box::new(TmpfsSpecialNode::new()),
_ => return error!(EACCES),
};
*self.child_count.lock() += 1;
self.create_node(node, ops, mode)
}
fn create_symlink(
&self,
node: &FsNode,
_name: &FsStr,
target: &FsStr,
) -> Result<FsNodeHandle, Errno> {
*self.child_count.lock() += 1;
self.create_node(node, Box::new(SymlinkNode::new(target)), FileMode::IFLNK)
}
fn link(&self, _node: &FsNode, _name: &FsStr, child: &FsNodeHandle) -> Result<(), Errno> {
child.info_write().link_count += 1;
*self.child_count.lock() += 1;
Ok(())
}
fn unlink(&self, node: &FsNode, _name: &FsStr, child: &FsNodeHandle) -> Result<(), Errno> {
if child.is_dir() {
node.info_write().link_count -= 1;
}
child.info_write().link_count -= 1;
*self.child_count.lock() -= 1;
Ok(())
}
}
struct TmpfsSpecialNode {
xattrs: MemoryXattrStorage,
}
impl TmpfsSpecialNode {
pub fn new() -> Self {
Self { xattrs: MemoryXattrStorage::default() }
}
}
impl FsNodeOps for TmpfsSpecialNode {
fs_node_impl_xattr_delegate!(self, self.xattrs);
fn open(&self, _node: &FsNode, _flags: OpenFlags) -> Result<Box<dyn FileOps>, Errno> {
unreachable!("Special nodes cannot be opened.");
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::mm::*;
use crate::task::*;
use crate::testing::*;
use fuchsia_zircon as zx;
use std::ffi::CString;
use std::sync::Arc;
use zerocopy::AsBytes;
#[::fuchsia::test]
fn test_tmpfs() {
let (kernel, _current_task) = create_kernel_and_task();
let fs = TmpFs::new(&kernel);
let root = fs.root();
let usr = root.create_dir(b"usr").unwrap();
let _etc = root.create_dir(b"etc").unwrap();
let _usr_bin = usr.create_dir(b"bin").unwrap();
let mut names = root.copy_child_names();
names.sort();
assert!(names.iter().eq([b"etc", b"usr"].iter()));
}
#[::fuchsia::test]
fn test_write_read() {
let (_kernel, current_task) = create_kernel_and_task();
let test_mem_size = 0x10000;
let test_vmo = Arc::new(zx::Vmo::create(test_mem_size).unwrap());
let path = b"test.bin";
let _file = current_task
.fs
.root
.create_node(path, FileMode::IFREG | FileMode::ALLOW_ALL, DeviceType::NONE)
.unwrap();
let wr_file = current_task.open_file(path, OpenFlags::RDWR).unwrap();
let flags = zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE;
let test_addr = current_task
.mm
.map(
DesiredAddress::Hint(UserAddress::default()),
test_vmo,
0,
test_mem_size as usize,
flags,
MappingOptions::empty(),
None,
)
.unwrap();
let seq_addr = UserAddress::from_ptr(test_addr.ptr() + path.len());
let test_seq = 0..10000u16;
let test_vec = test_seq.collect::<Vec<_>>();
let test_bytes = test_vec.as_slice().as_bytes();
current_task.mm.write_memory(seq_addr, test_bytes).unwrap();
let buf = [UserBuffer { address: seq_addr, length: test_bytes.len() }];
let written = wr_file.write(&current_task, &buf).unwrap();
assert_eq!(written, test_bytes.len());
let mut read_vec = vec![0u8; test_bytes.len()];
current_task.mm.read_memory(seq_addr, read_vec.as_bytes_mut()).unwrap();
assert_eq!(test_bytes, &*read_vec);
}
#[::fuchsia::test]
fn test_read_past_eof() {
let (_kernel, current_task) = create_kernel_and_task();
// Open an empty file
let path = b"test.bin";
let _file = current_task
.fs
.root
.create_node(path, FileMode::IFREG | FileMode::ALLOW_ALL, DeviceType::NONE)
.unwrap();
let rd_file = current_task.open_file(path, OpenFlags::RDONLY).unwrap();
// Verify that attempting to read past the EOF (i.e. at a non-zero offset) returns 0
let test_mem_size = 0x10000;
let test_vmo = Arc::new(zx::Vmo::create(test_mem_size).unwrap());
let flags = zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE;
let test_addr = current_task
.mm
.map(
DesiredAddress::Hint(UserAddress::default()),
test_vmo,
0,
test_mem_size as usize,
flags,
MappingOptions::empty(),
None,
)
.unwrap();
let buf = [UserBuffer { address: test_addr, length: test_mem_size as usize }];
let test_offset = 100;
let result = rd_file.read_at(&current_task, test_offset, &buf).unwrap();
assert_eq!(result, 0);
}
#[::fuchsia::test]
fn test_permissions() {
let (_kernel, current_task) = create_kernel_and_task();
let path = b"test.bin";
let file = current_task
.open_file_at(
FdNumber::AT_FDCWD,
path,
OpenFlags::CREAT | OpenFlags::RDONLY,
FileMode::ALLOW_ALL,
)
.expect("failed to create file");
assert_eq!(0, file.read(&current_task, &[]).expect("failed to read"));
assert!(file.write(&current_task, &[]).is_err());
let file = current_task
.open_file_at(FdNumber::AT_FDCWD, path, OpenFlags::WRONLY, FileMode::ALLOW_ALL)
.expect("failed to open file WRONLY");
assert!(file.read(&current_task, &[]).is_err());
assert_eq!(0, file.write(&current_task, &[]).expect("failed to write"));
let file = current_task
.open_file_at(FdNumber::AT_FDCWD, path, OpenFlags::RDWR, FileMode::ALLOW_ALL)
.expect("failed to open file RDWR");
assert_eq!(0, file.read(&current_task, &[]).expect("failed to read"));
assert_eq!(0, file.write(&current_task, &[]).expect("failed to write"));
}
#[::fuchsia::test]
fn test_persistence() {
let kernel = Arc::new(
Kernel::new(&CString::new("test-kernel").unwrap(), &Vec::new())
.expect("failed to create kernel"),
);
let fs = TmpFs::new(&kernel);
{
let root = fs.root();
let usr = root.create_dir(b"usr").expect("failed to create usr");
root.create_dir(b"etc").expect("failed to create usr/etc");
usr.create_dir(b"bin").expect("failed to create usr/bin");
}
let current_task = Task::create_process_without_parent(
&kernel.clone(),
CString::new("test-task").unwrap(),
FsContext::new(fs),
)
.expect("failed to create first task");
// Take the lock on thread group and task in the correct order to ensure any wrong ordering
// will trigger the tracing-mutex at the right call site.
{
let _l1 = current_task.thread_group.read();
let _l2 = current_task.read();
}
// At this point, all the nodes are dropped.
current_task
.open_file(b"/usr/bin", OpenFlags::RDONLY | OpenFlags::DIRECTORY)
.expect("failed to open /usr/bin");
assert_eq!(
errno!(ENOENT),
current_task.open_file(b"/usr/bin/test.txt", OpenFlags::RDWR).unwrap_err()
);
current_task
.open_file_at(
FdNumber::AT_FDCWD,
b"/usr/bin/test.txt",
OpenFlags::RDWR | OpenFlags::CREAT,
FileMode::ALLOW_ALL,
)
.expect("failed to create test.txt");
let txt = current_task
.open_file(b"/usr/bin/test.txt", OpenFlags::RDWR)
.expect("failed to open test.txt");
let usr_bin = current_task
.open_file(b"/usr/bin", OpenFlags::RDONLY)
.expect("failed to open /usr/bin");
usr_bin
.name
.unlink(b"test.txt", UnlinkKind::NonDirectory)
.expect("failed to unlink test.text");
assert_eq!(
errno!(ENOENT),
current_task.open_file(b"/usr/bin/test.txt", OpenFlags::RDWR).unwrap_err()
);
assert_eq!(
errno!(ENOENT),
usr_bin.name.unlink(b"test.txt", UnlinkKind::NonDirectory).unwrap_err()
);
assert_eq!(0, txt.read(&current_task, &[]).expect("failed to read"));
std::mem::drop(txt);
assert_eq!(
errno!(ENOENT),
current_task.open_file(b"/usr/bin/test.txt", OpenFlags::RDWR).unwrap_err()
);
std::mem::drop(usr_bin);
let usr = current_task.open_file(b"/usr", OpenFlags::RDONLY).expect("failed to open /usr");
assert_eq!(
errno!(ENOENT),
current_task.open_file(b"/usr/foo", OpenFlags::RDONLY).unwrap_err()
);
usr.name.unlink(b"bin", UnlinkKind::Directory).expect("failed to unlink /usr/bin");
}
}