blob: 85d37624baccae324cb9215aee00181394587df5 [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::convert::TryInto;
use std::usize;
use crate::fs::pipe::*;
use crate::fs::*;
use crate::not_implemented;
use crate::syscalls::*;
use crate::task::*;
use crate::types::*;
pub fn sys_read(
ctx: &SyscallContext<'_>,
fd: FdNumber,
address: UserAddress,
length: usize,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
Ok(file.read(&ctx.task, &[UserBuffer { address, length }])?.into())
}
pub fn sys_write(
ctx: &SyscallContext<'_>,
fd: FdNumber,
address: UserAddress,
length: usize,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
Ok(file.write(&ctx.task, &[UserBuffer { address, length }])?.into())
}
pub fn sys_close(ctx: &SyscallContext<'_>, fd: FdNumber) -> Result<SyscallResult, Errno> {
ctx.task.files.close(fd)?;
Ok(SUCCESS)
}
pub fn sys_lseek(
ctx: &SyscallContext<'_>,
fd: FdNumber,
offset: off_t,
whence: u32,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
Ok(file.seek(&ctx.task, offset, SeekOrigin::from_raw(whence)?)?.into())
}
pub fn sys_fcntl(
ctx: &SyscallContext<'_>,
fd: FdNumber,
cmd: u32,
arg: u64,
) -> Result<SyscallResult, Errno> {
match cmd {
F_GETOWN => {
let file = ctx.task.files.get(fd)?;
Ok(file.get_async_owner().into())
}
F_SETOWN => {
if arg > std::i32::MAX as u64 {
// Negative values are process groups.
not_implemented!("fcntl(F_SETOWN) does not support process groups");
return Err(EINVAL);
}
let file = ctx.task.files.get(fd)?;
let task = ctx.task.get_task(arg.try_into().map_err(|_| EINVAL)?);
file.set_async_owner(task.map_or(0, |task| task.id));
Ok(SUCCESS)
}
F_GETFD => Ok(ctx.task.files.get_fd_flags(fd)?.bits().into()),
F_SETFD => {
ctx.task.files.set_fd_flags(fd, FdFlags::from_bits_truncate(arg as u32))?;
Ok(SUCCESS)
}
F_GETFL => {
let file = ctx.task.files.get(fd)?;
Ok(file.flags().bits().into())
}
F_SETFL => {
// TODO: Add O_ASYNC once we have a decl for it.
let settable_flags =
OpenFlags::APPEND | OpenFlags::DIRECT | OpenFlags::NOATIME | OpenFlags::NONBLOCK;
let requested_flags =
OpenFlags::from_bits_truncate((arg as u32) & settable_flags.bits());
let file = ctx.task.files.get(fd)?;
file.update_file_flags(requested_flags, settable_flags);
Ok(SUCCESS)
}
F_GETPIPE_SZ | F_SETPIPE_SZ => {
let file = ctx.task.files.get(fd)?;
file.fcntl(&ctx.task, cmd, arg)
}
_ => {
not_implemented!("fcntl command {} not implemented", cmd);
Err(ENOSYS)
}
}
}
pub fn sys_pread64(
ctx: &SyscallContext<'_>,
fd: FdNumber,
address: UserAddress,
length: usize,
offset: off_t,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
let offset = offset.try_into().map_err(|_| EINVAL)?;
Ok(file.read_at(&ctx.task, offset, &[UserBuffer { address, length }])?.into())
}
pub fn sys_pwrite64(
ctx: &SyscallContext<'_>,
fd: FdNumber,
address: UserAddress,
length: usize,
offset: off_t,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
let offset = offset.try_into().map_err(|_| EINVAL)?;
Ok(file.write_at(&ctx.task, offset, &[UserBuffer { address, length }])?.into())
}
pub fn sys_readv(
ctx: &SyscallContext<'_>,
fd: FdNumber,
iovec_addr: UserAddress,
iovec_count: i32,
) -> Result<SyscallResult, Errno> {
let iovec = ctx.task.mm.read_iovec(iovec_addr, iovec_count)?;
let file = ctx.task.files.get(fd)?;
Ok(file.read(&ctx.task, &iovec)?.into())
}
pub fn sys_writev(
ctx: &SyscallContext<'_>,
fd: FdNumber,
iovec_addr: UserAddress,
iovec_count: i32,
) -> Result<SyscallResult, Errno> {
let iovec = ctx.task.mm.read_iovec(iovec_addr, iovec_count)?;
let file = ctx.task.files.get(fd)?;
Ok(file.write(&ctx.task, &iovec)?.into())
}
pub fn sys_fstatfs(
ctx: &SyscallContext<'_>,
_fd: FdNumber,
user_buf: UserRef<statfs>,
) -> Result<SyscallResult, Errno> {
let result = statfs::default();
ctx.task.mm.write_object(user_buf, &result)?;
Ok(SUCCESS)
}
/// A convenient wrapper for Task::open_file_at.
///
/// Reads user_path from user memory and then calls through to Task::open_file_at.
fn open_file_at(
task: &Task,
dir_fd: FdNumber,
user_path: UserCString,
flags: u32,
mode: FileMode,
) -> Result<FileHandle, Errno> {
let mut buf = [0u8; PATH_MAX as usize];
let path = task.mm.read_c_string(user_path, &mut buf)?;
task.open_file_at(dir_fd, path, OpenFlags::from_bits_truncate(flags), mode)
}
fn lookup_parent_at<T, F>(
task: &Task,
dir_fd: FdNumber,
user_path: UserCString,
callback: F,
) -> Result<T, Errno>
where
F: Fn(NamespaceNode, &FsStr) -> Result<T, Errno>,
{
let mut buf = [0u8; PATH_MAX as usize];
let path = task.mm.read_c_string(user_path, &mut buf)?;
let (parent, basename) = task.lookup_parent_at(dir_fd, path)?;
callback(parent, basename)
}
fn lookup_node_at(
task: &Task,
dir_fd: FdNumber,
user_path: UserCString,
symlink_follow_mode: SymlinkFollowing,
) -> Result<FsNodeHandle, Errno> {
lookup_parent_at(task, dir_fd, user_path, |parent, basename| {
Ok(parent.lookup(&task.fs, basename, symlink_follow_mode)?.node)
})
}
pub fn sys_openat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
flags: u32,
mode: FileMode,
) -> Result<SyscallResult, Errno> {
let file = open_file_at(&ctx.task, dir_fd, user_path, flags, mode)?;
Ok(ctx.task.files.add(file)?.into())
}
pub fn sys_faccessat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
mode: u32,
) -> Result<SyscallResult, Errno> {
// These values are defined in libc headers rather than in UAPI headers.
const F_OK: u32 = 0;
const X_OK: u32 = 1;
const W_OK: u32 = 2;
const R_OK: u32 = 4;
if mode & !(X_OK | W_OK | R_OK) != 0 {
return Err(EINVAL);
}
let node = lookup_node_at(&ctx.task, dir_fd, user_path, SymlinkFollowing::Disabled)?;
if mode == F_OK {
return Ok(SUCCESS);
}
// TODO(security): These access checks are not quite correct because
// they don't consider the current uid and they don't consider GRO or
// OTH bits. Really, these checks should be done by the auth system once
// that exists.
let stat = node.stat()?;
if mode & X_OK != 0 && stat.st_mode & S_IXUSR == 0 {
return Err(EACCES);
}
if mode & W_OK != 0 && stat.st_mode & S_IWUSR == 0 {
return Err(EACCES);
}
if mode & R_OK != 0 && stat.st_mode & S_IRUSR == 0 {
return Err(EACCES);
}
Ok(SUCCESS)
}
pub fn sys_chdir(ctx: &SyscallContext<'_>, user_path: UserCString) -> Result<SyscallResult, Errno> {
// TODO: This should probably use lookup_node.
let file = open_file_at(
&ctx.task,
FdNumber::AT_FDCWD,
user_path,
O_RDONLY | O_DIRECTORY,
FileMode::default(),
)?;
ctx.task.fs.chdir(&file);
Ok(SUCCESS)
}
pub fn sys_fchdir(ctx: &SyscallContext<'_>, fd: FdNumber) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
// TODO: Check isdir and return ENOTDIR.
ctx.task.fs.chdir(&file);
Ok(SUCCESS)
}
pub fn sys_access(
ctx: &SyscallContext<'_>,
user_path: UserCString,
mode: u32,
) -> Result<SyscallResult, Errno> {
sys_faccessat(ctx, FdNumber::AT_FDCWD, user_path, mode)
}
pub fn sys_fstat(
ctx: &SyscallContext<'_>,
fd: FdNumber,
buffer: UserRef<stat_t>,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
let result = file.node().stat()?;
ctx.task.mm.write_object(buffer, &result)?;
Ok(SUCCESS)
}
pub fn sys_newfstatat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
buffer: UserRef<stat_t>,
flags: u32,
) -> Result<SyscallResult, Errno> {
if flags & !AT_SYMLINK_NOFOLLOW != 0 {
not_implemented!("newfstatat: flags 0x{:x}", flags);
return Err(ENOSYS);
}
let node = if flags & AT_SYMLINK_NOFOLLOW != 0 {
lookup_node_at(ctx.task, dir_fd, user_path, SymlinkFollowing::Disabled)?
} else {
lookup_node_at(ctx.task, dir_fd, user_path, SymlinkFollowing::Enabled)?
};
let result = node.stat()?;
ctx.task.mm.write_object(buffer, &result)?;
Ok(SUCCESS)
}
pub fn sys_readlinkat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
buffer: UserAddress,
buffer_size: usize,
) -> Result<SyscallResult, Errno> {
let node = lookup_node_at(ctx.task, dir_fd, user_path, SymlinkFollowing::Disabled)?;
let link = node.readlink()?;
// Cap the returned length at buffer_size.
let length = std::cmp::min(buffer_size, link.len());
ctx.task.mm.write_memory(buffer, &link[..length])?;
Ok(length.into())
}
pub fn sys_readlink(
ctx: &SyscallContext<'_>,
user_path: UserCString,
buffer: UserAddress,
buffer_size: usize,
) -> Result<SyscallResult, Errno> {
sys_readlinkat(ctx, FdNumber::AT_FDCWD, user_path, buffer, buffer_size)
}
pub fn sys_truncate(
ctx: &SyscallContext<'_>,
user_path: UserCString,
length: off_t,
) -> Result<SyscallResult, Errno> {
let length = length.try_into().map_err(|_| EINVAL)?;
let node = lookup_node_at(&ctx.task, FdNumber::AT_FDCWD, user_path, SymlinkFollowing::Enabled)?;
// TODO: Check for writability.
node.truncate(length)?;
Ok(SUCCESS)
}
pub fn sys_ftruncate(
ctx: &SyscallContext<'_>,
fd: FdNumber,
length: off_t,
) -> Result<SyscallResult, Errno> {
let length = length.try_into().map_err(|_| EINVAL)?;
let file = ctx.task.files.get(fd)?;
// TODO: Check for writability.
file.node().truncate(length)?;
Ok(SUCCESS)
}
pub fn sys_mkdirat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
mode: FileMode,
) -> Result<SyscallResult, Errno> {
let mode = ctx.task.fs.apply_umask(mode & FileMode::ALLOW_ALL);
lookup_parent_at(&ctx.task, dir_fd, user_path, |parent, basename| {
parent.mknod(basename, FileMode::IFDIR | mode, 0)
})?;
Ok(SUCCESS)
}
pub fn sys_mknodat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
mode: FileMode,
dev: dev_t,
) -> Result<SyscallResult, Errno> {
let file_type = match mode & FileMode::IFMT {
FileMode::IFREG => FileMode::IFREG,
FileMode::IFBLK => FileMode::IFBLK,
FileMode::IFIFO => FileMode::IFIFO,
FileMode::IFSOCK => FileMode::IFSOCK,
FileMode::EMPTY => FileMode::IFREG,
_ => {
return Err(EINVAL);
}
};
let mode = file_type | ctx.task.fs.apply_umask(mode & FileMode::ALLOW_ALL);
lookup_parent_at(&ctx.task, dir_fd, user_path, |parent, basename| {
parent.mknod(basename, mode, dev)
})?;
Ok(SUCCESS)
}
pub fn sys_unlinkat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
flags: u32,
) -> Result<SyscallResult, Errno> {
if flags & !AT_REMOVEDIR != 0 {
return Err(EINVAL);
}
let kind =
if flags & AT_REMOVEDIR != 0 { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
lookup_parent_at(&ctx.task, dir_fd, user_path, |parent, basename| {
parent.unlink(&ctx.task.fs, basename, kind)
})?;
Ok(SUCCESS)
}
pub fn sys_fchmodat(
ctx: &SyscallContext<'_>,
dir_fd: FdNumber,
user_path: UserCString,
mode: FileMode,
) -> Result<SyscallResult, Errno> {
if mode & FileMode::IFMT != FileMode::EMPTY {
return Err(EINVAL);
}
let node = lookup_node_at(&ctx.task, dir_fd, user_path, SymlinkFollowing::Enabled)?;
node.info_write().mode = mode;
Ok(SUCCESS)
}
pub fn sys_fchownat(
_ctx: &SyscallContext<'_>,
_dir_fd: FdNumber,
_user_path: UserCString,
_owner: u32,
_group: u32,
_flags: u32,
) -> Result<SyscallResult, Errno> {
not_implemented!("Stubbed fchownat has no effect.");
Ok(SUCCESS)
}
pub fn sys_getcwd(
ctx: &SyscallContext<'_>,
buf: UserAddress,
size: usize,
) -> Result<SyscallResult, Errno> {
let mut bytes = ctx.task.fs.cwd().path();
bytes.push(b'\0');
if bytes.len() > size {
return Err(ERANGE);
}
ctx.task.mm.write_memory(buf, &bytes)?;
return Ok(bytes.len().into());
}
pub fn sys_umask(ctx: &SyscallContext<'_>, umask: FileMode) -> Result<SyscallResult, Errno> {
Ok(ctx.task.fs.set_umask(umask).bits().into())
}
fn get_fd_flags(flags: u32) -> FdFlags {
if flags & O_CLOEXEC != 0 {
FdFlags::CLOEXEC
} else {
FdFlags::empty()
}
}
pub fn sys_pipe2(
ctx: &SyscallContext<'_>,
user_pipe: UserRef<FdNumber>,
flags: u32,
) -> Result<SyscallResult, Errno> {
let supported_file_flags = OpenFlags::NONBLOCK | OpenFlags::DIRECT;
if flags & !(O_CLOEXEC | supported_file_flags.bits()) != 0 {
return Err(EINVAL);
}
let (read, write) = new_pipe(ctx.kernel());
let file_flags = OpenFlags::from_bits_truncate(flags & supported_file_flags.bits());
read.update_file_flags(file_flags, supported_file_flags);
write.update_file_flags(file_flags, supported_file_flags);
let fd_flags = get_fd_flags(flags);
let fd_read = ctx.task.files.add_with_flags(read, fd_flags)?;
let fd_write = ctx.task.files.add_with_flags(write, fd_flags)?;
ctx.task.mm.write_object(user_pipe, &fd_read)?;
let user_pipe = user_pipe.next();
ctx.task.mm.write_object(user_pipe, &fd_write)?;
Ok(SUCCESS)
}
pub fn sys_ioctl(
ctx: &SyscallContext<'_>,
fd: FdNumber,
request: u32,
in_addr: UserAddress,
out_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
let file = ctx.task.files.get(fd)?;
file.ioctl(&ctx.task, request, in_addr, out_addr)
}
pub fn sys_symlinkat(
ctx: &SyscallContext<'_>,
user_target: UserCString,
new_dir_fd: FdNumber,
user_path: UserCString,
) -> Result<SyscallResult, Errno> {
let mut buf = [0u8; PATH_MAX as usize];
let target = ctx.task.mm.read_c_string(user_target, &mut buf)?;
if target.len() == 0 {
return Err(ENOENT);
}
let mut buf = [0u8; PATH_MAX as usize];
let path = ctx.task.mm.read_c_string(user_path, &mut buf)?;
// TODO: This check could probably be moved into parent.symlink(..).
if path.len() == 0 {
return Err(ENOENT);
}
lookup_parent_at(ctx.task, new_dir_fd, user_path, |parent, basename| {
let stat = parent.node.stat()?;
if stat.st_mode & S_IWUSR == 0 {
return Err(EACCES);
}
parent.symlink(basename, target)
})?;
Ok(SUCCESS)
}
pub fn sys_dup(ctx: &SyscallContext<'_>, oldfd: FdNumber) -> Result<SyscallResult, Errno> {
let newfd = ctx.task.files.duplicate(oldfd, None, FdFlags::empty())?;
Ok(newfd.into())
}
pub fn sys_dup3(
ctx: &SyscallContext<'_>,
oldfd: FdNumber,
newfd: FdNumber,
flags: u32,
) -> Result<SyscallResult, Errno> {
if oldfd == newfd {
return Err(EINVAL);
}
let valid_flags = FdFlags::from_bits(flags).ok_or(EINVAL)?;
ctx.task.files.duplicate(oldfd, Some(newfd), valid_flags)?;
Ok(newfd.into())
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_async as fasync;
use std::sync::Arc;
use crate::testing::*;
#[fasync::run_singlethreaded(test)]
async fn test_sys_lseek() -> Result<(), Errno> {
let (_kernel, task_owner) = create_kernel_and_task_with_pkgfs();
let ctx = SyscallContext::new(&task_owner.task);
let fd = FdNumber::from_raw(10);
let file_handle = task_owner.task.open_file(b"data/testfile.txt", OpenFlags::RDONLY)?;
let file_size = file_handle.node().stat().unwrap().st_size;
task_owner.task.files.insert(fd, file_handle);
assert_eq!(sys_lseek(&ctx, fd, 0, SeekOrigin::CUR as u32)?, SyscallResult::Success(0));
assert_eq!(sys_lseek(&ctx, fd, 1, SeekOrigin::CUR as u32)?, SyscallResult::Success(1));
assert_eq!(sys_lseek(&ctx, fd, 3, SeekOrigin::SET as u32)?, SyscallResult::Success(3));
assert_eq!(sys_lseek(&ctx, fd, -3, SeekOrigin::CUR as u32)?, SyscallResult::Success(0));
assert_eq!(
sys_lseek(&ctx, fd, 0, SeekOrigin::END as u32)?,
SyscallResult::Success(file_size as u64)
);
assert_eq!(sys_lseek(&ctx, fd, -5, SeekOrigin::SET as u32), Err(EINVAL));
// Make sure that the failed call above did not change the offset.
assert_eq!(
sys_lseek(&ctx, fd, 0, SeekOrigin::CUR as u32)?,
SyscallResult::Success(file_size as u64)
);
// Prepare for an overflow.
assert_eq!(sys_lseek(&ctx, fd, 3, SeekOrigin::SET as u32)?, SyscallResult::Success(3));
// Check for overflow.
assert_eq!(sys_lseek(&ctx, fd, i64::MAX, SeekOrigin::CUR as u32), Err(EINVAL));
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_sys_dup() -> Result<(), Errno> {
let (_kernel, task_owner) = create_kernel_and_task_with_pkgfs();
let ctx = SyscallContext::new(&task_owner.task);
let file_handle = task_owner.task.open_file(b"data/testfile.txt", OpenFlags::RDONLY)?;
let files = &task_owner.task.files;
let oldfd = files.add(file_handle)?;
let syscall_result = sys_dup(&ctx, oldfd)?;
let newfd;
if let SyscallResult::Success(value) = syscall_result {
newfd = FdNumber::from_raw(value as i32);
} else {
return Err(EBADF);
}
assert_ne!(oldfd, newfd);
assert!(Arc::ptr_eq(&files.get(oldfd).unwrap(), &files.get(newfd).unwrap()));
assert_eq!(sys_dup(&ctx, FdNumber::from_raw(3)), Err(EBADF));
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_sys_dup3() -> Result<(), Errno> {
let (_kernel, task_owner) = create_kernel_and_task_with_pkgfs();
let ctx = SyscallContext::new(&task_owner.task);
let file_handle = task_owner.task.open_file(b"data/testfile.txt", OpenFlags::RDONLY)?;
let files = &task_owner.task.files;
let oldfd = files.add(file_handle)?;
let newfd = FdNumber::from_raw(2);
sys_dup3(&ctx, oldfd, newfd, FdFlags::CLOEXEC.bits())?;
assert_ne!(oldfd, newfd);
assert!(Arc::ptr_eq(&files.get(oldfd).unwrap(), &files.get(newfd).unwrap()));
assert_eq!(files.get_fd_flags(oldfd).unwrap(), FdFlags::empty());
assert_eq!(files.get_fd_flags(newfd).unwrap(), FdFlags::CLOEXEC);
assert_eq!(sys_dup3(&ctx, oldfd, oldfd, FdFlags::CLOEXEC.bits()), Err(EINVAL));
// Pass invalid flags.
let invalid_flags = 1234;
assert_eq!(sys_dup3(&ctx, oldfd, newfd, invalid_flags), Err(EINVAL));
// Makes sure that dup closes the old file handle before the fd points
// to the new file handle.
let second_file_handle =
task_owner.task.open_file(b"data/testfile.txt", OpenFlags::RDONLY)?;
let different_file_fd = files.add(second_file_handle)?;
assert!(!Arc::ptr_eq(&files.get(oldfd).unwrap(), &files.get(different_file_fd).unwrap()));
sys_dup3(&ctx, oldfd, different_file_fd, FdFlags::CLOEXEC.bits())?;
assert!(Arc::ptr_eq(&files.get(oldfd).unwrap(), &files.get(different_file_fd).unwrap()));
Ok(())
}
}