use std::os::fd::AsRawFd;
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, NixPath};
use crate::{
compat::{AddWatchFlags, FsType},
fd::{to_fd, PROC_FILE},
fs::{inotify_add_watch, readlinkat},
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;
#[expect(clippy::cast_possible_truncation)]
let mask = req.data.args[2] as u32;
let mask = match AddWatchFlags::from_bits(mask) {
Some(mask) if !mask.is_empty() => mask,
_ => return request.fail_syscall(Errno::EINVAL),
};
let infd = match to_fd(req.data.args[0]) {
Ok(fd) => fd,
Err(errno) => return request.fail_syscall(errno),
};
let infd = match request.get_fd(infd) {
Ok(fd) => fd,
Err(errno) => return request.fail_syscall(errno),
};
if mask.contains(AddWatchFlags::IN_MASK_ADD | AddWatchFlags::IN_MASK_CREATE) {
return request.fail_syscall(Errno::EINVAL);
}
match FsType::get(&infd) {
Ok(fst) if fst.is_anon_inode() => {
let pfd = match XPathBuf::from_self_fd(infd.as_raw_fd()) {
Ok(pfd) => pfd,
Err(errno) => return request.fail_syscall(errno),
};
match readlinkat(PROC_FILE(), &pfd) {
Ok(target) if target.is_equal(b"anon_inode:inotify") => {}
_ => return request.fail_syscall(Errno::EINVAL),
}
}
Ok(_) => return request.fail_syscall(Errno::EINVAL),
Err(errno) => return request.fail_syscall(errno),
}
let mut fsflags = FsFlags::MUST_PATH;
if mask.contains(AddWatchFlags::IN_DONT_FOLLOW) {
fsflags |= FsFlags::NO_FOLLOW_LAST;
}
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);
#[expect(clippy::disallowed_methods)]
let path = &path_args.0.as_ref().unwrap().path;
assert!(path.base().is_empty());
let mut mask = mask & !AddWatchFlags::IN_DONT_FOLLOW;
if mask.is_empty() {
mask = AddWatchFlags::IN_UNMOUNT;
}
if restrict_notify_bdev || restrict_notify_cdev {
#[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);
}
}
let mut pfd = XPathBuf::from("/proc/thread-self/fd");
pfd.push_fd(path.dir().as_raw_fd());
request.cache.add_sys_block(req, false)?;
let result = inotify_add_watch(&infd, &pfd, mask);
request.cache.del_sys_block(req.id)?;
result.map(|retval| request.return_syscall(i64::from(retval)))
},
)
}