//
// Syd: rock-solid application kernel
// src/cookie.rs: Syscall argument cookies
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

// We allow this to easily write portable code.
// FIXME: Do not be lazy.
#![allow(dead_code)]

use std::{
    mem::MaybeUninit,
    os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd},
    sync::LazyLock,
};

use libseccomp::ScmpSyscall;
use nix::{
    errno::Errno,
    fcntl::{AtFlags, OFlag, OpenHow},
    sys::socket::{bind, connect, AddressFamily, Shutdown, SockFlag, SockType, SockaddrLike},
    unistd::UnlinkatFlags,
    NixPath,
};

use crate::{
    compat::{MFdFlags, RenameFlags},
    rng::randint,
    uts::UtsName,
};

/// A platform-sized secure cookie
///
/// 32 bits on 32-bit, 64 bits on 64-bit targets.
#[cfg(target_pointer_width = "32")]
pub(crate) type Cookie = u32;
#[cfg(target_pointer_width = "64")]
pub(crate) type Cookie = u64;

/// Generate a random `Cookie` using the OS's secure RNG.
///
/// This uses `syd::fs::fillrandom` under the hood to pull in
/// exactly the number of bytes needed for `Cookie`,
/// interprets them in little-endian order, and returns the result.
pub(crate) fn getcookie() -> Result<Cookie, Errno> {
    #[cfg(target_pointer_width = "32")]
    {
        randint(0u32..=u32::MAX)
    }

    #[cfg(target_pointer_width = "64")]
    {
        randint(0u64..=u64::MAX)
    }
}

