blob: 0487d070be547f93e6109ff9096342084c39ddba [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 fuchsia_zircon::{self as zx, AsHandleRef, Task};
use fidl_fuchsia_io as fio;
use io_util::directory;
use io_util::node::OpenError;
use log::{info, warn};
use std::convert::TryInto;
use std::ffi::CStr;
use zerocopy::AsBytes;
use crate::executive::*;
use crate::fs::*;
use crate::types::*;
use crate::block_on;
pub fn sys_write(
ctx: &ThreadContext,
fd: FdNumber,
buffer: UserAddress,
count: usize,
) -> Result<SyscallResult, Errno> {
let fd = ctx.process.fd_table.get(fd)?;
Ok(fd.write(ctx, &[iovec{iov_base: buffer, iov_len: count}])?.into())
}
pub fn sys_fstat(
ctx: &ThreadContext,
fd: i32,
buffer: UserAddress,
) -> Result<SyscallResult, Errno> {
let fd = ctx.process.fd_table.get(fd)?;
let result = fd.fstat(ctx)?;
let bytes = result.as_bytes();
ctx.process.write_memory(buffer, bytes)?;
return Ok(SUCCESS);
}
fn mmap_prot_to_vm_opt(prot: i32) -> zx::VmarFlags {
let mut flags = zx::VmarFlags::empty();
if prot & PROT_READ != 0 {
flags |= zx::VmarFlags::PERM_READ;
}
if prot & PROT_WRITE != 0 {
flags |= zx::VmarFlags::PERM_WRITE;
}
if prot & PROT_EXEC != 0 {
flags |= zx::VmarFlags::PERM_EXECUTE;
}
flags
}
pub fn sys_mmap(
ctx: &ThreadContext,
addr: UserAddress,
length: usize,
prot: i32,
flags: i32,
fd: i32,
offset: usize,
) -> Result<SyscallResult, Errno> {
info!(
"mmap({:#x}, {:#x}, {:#x}, {:#x}, {}, {:#x})",
addr.ptr(),
length,
prot,
flags,
fd,
offset
);
// These are the flags that are currently supported.
if prot & !(PROT_READ | PROT_WRITE | PROT_EXEC) != 0 {
return Err(EINVAL);
}
if flags & !(MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE) != 0 {
return Err(EINVAL);
}
if flags & (MAP_PRIVATE | MAP_SHARED) == 0
|| flags & (MAP_PRIVATE | MAP_SHARED) == MAP_PRIVATE | MAP_SHARED
{
return Err(EINVAL);
}
if length == 0 {
return Err(EINVAL);
}
// TODO(tbodt): should we consider MAP_NORESERVE?
if flags & MAP_ANONYMOUS != 0 && fd != -1 {
return Err(EINVAL);
}
let mut zx_flags = mmap_prot_to_vm_opt(prot) | zx::VmarFlags::ALLOW_FAULTS;
if addr.ptr() != 0 {
// TODO(tbodt): if no MAP_FIXED, retry on EINVAL
zx_flags |= zx::VmarFlags::SPECIFIC;
}
if flags & MAP_FIXED != 0 {
// SAFETY: this is stupid
zx_flags |= unsafe { zx::VmarFlags::from_bits_unchecked(zx::VmarFlagsExtended::SPECIFIC_OVERWRITE.bits()) };
}
let (vmo, vmo_offset) = if flags & MAP_ANONYMOUS != 0 {
let vmo = zx::Vmo::create(length as u64).map_err(|s| match s {
zx::Status::NO_MEMORY => ENOMEM,
_ => impossible_error(s),
})?;
vmo.set_name(CStr::from_bytes_with_nul(b"starnix-anon\0").unwrap())
.map_err(impossible_error)?;
(vmo, 0)
} else {
// TODO(tbodt): maximize protection flags so that mprotect works
let fd = ctx.process.fd_table.get(fd)?;
let zx_prot = mmap_prot_to_vm_opt(prot);
if flags & MAP_PRIVATE != 0 {
let (mut vmo, vmo_offset) = fd.mmap(ctx, zx_prot - zx::VmarFlags::PERM_WRITE, flags, offset)?;
let mut clone_flags = zx::VmoChildOptions::COPY_ON_WRITE;
if !zx_prot.contains(zx::VmarFlags::PERM_WRITE) {
clone_flags |= zx::VmoChildOptions::NO_WRITE;
}
vmo = vmo.create_child(clone_flags, 0, vmo.get_size().map_err(impossible_error)?).map_err(|s| match s {
_ => impossible_error(s),
})?;
(vmo, vmo_offset)
} else {
fd.mmap(ctx, zx_prot, flags, offset)?
}
};
let root_base = ctx.process.mm.root_vmar.info().unwrap().base;
let ptr = if addr.ptr() == 0 { 0 } else { addr.ptr() - root_base };
let addr = ctx.process.mm.root_vmar.map(ptr, &vmo, vmo_offset as u64, length, zx_flags).map_err(
|s| match s {
zx::Status::INVALID_ARGS => EINVAL,
zx::Status::ACCESS_DENIED => EACCES, // or EPERM?
zx::Status::NOT_SUPPORTED => ENODEV,
zx::Status::BUFFER_TOO_SMALL => panic!("faults should have been allowed!"),
zx::Status::NO_MEMORY => ENOMEM,
_ => impossible_error(s),
},
)?;
Ok(addr.into())
}
pub fn sys_mprotect(
ctx: &ThreadContext,
addr: UserAddress,
length: usize,
prot: i32,
) -> Result<SyscallResult, Errno> {
// This is safe because the vmar belongs to a different process.
unsafe { ctx.process.mm.root_vmar.protect(addr.ptr(), length, mmap_prot_to_vm_opt(prot)) }
.map_err(|s| match s {
zx::Status::INVALID_ARGS => EINVAL,
// TODO: This should still succeed and change protection on whatever is mapped.
zx::Status::NOT_FOUND => EINVAL,
zx::Status::ACCESS_DENIED => EACCES,
_ => EINVAL, // impossible!
})?;
Ok(SUCCESS)
}
pub fn sys_brk(ctx: &ThreadContext, addr: UserAddress) -> Result<SyscallResult, Errno> {
// info!("brk: addr={}", addr);
Ok(ctx.process.mm.set_program_break(addr).map_err(Errno::from)?.into())
}
pub fn sys_pread64(
ctx: &ThreadContext,
fd: FdNumber,
buf: UserAddress,
count: usize,
mut offset: usize,
) -> Result<SyscallResult, Errno> {
let fd = ctx.process.fd_table.get(fd)?;
let bytes = fd.read(ctx, &mut offset, &[iovec{iov_base: buf, iov_len: count}])?;
Ok(bytes.into())
}
pub fn sys_writev(
ctx: &ThreadContext,
fd: FdNumber,
iovec_addr: UserAddress,
iovec_count: i32,
) -> Result<SyscallResult, Errno> {
let iovec_count: usize = iovec_count.try_into().map_err(|_| EINVAL)?;
if iovec_count > UIO_MAXIOV as usize {
return Err(EINVAL);
}
let mut iovecs: Vec<iovec> = Vec::new();
iovecs.reserve(iovec_count); // TODO: try_reserve
iovecs.resize(iovec_count, iovec::default());
ctx.process.read_memory(iovec_addr, iovecs.as_mut_slice().as_bytes_mut())?;
let fd = ctx.process.fd_table.get(fd)?;
Ok(fd.write(ctx, &iovecs)?.into())
}
pub fn sys_access(
_ctx: &ThreadContext,
path: UserAddress,
mode: i32,
) -> Result<SyscallResult, Errno> {
info!("access: path={} mode={}", path, mode);
Err(ENOSYS)
}
pub fn sys_getpid(_ctx: &ThreadContext) -> Result<SyscallResult, Errno> {
// This is set to 1 because Bionic skips referencing /dev if getpid() == 1, under the
// assumption that anything running after init will have access to /dev.
// TODO(tbodt): actual PID field
Ok(1.into())
}
pub fn sys_exit(ctx: &ThreadContext, error_code: i32) -> Result<SyscallResult, Errno> {
info!("exit: error_code={}", error_code);
// TODO: Set the error_code on the process.
ctx.process.handle.kill().map_err(Errno::from)?;
Ok(SUCCESS)
}
pub fn sys_uname(ctx: &ThreadContext, name: UserAddress) -> Result<SyscallResult, Errno> {
fn init_array(fixed: &mut [u8; 65], init: &'static str) {
let init_bytes = init.as_bytes();
let len = init.len();
fixed[..len].copy_from_slice(init_bytes)
}
let mut result = utsname_t {
sysname: [0; 65],
nodename: [0; 65],
release: [0; 65],
version: [0; 65],
machine: [0; 65],
};
init_array(&mut result.sysname, "Linux");
init_array(&mut result.nodename, "local");
init_array(&mut result.release, "5.7.17-starnix");
init_array(&mut result.version, "starnix");
init_array(&mut result.machine, "x86_64");
let bytes = result.as_bytes();
ctx.process.write_memory(name, bytes)?;
return Ok(SUCCESS);
}
pub fn sys_readlink(
_ctx: &ThreadContext,
_path: UserAddress,
_buffer: UserAddress,
_buffer_size: usize,
) -> Result<SyscallResult, Errno> {
// info!(
// "readlink: path={} buffer={} buffer_size={}",
// path, buffer, buffer_size
// );
Err(EINVAL)
}
pub fn sys_getuid(ctx: &ThreadContext) -> Result<SyscallResult, Errno> {
Ok(ctx.process.security.uid.into())
}
pub fn sys_getgid(ctx: &ThreadContext) -> Result<SyscallResult, Errno> {
Ok(ctx.process.security.gid.into())
}
pub fn sys_geteuid(ctx: &ThreadContext) -> Result<SyscallResult, Errno> {
Ok(ctx.process.security.euid.into())
}
pub fn sys_getegid(ctx: &ThreadContext) -> Result<SyscallResult, Errno> {
Ok(ctx.process.security.egid.into())
}
pub fn sys_fstatfs(
ctx: &ThreadContext,
_fd: FdNumber,
buf_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
let result = statfs::default();
ctx.process.write_memory(buf_addr, result.as_bytes())?;
Ok(SUCCESS)
}
pub fn sys_sched_getscheduler(_ctx: &ThreadContext, _pid: i32) -> Result<SyscallResult, Errno> {
Ok(SCHED_OTHER.into())
}
pub fn sys_arch_prctl(
ctx: &mut ThreadContext,
code: i32,
addr: UserAddress,
) -> Result<SyscallResult, Errno> {
match code {
ARCH_SET_FS => {
ctx.registers.fs_base = addr.ptr() as u64;
Ok(SUCCESS)
}
_ => {
info!("arch_prctl: Unknown code: code=0x{:x} addr={}", code, addr);
Err(ENOSYS)
}
}
}
pub fn sys_exit_group(ctx: &ThreadContext, error_code: i32) -> Result<SyscallResult, Errno> {
info!("exit_group: error_code={}", error_code);
// TODO: Set the error_code on the process.
ctx.process.handle.kill().map_err(Errno::from)?;
Ok(SUCCESS)
}
pub fn sys_openat(
ctx: &ThreadContext,
dir_fd: i32,
path_addr: UserAddress,
flags: i32,
mode: i32,
) -> Result<SyscallResult, Errno> {
if dir_fd != AT_FDCWD {
return Err(EINVAL);
}
let mut buf = [0u8; PATH_MAX];
let path = ctx.process.read_c_string(path_addr, &mut buf)?;
info!("openat({}, {}, {:#x}, {:#o})", dir_fd, String::from_utf8_lossy(path), flags, mode);
if path[0] != b'/' {
warn!("non-absolute paths are unimplemented");
return Err(ENOENT);
}
let path = &path[1..];
// TODO(tbodt): Need to switch to filesystem APIs that do not require UTF-8
let path = std::str::from_utf8(path).expect("bad UTF-8 in filename");
let node = block_on(directory::open_node(&ctx.process.root, path, fio::OPEN_RIGHT_READABLE, 0)).map_err(|e| match e {
OpenError::OpenError(zx::Status::NOT_FOUND) => ENOENT,
_ => {
warn!("open failed: {:?}", e);
EIO
}
})?;
Ok(ctx.process.fd_table.install_fd(FidlFile::from_node(node)?)?.into())
}
pub fn sys_getrandom(
ctx: &ThreadContext,
buf_addr: UserAddress,
size: usize,
_flags: i32,
) -> Result<SyscallResult, Errno> {
let mut buf = vec![0; size];
let size = zx::cprng_draw(&mut buf)?;
ctx.process.write_memory(buf_addr, &buf[0..size])?;
Ok(size.into())
}
pub fn sys_unknown(
_ctx: &ThreadContext,
syscall_number: syscall_number_t,
) -> Result<SyscallResult, Errno> {
info!("UNKNOWN syscall: {}", syscall_number);
// TODO: We should send SIGSYS once we have signals.
Err(ENOSYS)
}
// Call this when you get an error that should "never" happen, i.e. if it does that means the
// kernel was updated to produce some other error after this match was written.
// TODO(tbodt): find a better way to handle this than a panic.
pub fn impossible_error(status: zx::Status) -> Errno {
panic!("encountered impossible error: {}", status);
}