//
// Syd: rock-solid application kernel
// src/kernel/mkdir.rs: mkdir(2) and mkdirat(2) handlers
//
// Copyright (c) 2023, 2024, 2025 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::{mkdirat, Mode},
};

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

pub(crate) fn sys_mkdir(request: UNotifyEventRequest) -> ScmpNotifResp {
    // We want NO_FOLLOW_LAST because creating an entry
    // through a dangling symbolic link should return EEXIST!
    let req = request.scmpreq;

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

    let argv = &[SysArg {
        path: Some(0),
        dotlast: Some(Errno::ENOENT),
        fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST,
        ..Default::default()
    }];
    syscall_path_handler(request, "mkdir", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_mkdir_handler(request, path_args, mode)
    })
}

pub(crate) fn sys_mkdirat(request: UNotifyEventRequest) -> ScmpNotifResp {
    // We want NO_FOLLOW_LAST because creating an entry
    // through a dangling symbolic link should return EEXIST!
    let req = request.scmpreq;

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

    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        dotlast: Some(Errno::ENOENT),
        fsflags: FsFlags::MISS_LAST | FsFlags::NO_FOLLOW_LAST,
        ..Default::default()
    }];
    syscall_path_handler(request, "mkdirat", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_mkdir_handler(request, path_args, mode)
    })
}

/// A helper function to handle mkdir* syscalls.
fn syscall_mkdir_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    mode: Mode,
) -> Result<ScmpNotifResp, Errno> {
    // SAFETY: SysArg has one element.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();

    // SAFETY: Return EEXIST if the path already exists.
    // We do this after the access check to ensure
    // the EEXIST errno cannot be misused to detect
    // hidden files.
    if path.typ.is_some() {
        return Err(Errno::EEXIST);
    }

    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);

    mkdirat(
        path.dir.as_ref().map(|fd| fd.as_fd()).ok_or(Errno::EBADF)?,
        path.base(),
        mode,
    )
    .map(|_| request.return_syscall(0))
}