// These cookies are confined by seccomp for use with openat2(2).
#[expect(clippy::disallowed_methods)]
pub(crate) static OPENAT2_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static OPENAT2_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: socket(2) $arg3
#[expect(clippy::disallowed_methods)]
pub static SOCKET_COOKIE_ARG3: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: socket(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static SOCKET_COOKIE_ARG4: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: socket(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static SOCKET_COOKIE_ARG5: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: socket(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static SOCKETPAIR_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: socket(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static SOCKETPAIR_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: accept4(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static ACCEPT4_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: accept4(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static ACCEPT4_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: bind(2) $arg3
#[expect(clippy::disallowed_methods)]
pub static BIND_COOKIE_ARG3: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: bind(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static BIND_COOKIE_ARG4: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: bind(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static BIND_COOKIE_ARG5: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: connect(2) $arg3
#[expect(clippy::disallowed_methods)]
pub static CONNECT_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: connect(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static CONNECT_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: connect(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static CONNECT_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: shutdown(2) $arg2
#[expect(clippy::disallowed_methods)]
pub static SHUTDOWN_COOKIE_ARG2: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: shutdown(2) $arg3
#[expect(clippy::disallowed_methods)]
pub static SHUTDOWN_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: shutdown(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static SHUTDOWN_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: shutdown(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static SHUTDOWN_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));

#[expect(clippy::disallowed_methods)]
pub(crate) static MEMFD_CREATE_COOKIE_ARG2: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static MEMFD_CREATE_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static MEMFD_CREATE_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static MEMFD_CREATE_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static RENAMEAT2_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE_COOKIE_ARG2: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE64_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE64_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static TRUNCATE64_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE_COOKIE_ARG2: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE64_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE64_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static FTRUNCATE64_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNAME_COOKIE_ARG1: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNAME_COOKIE_ARG2: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNAME_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNAME_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNAME_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNLINKAT_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNLINKAT_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static UNLINKAT_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static LINKAT_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: pipe2(2) $arg2
#[expect(clippy::disallowed_methods)]
pub static PIPE2_COOKIE_ARG2: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: pipe2(2) $arg3
#[expect(clippy::disallowed_methods)]
pub static PIPE2_COOKIE_ARG3: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: pipe2(2) $arg4
#[expect(clippy::disallowed_methods)]
pub static PIPE2_COOKIE_ARG4: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));
/// System call argument cookie: pipe2(2) $arg5
#[expect(clippy::disallowed_methods)]
pub static PIPE2_COOKIE_ARG5: LazyLock<Cookie> = LazyLock::new(|| getcookie().expect("getcookie"));

/// These are used in `syd::fs::seccomp_notify_addfd`.
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_ADDFD_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_ADDFD_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_ADDFD_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));

/// These are used in `syd::fs::seccomp_notify_respond`.
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_SEND_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_SEND_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static SECCOMP_IOCTL_NOTIF_SEND_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));

/// These are used in `syd::proc::procmap_query`.
#[expect(clippy::disallowed_methods)]
pub(crate) static PROCMAP_QUERY_COOKIE_ARG3: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static PROCMAP_QUERY_COOKIE_ARG4: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));
#[expect(clippy::disallowed_methods)]
pub(crate) static PROCMAP_QUERY_COOKIE_ARG5: LazyLock<Cookie> =
    LazyLock::new(|| getcookie().expect("getcookie"));

/// Safe openat2(2) confined by syscall cookies.
#[inline(always)]
pub(crate) fn safe_openat2<Fd: AsFd, P: NixPath + ?Sized>(
    dirfd: Fd,
    path: &P,
    mut how: OpenHow,
) -> Result<OwnedFd, Errno> {
    // SAFETY: In libc we trust.
    #[expect(clippy::cast_possible_truncation)]
    let fd = path.with_nix_path(|cstr| unsafe {
        libc::syscall(
            libc::SYS_openat2,
            dirfd.as_fd().as_raw_fd(),
            cstr.as_ptr(),
            std::ptr::addr_of_mut!(how),
            size_of::<libc::open_how>(),
            *OPENAT2_COOKIE_ARG4,
            *OPENAT2_COOKIE_ARG5,
        )
    })? as RawFd;

    Errno::result(fd)?;

    // SAFETY:
    //
    // `openat2(2)` should return a valid owned fd on success
    Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

/// socket(2) may be multiplexed by socketcall(2).
pub static SYS_SOCKET: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("socket")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe socket(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_socket(
    domain: libc::c_int,
    stype: libc::c_int,
    proto: libc::c_int,
) -> Result<OwnedFd, Errno> {
    if let Some(sys_socket) = *SYS_SOCKET {
        // SAFETY: In libc we trust.
        #[expect(clippy::cast_possible_truncation)]
        Errno::result(unsafe {
            libc::syscall(
                sys_socket,
                domain,
                stype,
                proto,
                *SOCKET_COOKIE_ARG3,
                *SOCKET_COOKIE_ARG4,
                *SOCKET_COOKIE_ARG5,
            )
        })
        .map(|fd| fd as RawFd)
    } else {
        // SAFETY:
        // socketcall(2) on multiplexed architecture.
        // We use libc version for convenience.
        Errno::result(unsafe { libc::socket(domain, stype, proto) })
    }
    .map(|fd| {
        // SAFETY: socket returns a valid FD on success.
        unsafe { OwnedFd::from_raw_fd(fd) }
    })
}

/// socketpair(2) may be multiplexed by socketcall(2).
pub static SYS_SOCKETPAIR: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("socketpair")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe socketpair(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_socketpair(
    domain: AddressFamily,
    ty: SockType,
    proto: libc::c_int,
    flags: SockFlag,
) -> Result<(OwnedFd, OwnedFd), Errno> {
    // Merge SockType and SockFlags together.
    let mut ty = ty as libc::c_int;
    ty |= flags.bits();

    let mut fds = [-1, -1];

    if let Some(sys_socketpair) = *SYS_SOCKETPAIR {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_socketpair,
                domain as libc::c_int,
                ty,
                proto,
                fds.as_mut_ptr(),
                *SOCKETPAIR_COOKIE_ARG4,
                *SOCKETPAIR_COOKIE_ARG5,
            )
        })?;
    } else {
        // SAFETY:
        // socketcall(2) on multiplexed architecture.
        // We use libc version for convenience.
        Errno::result(unsafe {
            libc::socketpair(domain as libc::c_int, ty, proto, fds.as_mut_ptr())
        })?;
    }

    // SAFETY: socketpair returns valid FDs on success.
    unsafe { Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1]))) }
}

