starry-kernel 0.10.2

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
use alloc::{string::String, sync::Arc};
use core::ffi::c_char;

use ax_fs_ng::vfs::{OpenOptions, current_fs_context};
use linux_raw_sys::general::{MFD_CLOEXEC, O_RDWR};

pub(crate) use crate::file::memfd::{
    apply_shared_writable_deltas as memfd_apply_shared_writable_deltas,
    check_write_seal_for_shared_file_backend as memfd_check_write_seal_for_shared_file_backend,
    collect_metas_touching_mprotect_range as memfd_collect_metas_touching_mprotect_range,
    on_after_map as memfd_on_after_map,
    prepare_aspace_replace_deltas as memfd_prepare_aspace_replace_deltas,
    prepare_aspace_unmap_deltas as memfd_prepare_aspace_unmap_deltas,
    resync_shared_writable_counts_after_mprotect as memfd_resync_shared_writable_counts_after_mprotect,
};
use crate::{
    StarryError, StarryResult,
    file::{
        File, FileLike, add_file_like,
        memfd::{Memfd, MemfdRef},
    },
    mm::vm_load_string,
    pseudofs,
};

/// `MFD_ALLOW_SEALING` — bit 1. `linux-raw-sys` does not export it on every
/// target, so define locally.
const MFD_ALLOW_SEALING: u32 = 0x0002;

/// `MFD_HUGETLB` — bit 2. We do not back memfds with hugepages yet, so reject it.
const MFD_HUGETLB: u32 = 0x0004;
/// `MFD_NOEXEC_SEAL` — Linux 6.3+. Removes the backing inode's execute bits.
/// Locking those mode bits with `F_SEAL_EXEC` is not implemented yet.
const MFD_NOEXEC_SEAL: u32 = 0x0008;
/// `MFD_EXEC` — explicitly selects the default executable inode mode.
const MFD_EXEC: u32 = 0x0010;

/// Linux enforces `NAME_MAX - strlen("memfd:")` = 249 bytes for the name.
const MEMFD_NAME_MAX: usize = 249;

pub fn sys_memfd_create(
    current: &crate::task::UserTaskRef,
    name: *const c_char,
    flags: u32,
) -> crate::StarryResult<isize> {
    let valid_flags = MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL | MFD_EXEC;
    if flags & !valid_flags != 0 || flags & MFD_HUGETLB != 0 {
        return Err(StarryError::InvalidInput);
    }

    let cloexec = flags & MFD_CLOEXEC != 0;
    let allow_sealing = flags & MFD_ALLOW_SEALING != 0;

    // Load the name argument. Linux rejects overlong names.
    let name_str: String = vm_load_string(current, name)?;
    if name_str.len() > MEMFD_NAME_MAX {
        return Err(StarryError::InvalidInput);
    }

    let (mount_path, tmpfs) = if fs_has_dir("/dev/shm") {
        ("/dev/shm", pseudofs::shm_tmpfs())
    } else {
        ("/tmp", pseudofs::tmp_tmpfs())
    };
    let tmpfs = tmpfs.ok_or(StarryError::NotFound)?;

    let fs_context = current_fs_context();
    let fs = fs_context.lock();
    let mountpoint = fs.resolve(mount_path)?.mountpoint().clone();
    let cred = current.as_thread().cred();
    // Linux shmem_file_setup creates an executable anonymous inode; the
    // explicit no-exec flag removes its execute bits before publication.
    let mode = if flags & MFD_NOEXEC_SEAL != 0 {
        0o666
    } else {
        0o777
    };
    let entry = tmpfs.create_anonymous_file(
        &name_str,
        axfs_ng_vfs::NodePermission::from_bits_truncate(mode),
        cred.fsuid,
        cred.fsgid,
    );
    let loc = axfs_ng_vfs::Location::new(mountpoint, entry);

    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open_loc(loc.clone())?
        .into_file()?;

    let inner = Arc::new(File::new(file, O_RDWR));
    let memfd = Memfd::new(inner, name_str, allow_sealing);
    loc.user_data().insert(MemfdRef(memfd.clone()));
    add_file_like(memfd, cloexec).map(|fd| fd as _)
}

fn fs_has_dir(path: &str) -> bool {
    current_fs_context().lock().resolve(path).is_ok()
}

fn memfd_from_file_like(file_like: &Arc<dyn FileLike>) -> Option<Arc<Memfd>> {
    if let Ok(memfd) = file_like.clone().downcast_arc::<Memfd>() {
        return Some(memfd);
    }
    let file = file_like.downcast_ref::<File>()?;
    file.inner()
        .backend()
        .ok()?
        .location()
        .user_data()
        .get::<MemfdRef>()
        .map(|memfd| memfd.0.clone())
}

pub fn memfd_check_write_seal(file_like: &Arc<dyn FileLike>) -> StarryResult<()> {
    let Some(memfd) = memfd_from_file_like(file_like) else {
        return Ok(());
    };
    memfd.check_write_seal()
}

pub fn memfd_check_resize_seals(
    file_like: &Arc<dyn FileLike>,
    old_len: u64,
    new_len: u64,
) -> StarryResult<()> {
    let Some(memfd) = memfd_from_file_like(file_like) else {
        return Ok(());
    };
    let seals = memfd.get_seals();
    if new_len < old_len && seals & crate::file::memfd::F_SEAL_SHRINK != 0 {
        return Err(StarryError::OperationNotPermitted);
    }
    if new_len > old_len && seals & crate::file::memfd::F_SEAL_GROW != 0 {
        return Err(StarryError::OperationNotPermitted);
    }
    Ok(())
}

pub fn memfd_checks_before_stream_write(
    file_like: &Arc<dyn FileLike>,
    write_len: u64,
) -> StarryResult<()> {
    if write_len == 0 {
        return Ok(());
    }
    memfd_check_write_seal(file_like)
}

/// Preserves Linux's EFAULT-before-seal ordering for scalar stream writes.
///
/// Non-memfd streams keep the user buffer as an I/O cursor and therefore skip
/// eager address-space preparation. A memfd can reject the write before
/// consuming that cursor, so its input range must be validated first.
pub fn memfd_checks_before_write_at(
    file_like: &Arc<dyn FileLike>,
    _offset: u64,
    write_len: u64,
) -> StarryResult<()> {
    if write_len == 0 {
        return Ok(());
    }
    memfd_check_write_seal(file_like)
}