use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, unistd::UnlinkatFlags, NixPath};
use crate::{
cookie::safe_unlinkat,
kernel::syscall_path_handler,
lookup::FsFlags,
req::{PathArgs, SysArg, UNotifyEventRequest},
};
pub(crate) fn sys_rmdir(request: UNotifyEventRequest) -> ScmpNotifResp {
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::NO_FOLLOW_LAST
| FsFlags::MUST_PATH
| FsFlags::WANT_BASE
| FsFlags::DOTLAST_ERMDIR,
..Default::default()
}];
syscall_path_handler(request, "rmdir", argv, |path_args, request, sandbox| {
drop(sandbox);
syscall_unlink_handler(request, path_args, true)
})
}
pub(crate) fn sys_unlink(request: UNotifyEventRequest) -> ScmpNotifResp {
let argv = &[SysArg {
path: Some(0),
fsflags: FsFlags::NO_FOLLOW_LAST
| FsFlags::MUST_PATH
| FsFlags::WANT_BASE
| FsFlags::DOTLAST_EISDIR,
..Default::default()
}];
syscall_path_handler(request, "unlink", argv, |path_args, request, sandbox| {
drop(sandbox);
syscall_unlink_handler(request, path_args, false)
})
}
pub(crate) fn sys_unlinkat(request: UNotifyEventRequest) -> ScmpNotifResp {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let flags = req.data.args[2] as libc::c_int;
if flags & !libc::AT_REMOVEDIR != 0 {
return request.fail_syscall(Errno::EINVAL);
}
let rmdir = flags & libc::AT_REMOVEDIR != 0;
let dotlast = if rmdir {
FsFlags::DOTLAST_ERMDIR
} else {
FsFlags::DOTLAST_EISDIR
};
let argv = &[SysArg {
dirfd: Some(0),
path: Some(1),
fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::MUST_PATH | FsFlags::WANT_BASE | dotlast,
..Default::default()
}];
syscall_path_handler(request, "unlinkat", argv, |path_args, request, sandbox| {
drop(sandbox);
syscall_unlink_handler(request, path_args, rmdir)
})
}
fn syscall_unlink_handler(
request: &UNotifyEventRequest,
args: PathArgs,
rmdir: bool,
) -> Result<ScmpNotifResp, Errno> {
#[expect(clippy::disallowed_methods)]
let path = &args.0.as_ref().unwrap().path;
if let Some(ftyp) = path.typ {
if ftyp.is_magic_link() {
return Err(Errno::EACCES);
}
if ftyp.is_dir() && !rmdir {
return Err(Errno::EISDIR);
}
if !ftyp.is_dir() && rmdir {
return Err(Errno::ENOTDIR);
}
}
if path.base().is_empty() {
return Err(Errno::EBUSY);
}
let flags = if rmdir {
UnlinkatFlags::RemoveDir
} else {
UnlinkatFlags::NoRemoveDir
};
let req = request.scmpreq;
request.cache.add_sys_block(req, false)?;
let result = safe_unlinkat(path.dir(), path.base(), flags);
request.cache.del_sys_block(req.id)?;
result.map(|_| request.return_syscall(0))
}