/// accept4(2) may be multiplexed by socketcall(2).
pub static SYS_ACCEPT4: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("accept4")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe accept4(2) confined by syscall cookies.
///
/// # Safety
///
/// Dereferences raw pointers `addr` and `len`.
#[inline(always)]
pub unsafe fn safe_accept4<Fd: AsFd>(
    fd: Fd,
    addr: *mut libc::sockaddr,
    len: *mut libc::socklen_t,
    flags: SockFlag,
) -> Result<OwnedFd, Errno> {
    if let Some(sys_accept4) = *SYS_ACCEPT4 {
        // SAFETY: In libc we trust.
        #[expect(clippy::cast_possible_truncation)]
        Errno::result(unsafe {
            libc::syscall(
                sys_accept4,
                fd.as_fd().as_raw_fd(),
                addr,
                len,
                flags.bits(),
                *ACCEPT4_COOKIE_ARG4,
                *ACCEPT4_COOKIE_ARG5,
            )
        })
        .map(|fd| {
            // SAFETY: accept4 returns a valid fd on success.
            unsafe { OwnedFd::from_raw_fd(fd as RawFd) }
        })
    } else {
        // SAFETY: socketcall(2) on multiplexed architecture.
        Errno::result(unsafe { libc::accept4(fd.as_fd().as_raw_fd(), addr, len, flags.bits()) })
            .map(|fd| {
                // SAFETY: accept4 returns a valid fd on success.
                unsafe { OwnedFd::from_raw_fd(fd) }
            })
    }
}

/// Safe accept4(2) confined by syscall cookies.
///
/// Unlike `safe_accept4`, this version does not return source address,
/// therefore does not need an unsafe clause to dereference pointers.
#[inline(always)]
pub fn safer_accept4<Fd: AsFd>(fd: Fd, flags: SockFlag) -> Result<OwnedFd, Errno> {
    // SAFETY: accept4(2) does not dereference NULL pointers.
    unsafe { safe_accept4(fd, std::ptr::null_mut(), std::ptr::null_mut(), flags) }
}

/// bind(2) may be multiplexed by socketcall(2).
pub(crate) static SYS_BIND: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("bind")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe bind(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_bind<Fd: AsFd>(fd: Fd, addr: &dyn SockaddrLike) -> Result<(), Errno> {
    if let Some(sys_bind) = *SYS_BIND {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_bind,
                fd.as_fd().as_raw_fd(),
                addr.as_ptr(),
                addr.len(),
                *BIND_COOKIE_ARG3,
                *BIND_COOKIE_ARG4,
                *BIND_COOKIE_ARG5,
            )
        })
        .map(drop)
    } else {
        // socketcall(2) on multiplexed architecture.
        bind(fd.as_fd().as_raw_fd(), addr)
    }
}

/// connect(2) may be multiplexed by socketcall(2).
pub static SYS_CONNECT: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("connect")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe connect(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_connect<Fd: AsFd>(fd: Fd, addr: &dyn SockaddrLike) -> Result<(), Errno> {
    if let Some(sys_connect) = *SYS_CONNECT {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_connect,
                fd.as_fd().as_raw_fd(),
                addr.as_ptr(),
                addr.len(),
                *CONNECT_COOKIE_ARG3,
                *CONNECT_COOKIE_ARG4,
                *CONNECT_COOKIE_ARG5,
            )
        })
        .map(drop)
    } else {
        // socketcall(2) on multiplexed architecture.
        connect(fd.as_fd().as_raw_fd(), addr)
    }
}

/// shutdown(2) may be multiplexed by socketcall(2).
///
/// This is used by syd-tor(1) only, syd(1) does not hook into shutdown(2).
pub static SYS_SHUTDOWN: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("shutdown")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe shutdown(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_shutdown<Fd: AsFd>(fd: Fd, how: Shutdown) -> Result<(), Errno> {
    let how = match how {
        Shutdown::Read => libc::SHUT_RD,
        Shutdown::Write => libc::SHUT_WR,
        Shutdown::Both => libc::SHUT_RDWR,
    };

    if let Some(sys_shutdown) = *SYS_SHUTDOWN {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_shutdown,
                fd.as_fd().as_raw_fd(),
                how,
                *SHUTDOWN_COOKIE_ARG2,
                *SHUTDOWN_COOKIE_ARG3,
                *SHUTDOWN_COOKIE_ARG4,
                *SHUTDOWN_COOKIE_ARG5,
            )
        })
        .map(drop)
    } else {
        // SAFETY: socketcall(2) on multiplexed architecture.
        Errno::result(unsafe { libc::shutdown(fd.as_fd().as_raw_fd(), how) }).map(drop)
    }
}

