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

use libseccomp::ScmpNotifResp;
use nix::errno::Errno;

use crate::{
    kernel::syscall_path_handler,
    req::{SysArg, UNotifyEventRequest},
};

pub(crate) fn sys_chdir(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: We do not pass `fsflags` which defaults to MUST_PATH.
    let argv = &[SysArg {
        path: Some(0),
        ..Default::default()
    }];

    syscall_path_handler(request, "chdir", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY: SysArg has one element.
        #[expect(clippy::disallowed_methods)]
        if let Some(typ) = path_args.0.as_ref().unwrap().typ.as_ref() {
            if !typ.is_dir() {
                return Ok(request.fail_syscall(Errno::ENOTDIR));
            }
        } else {
            return Ok(request.fail_syscall(Errno::ENOENT));
        }

        // SAFETY: This is vulnerable to TOCTTOU.
        // We only use this hook with trace/allow_unsafe_ptrace:1
        // hence the user is aware of the consequences.
        Ok(unsafe { request.continue_syscall() })
    })
}

pub(crate) fn sys_fchdir(request: UNotifyEventRequest) -> ScmpNotifResp {
    // SAFETY: We do not pass `fsflags` which defaults to MUST_PATH.
    // Note: fchdir works with O_PATH fds.
    let argv = &[SysArg {
        dirfd: Some(0),
        ..Default::default()
    }];

    syscall_path_handler(request, "fchdir", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.

        // SAFETY: SysArg has one element.
        #[expect(clippy::disallowed_methods)]
        let path = path_args.0.as_ref().unwrap();

        // Check file type.
        if let Some(typ) = path.typ.as_ref() {
            if !typ.is_dir() {
                // Deny non-directory with ENOTDIR.
                return Ok(request.fail_syscall(Errno::ENOTDIR));
            }
        } else {
            // No file type, file disappeared mid-way?
            return Ok(request.fail_syscall(Errno::ENOTDIR));
        }

        // SAFETY: This is vulnerable to TOCTTOU.
        // We only use this hook with trace/allow_unsafe_ptrace:1
        // hence the user is aware of the consequences.
        Ok(unsafe { request.continue_syscall() })
    })
}
