//
// Syd: rock-solid application kernel
// src/kernel/mknod.rs: mknod(2) and mknodat(2) handlers
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::os::fd::AsFd;

use libseccomp::ScmpNotifResp;
use nix::{
    errno::Errno,
    sys::stat::{Mode, SFlag},
};

use crate::{
    cookie::{safe_mknodat, safe_umask},
    kernel::{syscall_path_handler, to_mode, to_sflag},
    lookup::FsFlags,
    proc::proc_umask,
    req::{PathArgs, SysArg, UNotifyEventRequest},
};

pub(crate) fn sys_mknod(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // SAFETY: Reject undefined/invalid kind.
    let kind = match to_sflag(req.data.args[1]) {
        Ok(kind) => kind,
        Err(errno) => return request.fail_syscall(errno),
    };

    // SAFETY: Strip undefined/invalid perm bits.
    let perm = to_mode(req.data.args[1]);

    // SAFETY: Reject invalid dev.
    #[expect(clippy::useless_conversion)]
    let dev: libc::dev_t = match req.data.args[2].try_into() {
        Ok(dev) => dev,
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };

    // We want NO_FOLLOW_LAST because creating an entry
    // through a dangling symbolic link should return EEXIST!
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST,
        ..Default::default()
    }];
    syscall_path_handler(request, "mknod", argv, |path_args, request, sandbox| {
        let umask = sandbox.umask;
        drop(sandbox); // release the read-lock.
        syscall_mknod_handler(request, path_args, kind, perm, dev, umask)
    })
}

pub(crate) fn sys_mknodat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // SAFETY: Reject undefined/invalid kind.
    let kind = match to_sflag(req.data.args[2]) {
        Ok(kind) => kind,
        Err(errno) => return request.fail_syscall(errno),
    };

    // SAFETY: Strip undefined/invalid perm bits.
    let perm = to_mode(req.data.args[2]);

    // SAFETY: Reject invalid dev.
    #[expect(clippy::useless_conversion)]
    let dev: libc::dev_t = match req.data.args[3].try_into() {
        Ok(dev) => dev,
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };

    // We want NO_FOLLOW_LAST because creating an entry
    // through a dangling symbolic link should return EEXIST!
    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST,
        ..Default::default()
    }];
    syscall_path_handler(request, "mknodat", argv, |path_args, request, sandbox| {
        let umask = sandbox.umask;
        drop(sandbox); // release the read-lock.
        syscall_mknod_handler(request, path_args, kind, perm, dev, umask)
    })
}

/// A helper function to handle mknod* syscalls.
fn syscall_mknod_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    kind: SFlag,
    mut perm: Mode,
    dev: libc::dev_t,
    force_umask: Option<Mode>,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: SysArg has one element.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();

    // SAFETY:
    // 1. force_umask is only applied to regular files.
    // 2. force_umask overrides POSIX ACLs.
    if kind == SFlag::S_IFREG {
        if let Some(mask) = force_umask {
            perm &= !mask;
        }
    }

    let req = request.scmpreq;
    let mask = proc_umask(req.pid())?;

    // SAFETY: Honour process' umask.
    // Note, the umask is per-thread here.
    // Note, POSIX ACLs may override this.
    safe_umask(mask);

    safe_mknodat(
        path.dir.as_ref().map(|fd| fd.as_fd()).ok_or(Errno::EBADF)?,
        path.base(),
        kind,
        perm,
        dev,
    )
    .map(|_| request.return_syscall(0))
}