/// Safe memfd_create(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_memfd_create<P: NixPath + ?Sized>(name: &P, flags: MFdFlags) -> Result<OwnedFd, Errno> {
    // name limit is 249 bytes, excluding the terminating null byte.
    if name.len() > 249 {
        return Err(Errno::EINVAL);
    }

    let res = name.with_nix_path(|cstr| {
        // SAFETY: In libc we trust.
        unsafe {
            libc::syscall(
                libc::SYS_memfd_create,
                cstr.as_ptr(),
                flags.bits(),
                *MEMFD_CREATE_COOKIE_ARG2,
                *MEMFD_CREATE_COOKIE_ARG3,
                *MEMFD_CREATE_COOKIE_ARG4,
                *MEMFD_CREATE_COOKIE_ARG5,
            )
        }
    })?;

    #[expect(clippy::cast_possible_truncation)]
    let fd = Errno::result(res).map(|fd| fd as RawFd)?;

    // SAFETY:
    //
    // `memfd_create(2)` returns a valid owned fd on success.
    Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

/// Safe renameat2(2) confined by syscall cookies.
#[inline(always)]
pub(crate) fn safe_renameat2<Fd1: AsFd, Fd2: AsFd, P1: NixPath + ?Sized, P2: NixPath + ?Sized>(
    old_dirfd: Fd1,
    old_path: &P1,
    new_dirfd: Fd2,
    new_path: &P2,
    flags: RenameFlags,
) -> Result<(), Errno> {
    let res = old_path.with_nix_path(|old_cstr| {
        // SAFETY: In libc we trust.
        new_path.with_nix_path(|new_cstr| unsafe {
            libc::syscall(
                libc::SYS_renameat2,
                old_dirfd.as_fd().as_raw_fd(),
                old_cstr.as_ptr(),
                new_dirfd.as_fd().as_raw_fd(),
                new_cstr.as_ptr(),
                flags.bits(),
                *RENAMEAT2_COOKIE_ARG5,
            )
        })
    })??;

    Errno::result(res).map(drop)
}

/// truncate(2) may be aliased to truncate64(2) by libc.
static SYS_TRUNCATE: LazyLock<Option<libc::c_long>> =
    LazyLock::new(|| {
        match ScmpSyscall::from_name("truncate")
            .map(i32::from)
            .map(libc::c_long::from)
            .ok()
        {
            Some(n) if n < 0 => None,
            Some(n) => Some(n),
            None => None,
        }
    });

/// truncate64(2) may not always be available via libc.
static SYS_TRUNCATE64: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("truncate64")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// ftruncate(2) may be aliased to ftruncate64(2) by libc.
static SYS_FTRUNCATE: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("ftruncate")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// ftruncate64(2) may not always be available via libc.
static SYS_FTRUNCATE64: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
    match ScmpSyscall::from_name("ftruncate64")
        .map(i32::from)
        .map(libc::c_long::from)
        .ok()
    {
        Some(n) if n < 0 => None,
        Some(n) => Some(n),
        None => None,
    }
});

/// Safe truncate(2) confined by syscall cookies.
pub(crate) fn safe_truncate<P: NixPath + ?Sized>(path: &P, len: libc::off_t) -> Result<(), Errno> {
    // On ILP32 where off_t is 64-bit, dispatch to truncate64 ABI.
    if size_of::<libc::off_t>() > size_of::<libc::c_long>() {
        return safe_truncate64(path, libc::off64_t::from(len));
    }

    let sys_truncate = SYS_TRUNCATE.ok_or(Errno::ENOSYS)?;

    // SAFETY: In libc we trust.
    let res = path.with_nix_path(|cstr| unsafe {
        libc::syscall(
            sys_truncate,
            cstr.as_ptr(),
            len,
            *TRUNCATE_COOKIE_ARG2,
            *TRUNCATE_COOKIE_ARG3,
            *TRUNCATE_COOKIE_ARG4,
            *TRUNCATE_COOKIE_ARG5,
        )
    })?;

    Errno::result(res).map(drop)
}

