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

use std::os::fd::AsRawFd;

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, NixPath};

use crate::{
    compat::AddWatchFlags,
    fd::to_fd,
    fs::inotify_add_watch,
    kernel::syscall_path_handler,
    lookup::FsFlags,
    path::XPathBuf,
    req::{SysArg, UNotifyEventRequest},
};

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

    // Linux kernel truncates upper bits.
    #[expect(clippy::cast_possible_truncation)]
    let mask = req.data.args[2] as u32;

    // Reject invalid masks.
    let mask = match AddWatchFlags::from_bits(mask) {
        Some(mask) => mask,
        None => return request.fail_syscall(Errno::EINVAL),
    };

    // inotify(7) requires read access to the file or directory.
    let mut fsflags = FsFlags::MUST_PATH;
    if mask.contains(AddWatchFlags::IN_DONT_FOLLOW) {
        fsflags |= FsFlags::NO_FOLLOW_LAST;
    }

    // Validate inotify(7) fd is a valid file descriptor.
    let infd = match to_fd(req.data.args[0]) {
        Ok(fd) => fd,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        dirfd: None,
        path: Some(1),
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "inotify_add_watch",
        argv,
        |path_args, request, sandbox| {
            let restrict_notify_bdev = !sandbox.flags.allow_unsafe_notify_bdev();
            let restrict_notify_cdev = !sandbox.flags.allow_unsafe_notify_cdev();
            drop(sandbox); // release the read-lock.

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

            if !path.base().is_empty() {
                // Ensure we have a direct FD.
                unreachable!("BUG: inotify_add_watch with base path!");
            }

            let mut mask = mask & !AddWatchFlags::IN_DONT_FOLLOW;
            if restrict_notify_bdev || restrict_notify_cdev {
                // Strip IN_{ACCESS,MODIFY} if we're marking a sidechannel device.
                // Strip IN_DONT_FOLLOW which has already been handled during canonicalization.
                // MUST_PATH asserts `path.typ` is Some.
                #[expect(clippy::disallowed_methods)]
                let typ = path.typ.as_ref().unwrap();
                if (restrict_notify_bdev && typ.is_block_device())
                    || (restrict_notify_cdev && typ.is_char_device())
                {
                    mask.remove(AddWatchFlags::IN_ACCESS);
                    mask.remove(AddWatchFlags::IN_MODIFY);
                }
            }

            // We open a FD to the path and then use the proc(5) path
            // /proc/thread-self/fd/$fd in address' path argument to
            // avoid symlink TOCTOU.
            let mut pfd = XPathBuf::from("/proc/thread-self/fd");
            pfd.push_fd(path.dir().as_raw_fd());

            // Get the INotify FD.
            let fd = request.get_fd(infd)?;

            inotify_add_watch(&fd, &pfd, mask)
                .map(|retval| request.return_syscall(i64::from(retval)))
        },
    )
}
