use std::os::fd::{AsFd, AsRawFd, FromRawFd, RawFd};
use bitflags::bitflags;
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
NixPath,
};
use crate::{compat::with_opt_nix_path, fd::SafeOwnedFd};
pub const AT_RECURSIVE: AtFlags = AtFlags::from_bits_retain(0x8000);
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct FsOpenFlags: libc::c_uint {
const FSOPEN_CLOEXEC = 0x00000001;
}
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum FsConfigCmd {
SetFlag = 0,
SetString = 1,
SetBinary = 2,
SetPath = 3,
SetPathEmpty = 4,
SetFd = 5,
CmdCreate = 6,
CmdReconfigure = 7,
CmdCreateExcl = 8,
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct FsMountFlags: libc::c_uint {
const FSMOUNT_CLOEXEC = 0x00000001;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MountAttrFlags: libc::c_uint {
const MOUNT_ATTR_RDONLY = 0x00000001;
const MOUNT_ATTR_NOSUID = 0x00000002;
const MOUNT_ATTR_NODEV = 0x00000004;
const MOUNT_ATTR_NOEXEC = 0x00000008;
const MOUNT_ATTR_NOATIME = 0x00000010;
const MOUNT_ATTR__ATIME = 0x00000070;
const MOUNT_ATTR_STRICTATIME = 0x00000020;
const MOUNT_ATTR_NODIRATIME = 0x00000080;
const MOUNT_ATTR_IDMAP = 0x00100000;
const MOUNT_ATTR_NOSYMFOLLOW = 0x00200000;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MoveMountFlags: libc::c_uint {
const MOVE_MOUNT_F_SYMLINKS = 0x00000001;
const MOVE_MOUNT_F_AUTOMOUNTS = 0x00000002;
const MOVE_MOUNT_F_EMPTY_PATH = 0x00000004;
const MOVE_MOUNT_T_SYMLINKS = 0x00000010;
const MOVE_MOUNT_T_AUTOMOUNTS = 0x00000020;
const MOVE_MOUNT_T_EMPTY_PATH = 0x00000040;
const MOVE_MOUNT_SET_GROUP = 0x00000100;
const MOVE_MOUNT_BENEATH = 0x00000200;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct OpenTreeFlags: libc::c_uint {
const OPEN_TREE_CLONE = 0x00000001;
#[expect(clippy::cast_sign_loss)]
const OPEN_TREE_CLOEXEC = OFlag::O_CLOEXEC.bits() as libc::c_uint;
#[expect(clippy::cast_sign_loss)]
const AT_EMPTY_PATH = AtFlags::AT_EMPTY_PATH.bits() as libc::c_uint;
#[expect(clippy::cast_sign_loss)]
const AT_NO_AUTOMOUNT = AtFlags::AT_NO_AUTOMOUNT.bits() as libc::c_uint;
#[expect(clippy::cast_sign_loss)]
const AT_SYMLINK_NOFOLLOW = AtFlags::AT_SYMLINK_NOFOLLOW.bits() as libc::c_uint;
const AT_RECURSIVE = 0x8000;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct FsPickFlags: libc::c_uint {
const FSPICK_CLOEXEC = 0x00000001;
const FSPICK_SYMLINK_NOFOLLOW = 0x00000002;
const FSPICK_NO_AUTOMOUNT = 0x00000004;
const FSPICK_EMPTY_PATH = 0x00000008;
}
}
#[repr(C)]
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Default)]
pub struct MountAttr {
pub attr_set: u64,
pub attr_clr: u64,
pub propagation: u64,
pub userns_fd: u64,
}
pub fn fsopen<P: ?Sized + NixPath>(fsname: &P, flags: FsOpenFlags) -> Result<SafeOwnedFd, Errno> {
fsname.with_nix_path(|cstr| {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe { libc::syscall(libc::SYS_fsopen, cstr.as_ptr(), flags.bits()) }).map(
|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
},
)
})?
}
pub fn fspick<Fd, P>(dirfd: Fd, path: &P, flags: FsPickFlags) -> Result<SafeOwnedFd, Errno>
where
Fd: AsFd,
P: ?Sized + NixPath,
{
path.with_nix_path(|cstr| {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_fspick,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
flags.bits(),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
})?
}
pub fn fsconfig<Fd, P>(
fd: Fd,
cmd: FsConfigCmd,
key: Option<&P>,
value: Option<&[u8]>,
aux: libc::c_int,
) -> Result<(), Errno>
where
Fd: AsFd,
P: ?Sized + NixPath,
{
let fd = fd.as_fd().as_raw_fd();
let cmd = cmd as libc::c_uint;
let value: *const libc::c_void = value.map(|v| v.as_ptr().cast()).unwrap_or(std::ptr::null());
let res = with_opt_nix_path(key, |key| unsafe {
libc::syscall(libc::SYS_fsconfig, fd, cmd, key, value, aux)
})?;
Errno::result(res).map(|_| ())
}
pub fn fsmount<Fd: AsFd>(
fsfd: Fd,
flags: FsMountFlags,
attr_flags: MountAttrFlags,
) -> Result<SafeOwnedFd, Errno> {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_fsmount,
fsfd.as_fd().as_raw_fd(),
flags.bits(),
attr_flags.bits(),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
}
pub fn move_mount<Fd1, Fd2, P1, P2>(
from_dirfd: Fd1,
from_path: &P1,
to_dirfd: Fd2,
to_path: &P2,
flags: MoveMountFlags,
) -> Result<(), Errno>
where
Fd1: AsFd,
Fd2: AsFd,
P1: ?Sized + NixPath,
P2: ?Sized + NixPath,
{
from_path.with_nix_path(|from_cstr| {
to_path.with_nix_path(|to_cstr| {
Errno::result(unsafe {
libc::syscall(
libc::SYS_move_mount,
from_dirfd.as_fd().as_raw_fd(),
from_cstr.as_ptr(),
to_dirfd.as_fd().as_raw_fd(),
to_cstr.as_ptr(),
flags.bits(),
)
})
.map(drop)
})
})??
}
pub fn open_tree<Fd, P>(dirfd: Fd, path: &P, flags: OpenTreeFlags) -> Result<SafeOwnedFd, Errno>
where
Fd: AsFd,
P: ?Sized + NixPath,
{
path.with_nix_path(|cstr| {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
libc::SYS_open_tree,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
flags.bits(),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
})?
}
pub fn open_tree_attr<Fd, P>(
dirfd: Fd,
path: &P,
flags: OpenTreeFlags,
attr: &MountAttr,
) -> Result<SafeOwnedFd, Errno>
where
Fd: AsFd,
P: ?Sized + NixPath,
{
path.with_nix_path(|cstr| {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe {
libc::syscall(
SYS_OPEN_TREE_ATTR,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
flags.bits(),
&raw const attr,
size_of::<MountAttr>(),
)
})
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
})?
}
pub fn mount_setattr<Fd, P>(
dirfd: Fd,
path: &P,
flags: AtFlags,
attr: MountAttr,
) -> Result<(), Errno>
where
Fd: AsFd,
P: ?Sized + NixPath,
{
path.with_nix_path(|cstr| {
Errno::result(unsafe {
libc::syscall(
libc::SYS_mount_setattr,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
flags.bits(),
&raw const attr,
size_of::<MountAttr>(),
)
})
.map(drop)
})?
}
#[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
const SYS_OPEN_TREE_ATTR: libc::c_long = 467 + 4000;
#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
const SYS_OPEN_TREE_ATTR: libc::c_long = 467 + 5000;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
)))]
const SYS_OPEN_TREE_ATTR: libc::c_long = 467;