/// Safe truncate64(2) confined by syscall cookies.
pub(crate) fn safe_truncate64<P: NixPath + ?Sized>(
    path: &P,
    len: libc::off64_t,
) -> Result<(), Errno> {
    #[cfg(not(any(
        target_pointer_width = "64",
        all(target_arch = "x86_64", target_pointer_width = "32"),
        target_arch = "x86",
        target_arch = "arm",
        target_arch = "powerpc",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
    )))]
    {
        compile_error!("BUG: safe_truncate64 is not implemented for this architecture!");
    }

    #[cfg(any(
        target_pointer_width = "64",
        all(target_arch = "x86_64", target_pointer_width = "32"),
    ))]
    {
        safe_truncate(path, len)
    }

    #[cfg(any(target_arch = "m68k", target_arch = "x86",))]
    {
        let sys_truncate64 = SYS_TRUNCATE64.ok_or(Errno::ENOSYS)?;

        // i386: low, high
        let val = len as u64;
        let low = (val & 0xFFFF_FFFF) as libc::c_long;
        let high = (val >> 32) as libc::c_long;

        // SAFETY: In libc we trust.
        Errno::result(path.with_nix_path(|cstr| unsafe {
            libc::syscall(
                sys_truncate64,
                cstr.as_ptr(),
                low,
                high,
                *TRUNCATE64_COOKIE_ARG3,
                *TRUNCATE64_COOKIE_ARG4,
                *TRUNCATE64_COOKIE_ARG5,
            )
        })?)
        .map(drop)
    }

    #[cfg(any(
        target_arch = "arm",
        target_arch = "powerpc",
        target_arch = "mips",
        target_arch = "mips32r6"
    ))]
    {
        let sys_truncate64 = SYS_TRUNCATE64.ok_or(Errno::ENOSYS)?;

        // 32-bit ARM/ppc/mips: 0, low, high
        let val = len as u64;
        let low = (val & 0xFFFF_FFFF) as libc::c_long;
        let high = (val >> 32) as libc::c_long;

        // SAFETY: In libc we trust.
        Errno::result(path.with_nix_path(|cstr| unsafe {
            libc::syscall(
                sys_truncate64,
                cstr.as_ptr(),
                0 as libc::c_long,
                low,
                high,
                *TRUNCATE64_COOKIE_ARG4,
                *TRUNCATE64_COOKIE_ARG5,
            )
        })?)
        .map(drop)
    }
}

/// Safe ftruncate(2) confined by syscall cookies.
pub(crate) fn safe_ftruncate<Fd: AsFd>(fd: Fd, len: libc::off_t) -> Result<(), Errno> {
    // On ILP32 where off_t is 64-bit, dispatch to ftruncate64 ABI.
    if size_of::<libc::off_t>() > size_of::<libc::c_long>() {
        return safe_ftruncate64(fd, libc::off64_t::from(len));
    }

    let sys_ftruncate = SYS_FTRUNCATE.ok_or(Errno::ENOSYS)?;

    // SAFETY: In libc we trust.
    Errno::result(unsafe {
        libc::syscall(
            sys_ftruncate,
            fd.as_fd().as_raw_fd(),
            len,
            *FTRUNCATE_COOKIE_ARG2,
            *FTRUNCATE_COOKIE_ARG3,
            *FTRUNCATE_COOKIE_ARG4,
            *FTRUNCATE_COOKIE_ARG5,
        )
    })
    .map(drop)
}

/// Safe ftruncate64(2) confined by syscall cookies.
pub(crate) fn safe_ftruncate64<Fd: AsFd>(fd: Fd, len: libc::off64_t) -> Result<(), Errno> {
    #[cfg(not(any(
        target_pointer_width = "64",
        all(target_arch = "x86_64", target_pointer_width = "32"),
        target_arch = "x86",
        target_arch = "arm",
        target_arch = "powerpc",
        target_arch = "m68k",
        target_arch = "mips",
        target_arch = "mips32r6",
    )))]
    {
        compile_error!("BUG: safe_ftruncate64 is not implemented for this architecture!");
    }

    #[cfg(any(
        target_pointer_width = "64",
        all(target_arch = "x86_64", target_pointer_width = "32"),
    ))]
    {
        safe_ftruncate(fd, len)
    }

    #[cfg(any(target_arch = "m68k", target_arch = "x86",))]
    {
        let sys_ftruncate64 = SYS_FTRUNCATE64.ok_or(Errno::ENOSYS)?;

        // i386: low, high
        let val = len as u64;
        let low = (val & 0xFFFF_FFFF) as libc::c_long;
        let high = (val >> 32) as libc::c_long;

        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_ftruncate64,
                fd.as_fd().as_raw_fd(),
                low,
                high,
                *FTRUNCATE64_COOKIE_ARG3,
                *FTRUNCATE64_COOKIE_ARG4,
                *FTRUNCATE64_COOKIE_ARG5,
            )
        })
        .map(drop)
    }

    #[cfg(any(
        target_arch = "arm",
        target_arch = "powerpc",
        target_arch = "mips",
        target_arch = "mips32r6"
    ))]
    {
        let sys_ftruncate64 = SYS_FTRUNCATE64.ok_or(Errno::ENOSYS)?;

        // 32-bit ARM/ppc/mips: 0, low, high
        let val = len as u64;
        let low = (val & 0xFFFF_FFFF) as libc::c_long;
        let high = (val >> 32) as libc::c_long;

        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sys_ftruncate64,
                fd.as_fd().as_raw_fd(),
                0 as libc::c_long,
                low,
                high,
                *FTRUNCATE64_COOKIE_ARG4,
                *FTRUNCATE64_COOKIE_ARG5,
            )
        })
        .map(drop)
    }
}

/// Safe unlinkat(2) confined by syscall cookies.
#[inline(always)]
pub(crate) fn safe_unlinkat<Fd: AsFd, P: NixPath + ?Sized>(
    dirfd: Fd,
    path: &P,
    flag: UnlinkatFlags,
) -> Result<(), Errno> {
    let atflag = match flag {
        UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR,
        UnlinkatFlags::NoRemoveDir => AtFlags::empty(),
    };

    // SAFETY: In libc we trust.
    let res = path.with_nix_path(|cstr| unsafe {
        libc::syscall(
            libc::SYS_unlinkat,
            dirfd.as_fd().as_raw_fd(),
            cstr.as_ptr(),
            atflag.bits(),
            *UNLINKAT_COOKIE_ARG3,
            *UNLINKAT_COOKIE_ARG4,
            *UNLINKAT_COOKIE_ARG5,
        )
    })?;

    Errno::result(res).map(drop)
}

/// Safe linkat(2) confined by syscall cookies.
#[inline(always)]
pub(crate) fn safe_linkat<Fd1: AsFd, Fd2: AsFd, P1: NixPath + ?Sized, P2: NixPath + ?Sized>(
    olddirfd: Fd1,
    oldpath: &P1,
    newdirfd: Fd2,
    newpath: &P2,
    flag: AtFlags,
) -> Result<(), Errno> {
    let res = oldpath.with_nix_path(|oldcstr| {
        // SAFETY: In libc we trust.
        newpath.with_nix_path(|newcstr| unsafe {
            libc::syscall(
                libc::SYS_linkat,
                olddirfd.as_fd().as_raw_fd(),
                oldcstr.as_ptr(),
                newdirfd.as_fd().as_raw_fd(),
                newcstr.as_ptr(),
                flag.bits(),
                *LINKAT_COOKIE_ARG5,
            )
        })
    })??;

    Errno::result(res).map(drop)
}

/// Safe uname(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_uname() -> Result<UtsName, Errno> {
    let mut name = UtsName::default();

    // SAFETY: In libc we trust.
    Errno::result(unsafe {
        libc::syscall(
            libc::SYS_uname,
            &raw mut name,
            *UNAME_COOKIE_ARG1,
            *UNAME_COOKIE_ARG2,
            *UNAME_COOKIE_ARG3,
            *UNAME_COOKIE_ARG4,
            *UNAME_COOKIE_ARG5,
        )
    })?;

    Ok(name)
}

/// Safe pipe2(2) confined by syscall cookies.
#[inline(always)]
pub fn safe_pipe2(flags: OFlag) -> Result<(OwnedFd, OwnedFd), Errno> {
    let mut fds = MaybeUninit::<[OwnedFd; 2]>::uninit();

    // SAFETY: In libc we trust.
    Errno::result(unsafe {
        libc::syscall(
            libc::SYS_pipe2,
            fds.as_mut_ptr(),
            flags.bits(),
            *PIPE2_COOKIE_ARG2,
            *PIPE2_COOKIE_ARG3,
            *PIPE2_COOKIE_ARG4,
            *PIPE2_COOKIE_ARG5,
        )
    })?;

    // SAFETY: pipe2 returns valid FDs on success.
    let [read, write] = unsafe { fds.assume_init() };
    Ok((read, write))
}
