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

// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]

use std::{
    borrow::Cow,
    os::fd::{AsFd, AsRawFd},
};

use libc::{c_int, c_void, XATTR_CREATE, XATTR_REPLACE};
use libseccomp::ScmpNotifResp;
use nix::{
    errno::Errno,
    fcntl::{AtFlags, OFlag},
    NixPath,
};

use crate::{
    compat::{
        fgetxattr, flistxattr, getxattrat, lgetxattr, listxattrat, llistxattr, setxattrat,
        ResolveFlag, XattrArgs, XATTR_LIST_MAX, XATTR_SIZE_MAX,
    },
    confine::is_valid_ptr,
    cookie::{
        safe_fchdir, safe_fremovexattr, safe_fsetxattr, safe_lremovexattr, safe_lsetxattr,
        safe_removexattrat,
    },
    fd::PROC_FILE,
    kernel::{syscall_path_handler, to_atflags},
    lookup::{safe_open, safe_open_msym, FsFlags},
    path::{XPath, XPathBuf},
    req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
    sandbox::SandboxGuard,
    xattr::{denyxattr, filterxattr},
};

pub(crate) fn sys_getxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Size argument to the getxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let req = request.scmpreq;
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "getxattr", argv, |path_args, request, sandbox| {
        syscall_getxattr_handler(request, &sandbox, path_args, len, true /* reopen */)
    })
}

pub(crate) fn sys_lgetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Size argument to the getxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let req = request.scmpreq;
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // We set WANT_BASE to operate on the symbolic link directly.
    // syscall_lgetxattr_handler() doesn't follow symlinks to avoid TOCTOU.
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
        ..Default::default()
    }];
    syscall_path_handler(request, "lgetxattr", argv, |path_args, request, sandbox| {
        syscall_lgetxattr_handler(request, &sandbox, path_args, len)
    })
}

pub(crate) fn sys_fgetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Size argument to the getxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let req = request.scmpreq;
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "fgetxattr", argv, |path_args, request, sandbox| {
        syscall_getxattr_handler(request, &sandbox, path_args, len, false /* reopen */)
    })
}

pub(crate) fn sys_getxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
    // We set WANT_BASE to operate on symbolic links directly as necessary.
    // syscall_getxattrat_handler() doesn't follow symlinks to avoid TOCTOU.
    let req = request.scmpreq;

    // Reject undefined/invalid flags.
    let flags = match to_atflags(
        req.data.args[2],
        AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
    ) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    let xargs = match request.remote_xattr_args(req.data.args[4], req.data.args[5]) {
        Ok(xargs) => xargs,
        Err(errno) => return request.fail_syscall(errno),
    };

    // For getxattrat(2) flags member must be zero!
    if xargs.flags != 0 {
        return request.fail_syscall(Errno::EINVAL);
    }

    // Size element of the struct xattr_args must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_cap(xargs.size.into(), XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name pointer.
    if !is_valid_ptr(req.data.args[3], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Reject invalid value pointer.
    let val = xargs.value;
    if val != 0 && !is_valid_ptr(val, req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let mut fsflags = FsFlags::MUST_PATH | FsFlags::WANT_BASE;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if empty_path {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::empty()
        },
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "getxattrat",
        argv,
        |path_args, request, sandbox| {
            syscall_getxattrat_handler(request, &sandbox, path_args, val, len)
        },
    )
}

pub(crate) fn sys_setxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject undefined/invalid flags.
    let req = request.scmpreq;
    let flags = match to_xattr_flags(req.data.args[4]) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Size argument to the setxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "setxattr", argv, |path_args, request, sandbox| {
        syscall_setxattr_handler(
            request, &sandbox, path_args, len, flags, true, /* reopen */
        )
    })
}

pub(crate) fn sys_fsetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject undefined/invalid flags.
    let req = request.scmpreq;
    let flags = match to_xattr_flags(req.data.args[4]) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Size argument to the setxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "fsetxattr", argv, |path_args, request, sandbox| {
        syscall_setxattr_handler(
            request, &sandbox, path_args, len, flags, false, /* reopen */
        )
    })
}

pub(crate) fn sys_lsetxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject undefined/invalid flags.
    let req = request.scmpreq;
    let flags = match to_xattr_flags(req.data.args[4]) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Size argument to the setxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_cap(req.data.args[3], XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name and value pointers.
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }
    if req.data.args[2] != 0 && !is_valid_ptr(req.data.args[2], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // We set WANT_BASE because fsetxattr(2) does not work on O_PATH|O_NOFOLLOW fds.
    // syscall_lsetxattr_handler() doesn't follow symlinks to avoid TOCTOU.
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
        ..Default::default()
    }];

    syscall_path_handler(request, "lsetxattr", argv, |path_args, request, sandbox| {
        syscall_lsetxattr_handler(request, &sandbox, path_args, len, flags)
    })
}

pub(crate) fn sys_setxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
    // We set WANT_BASE to operate on symbolic links directly as necessary.
    // syscall_setxattrat_handler() doesn't follow symlinks to avoid TOCTOU.
    let req = request.scmpreq;

    // Reject undefined/invalid flags.
    let flags = match to_atflags(
        req.data.args[2],
        AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
    ) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    let xargs = match request.remote_xattr_args(req.data.args[4], req.data.args[5]) {
        Ok(xargs) => xargs,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject undefined/invalid flags.
    let xflags = match to_xattr_flags(xargs.flags.into()) {
        Ok(xflags) => xflags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Size argument to the setxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_val(xargs.size.into(), XATTR_SIZE_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name pointer.
    if !is_valid_ptr(req.data.args[3], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Reject invalid value pointer.
    let val = xargs.value;
    if val != 0 && !is_valid_ptr(val, req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let mut fsflags = FsFlags::MUST_PATH | FsFlags::WANT_BASE;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if empty_path {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::empty()
        },
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "setxattrat",
        argv,
        |path_args, request, sandbox| {
            syscall_setxattrat_handler(request, &sandbox, path_args, xflags, val, len)
        },
    )
}

pub(crate) fn sys_flistxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid list pointer.
    let req = request.scmpreq;
    if req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Size argument to the flistxattr(2) call must not be fully
    // trusted, it can be overly large, and allocating a Vector of that
    // capacity may overflow.
    let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        dirfd: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "flistxattr",
        argv,
        |path_args, request, sandbox| {
            syscall_listxattr_handler(request, &sandbox, path_args, len, false /* reopen */)
        },
    )
}

pub(crate) fn sys_listxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid list pointer.
    let req = request.scmpreq;
    if req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Size argument to the listxattr(2) call must not be fully trusted,
    // it can be overly large, and allocating a Vector of that capacity
    // may overflow.
    let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(request, "listxattr", argv, |path_args, request, sandbox| {
        syscall_listxattr_handler(request, &sandbox, path_args, len, true /* reopen */)
    })
}

pub(crate) fn sys_llistxattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid list pointer.
    let req = request.scmpreq;
    if req.data.args[1] != 0 && !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Size argument to the llistxattr(2) call must not be fully
    // trusted, it can be overly large, and allocating a Vector of that
    // capacity may overflow.
    let len = match to_len_cap(req.data.args[2], XATTR_LIST_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // We set WANT_BASE to operate on the symbolic link directly.
    // syscall_llistxattr_handler() doesn't follow symlinks to avoid TOCTOU.
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "llistxattr",
        argv,
        |path_args, request, sandbox| syscall_llistxattr_handler(request, &sandbox, path_args, len),
    )
}

pub(crate) fn sys_removexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid name pointer.
    let req = request.scmpreq;
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "removexattr",
        argv,
        |path_args, request, sandbox| {
            syscall_removexattr_handler(request, &sandbox, path_args, true /* reopen */)
        },
    )
}

pub(crate) fn sys_listxattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid list pointer.
    let req = request.scmpreq;
    if req.data.args[3] != 0 && !is_valid_ptr(req.data.args[3], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // Size argument to the llistxattrat(2) call must not be fully
    // trusted, it can be overly large, and allocating a Vector of that
    // capacity may overflow.
    let len = match to_len_cap(req.data.args[4], XATTR_LIST_MAX) {
        Ok(len) => len,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject undefined/invalid flags.
    let flags = match to_atflags(
        req.data.args[2],
        AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH,
    ) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // We set WANT_BASE to operate on symbolic links directly as necessary.
    // syscall_listxattrat_handler() doesn't follow symlinks to avoid TOCTOU.
    let mut fsflags = FsFlags::MUST_PATH | FsFlags::WANT_BASE;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if empty_path {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::empty()
        },
        fsflags,
        ..Default::default()
    }];

    syscall_path_handler(
        request,
        "listxattrat",
        argv,
        |path_args, request, sandbox| {
            syscall_listxattrat_handler(request, &sandbox, path_args, len)
        },
    )
}

pub(crate) fn sys_fremovexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid name pointer.
    let req = request.scmpreq;
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "fremovexattr",
        argv,
        |path_args, request, sandbox| {
            syscall_removexattr_handler(request, &sandbox, path_args, false /* reopen */)
        },
    )
}

pub(crate) fn sys_lremovexattr(request: UNotifyEventRequest) -> ScmpNotifResp {
    // Reject invalid name pointer.
    let req = request.scmpreq;
    if !is_valid_ptr(req.data.args[1], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    // We set WANT_BASE because fremovexattr(2) does not work on O_PATH|O_NOFOLLOW fds.
    // syscall_lremovexattr_handler() doesn't follow symlinks to avoid TOCTOU.
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH | FsFlags::NO_FOLLOW_LAST | FsFlags::WANT_BASE,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "lremovexattr",
        argv,
        |path_args, request, sandbox| syscall_lremovexattr_handler(request, &sandbox, path_args),
    )
}

pub(crate) fn sys_removexattrat(request: UNotifyEventRequest) -> ScmpNotifResp {
    // We set WANT_BASE to operate on symbolic links directly as necessary.
    // syscall_removexattrat_handler() doesn't follow symlinks to avoid TOCTOU.
    let req = request.scmpreq;

    // Reject undefined/invalid flags.
    let flags = match to_xattrat_flags(req.data.args[2]) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject invalid name pointer.
    let req = request.scmpreq;
    if !is_valid_ptr(req.data.args[3], req.data.arch) {
        return request.fail_syscall(Errno::EFAULT);
    }

    let mut fsflags = FsFlags::MUST_PATH | FsFlags::WANT_BASE;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if empty_path {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::empty()
        },
        fsflags,
        ..Default::default()
    }];
    syscall_path_handler(
        request,
        "removexattrat",
        argv,
        |path_args, request, sandbox| syscall_removexattrat_handler(request, &sandbox, path_args),
    )
}

// A helper function to handle getxattr(2) and fgetxattr(2) syscalls.
fn syscall_getxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
    reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

    // SysArg has one element.
    // Reopen as read-only for getxattr(2) after access check.
    // Use O_NOCTTY to avoid acquiring controlling terminal.
    // Use O_NONBLOCK to avoid blocking on pipes/fifos.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();
    let mut fd = Cow::Borrowed(path.dir());
    if reopen {
        let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
        let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
        fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
    }

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf: Vec<u8> = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = fgetxattr(fd.as_fd(), &name, buf.as_mut())?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        n = request.write_mem(&buf, req.data.args[2])?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle the lgetxattr(2) syscall.
fn syscall_lgetxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

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

    let mut fd = Cow::Borrowed(path.dir());
    let base = match path.typ.as_ref() {
        None => return Err(Errno::ENOENT),
        // Disallow xattrs on magiclinks.
        Some(t) if t.is_magic_link() => return Err(Errno::EPERM),
        Some(t) if t.is_symlink() => path.base(),
        _ => {
            // Open for read-only after access check.
            // Do not follow symbolic links and use RESOLVE_BENEATH.
            // Use O_NOCTTY to avoid acquiring controlling terminal.
            // Use O_NONBLOCK to avoid blocking on pipes/fifos.
            let base = if path.base().is_empty() {
                XPath::from_bytes(b".")
            } else {
                path.base()
            };

            fd = Cow::Owned(
                safe_open(
                    fd.as_fd(),
                    base,
                    OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                    ResolveFlag::empty(),
                )?
                .into(),
            );

            XPath::empty()
        }
    };

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf: Vec<u8> = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = if base.is_empty() {
        // Working on regular file directly.
        fgetxattr(fd.as_fd(), &name, buf.as_mut())
    } else {
        // Working on symlink using basename.
        // We must change directory using fchdir(2) for safety.
        // We must not follow symlinks in basename.
        safe_fchdir(fd.as_fd())?;
        lgetxattr(base, &name, buf.as_mut())
    }?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        n = request.write_mem(&buf, req.data.args[2])?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle getxattrat syscall.
fn syscall_getxattrat_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    val: u64,
    len: usize,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

    // Read extended attribute name.
    let name = request.read_xattr(req.data.args[3])?;

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

    let mut fd = Cow::Borrowed(path.dir());
    let mut flags = AtFlags::AT_SYMLINK_NOFOLLOW;
    let base = if path.is_magic_link() {
        // Disallow xattrs on magiclinks.
        return Err(Errno::EPERM);
    } else if path.is_symlink() {
        path.base()
    } else {
        // Open for read-only after access check.
        // Do not follow symbolic links and use RESOLVE_BENEATH.
        // Use O_NOCTTY to avoid acquiring controlling terminal.
        // Use O_NONBLOCK to avoid blocking on pipes/fifos.
        let base = if path.base().is_empty() {
            XPath::from_bytes(b".")
        } else {
            path.base()
        };

        fd = Cow::Owned(
            safe_open(
                fd.as_fd(),
                base,
                OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                ResolveFlag::empty(),
            )?
            .into(),
        );
        flags.insert(AtFlags::AT_EMPTY_PATH);

        XPath::empty()
    };

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf: Vec<u8> = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = getxattrat(fd.as_fd(), base, &name, flags, buf.as_mut())?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        n = request.write_mem(&buf, val)?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle lsetxattr(2) syscall.
fn syscall_lsetxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
    flags: c_int,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

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

    let mut fd = Cow::Borrowed(path.dir());
    let base = match path.typ.as_ref() {
        None => return Err(Errno::ENOENT),
        // Disallow xattrs on magiclinks.
        Some(t) if t.is_magic_link() => return Err(Errno::EPERM),
        Some(t) if t.is_symlink() => path.base(),
        _ => {
            // Open for read-only after access check.
            // Do not follow symbolic links and use RESOLVE_BENEATH.
            // Use O_NOCTTY to avoid acquiring controlling terminal.
            // Use O_NONBLOCK to avoid blocking on pipes/fifos.
            let base = if path.base().is_empty() {
                XPath::from_bytes(b".")
            } else {
                path.base()
            };

            fd = Cow::Owned(
                safe_open(
                    fd.as_fd(),
                    base,
                    OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                    ResolveFlag::empty(),
                )?
                .into(),
            );

            XPath::empty()
        }
    };

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    // Read extended attribute value as necessary.
    let val = if len > 0 {
        Some(request.read_vec(req.data.args[2], len)?)
    } else {
        None
    };

    if base.is_empty() {
        // Working on regular file directly.
        safe_fsetxattr(fd.as_fd(), &name, val.as_deref(), flags)
    } else {
        // Working on symlink using basename.
        // We must change directory using fchdir(2) for safety.
        // We must not follow symlinks in basename.
        safe_fchdir(fd.as_fd())?;
        safe_lsetxattr(base, &name, val.as_deref(), flags)
    }
    .map(|_| request.return_syscall(0))
}

// A helper function to handle setxattr(2) and fsetxattr(2) syscalls.
fn syscall_setxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
    flags: c_int,
    reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

    // SysArg has one element.
    // Reopen as read-only for setxattr(2) after access check.
    // Use O_NOCTTY to avoid acquiring controlling terminal.
    // Use O_NONBLOCK to avoid blocking on pipes/fifos.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();
    let mut fd = Cow::Borrowed(path.dir());
    if reopen {
        let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
        let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
        fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
    }

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name)?;
    }

    // Read extended attribute value as necessary.
    let val = if len > 0 {
        Some(request.read_vec(req.data.args[2], len)?)
    } else {
        None
    };

    safe_fsetxattr(fd.as_fd(), &name, val.as_deref(), flags).map(|_| request.return_syscall(0))
}

// A helper function to handle setxattrat syscall.
fn syscall_setxattrat_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    flags: c_int,
    val: u64,
    len: usize,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

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

    let mut fd = Cow::Borrowed(path.dir());
    let mut atflags = AtFlags::AT_SYMLINK_NOFOLLOW;
    let base = if path.is_magic_link() {
        // Disallow xattrs on magiclinks.
        return Err(Errno::EPERM);
    } else if path.is_symlink() {
        path.base()
    } else {
        // Open for read-only after access check.
        // Do not follow symbolic links and use RESOLVE_BENEATH.
        // Use O_NOCTTY to avoid acquiring controlling terminal.
        // Use O_NONBLOCK to avoid blocking on pipes/fifos.
        let base = if path.base().is_empty() {
            XPath::from_bytes(b".")
        } else {
            path.base()
        };

        fd = Cow::Owned(
            safe_open(
                fd.as_fd(),
                base,
                OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                ResolveFlag::empty(),
            )?
            .into(),
        );
        atflags.insert(AtFlags::AT_EMPTY_PATH);

        XPath::empty()
    };

    // Read extended attribute name.
    let name = request.read_xattr(req.data.args[3])?;

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name)?;
    }

    // Read extended attribute value as necessary.
    let val = if len > 0 {
        Some(request.read_vec(val, len)?)
    } else {
        None
    };
    let val = val.as_ref().map_or(std::ptr::null(), |b| b.as_ptr()) as *const c_void;

    #[expect(clippy::cast_possible_truncation)]
    #[expect(clippy::cast_sign_loss)]
    let args = XattrArgs {
        flags: flags as u32,
        value: val as u64,
        size: len as u32,
    };

    setxattrat(fd.as_fd(), base, &name, &args, atflags).map(|_| request.return_syscall(0))
}

// A helper function to handle listxattr(2) and flistxattr(2) syscalls.
fn syscall_listxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
    reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

    // SysArg has one element.
    // Reopen as read-only for listxattr(2) after access check.
    // Use O_NOCTTY to avoid acquiring controlling terminal.
    // Use O_NONBLOCK to avoid blocking on pipes/fifos.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();
    let mut fd = Cow::Borrowed(path.dir());
    if reopen {
        let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
        let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
        fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
    }

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = flistxattr(fd.as_fd(), buf.as_mut())?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        // Filter out attributes that start with security.*, trusted.*.
        // Deny only if the Sandbox is locked for the process.
        let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
            Cow::Owned(filterxattr(&buf, n)?)
        } else {
            Cow::Borrowed(&buf)
        };

        n = request.write_mem(&buf, req.data.args[1])?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle llistxattr(2) syscall.
fn syscall_llistxattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

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

    let mut fd = Cow::Borrowed(path.dir());
    let base = match path.typ.as_ref() {
        None => return Err(Errno::ENOENT),
        // Disallow xattrs on magiclinks.
        Some(t) if t.is_magic_link() => return Err(Errno::EPERM),
        Some(t) if t.is_symlink() => path.base(),
        _ => {
            // Open for read-only after access check.
            // Do not follow symbolic links and use RESOLVE_BENEATH.
            // Use O_NOCTTY to avoid acquiring controlling terminal.
            // Use O_NONBLOCK to avoid blocking on pipes/fifos.
            let base = if path.base().is_empty() {
                XPath::from_bytes(b".")
            } else {
                path.base()
            };

            fd = Cow::Owned(
                safe_open(
                    fd.as_fd(),
                    base,
                    OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                    ResolveFlag::empty(),
                )?
                .into(),
            );

            XPath::empty()
        }
    };

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = if base.is_empty() {
        // Working on regular file directly.
        flistxattr(fd.as_fd(), buf.as_mut())
    } else {
        // Working on symlink using basename.
        // We must change directory using fchdir(2) for safety.
        // We must not follow symlinks in basename.
        safe_fchdir(fd.as_fd())?;
        llistxattr(base, buf.as_mut())
    }?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        // Filter out attributes that start with security.*, trusted.*.
        // Deny only if the Sandbox is locked for the process.
        let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
            Cow::Owned(filterxattr(&buf, n)?)
        } else {
            Cow::Borrowed(&buf)
        };

        n = request.write_mem(&buf, req.data.args[1])?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle listxattrat syscall.
fn syscall_listxattrat_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    len: usize,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

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

    let mut fd = Cow::Borrowed(path.dir());
    let mut flags = AtFlags::AT_SYMLINK_NOFOLLOW;
    let base = if path.is_magic_link() {
        // Disallow xattrs on magiclinks.
        return Err(Errno::EPERM);
    } else if path.is_symlink() {
        path.base()
    } else {
        // Open for read-only after access check.
        // Do not follow symbolic links and use RESOLVE_BENEATH.
        // Use O_NOCTTY to avoid acquiring controlling terminal.
        // Use O_NONBLOCK to avoid blocking on pipes/fifos.
        let base = if path.base().is_empty() {
            XPath::from_bytes(b".")
        } else {
            path.base()
        };

        fd = Cow::Owned(
            safe_open(
                fd.as_fd(),
                base,
                OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                ResolveFlag::empty(),
            )?
            .into(),
        );
        flags.insert(AtFlags::AT_EMPTY_PATH);

        XPath::empty()
    };

    // Allocate buffer as necessary.
    let mut buf = if len > 0 {
        let mut buf = Vec::new();
        buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
        Some(buf)
    } else {
        None
    };

    // Perform the system call.
    let mut n = listxattrat(fd.as_fd(), base, flags, buf.as_mut())?;

    // Write result to sandbox process memory.
    if let Some(buf) = buf {
        // Filter out attributes that start with security.*, trusted.*.
        // Deny only if the Sandbox is locked for the process.
        let buf = if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
            Cow::Owned(filterxattr(&buf, n)?)
        } else {
            Cow::Borrowed(&buf)
        };

        n = request.write_mem(&buf, req.data.args[3])?;
    }

    #[expect(clippy::cast_possible_wrap)]
    Ok(request.return_syscall(n as i64))
}

// A helper function to handle removexattr(2) and fremovexattr(2) syscalls.
fn syscall_removexattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
    reopen: bool,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

    // SysArg has one element.
    // Reopen as read-only for removexattr(2) after access check.
    // Use O_NOCTTY to avoid acquiring controlling terminal.
    // Use O_NONBLOCK to avoid blocking on pipes/fifos.
    #[expect(clippy::disallowed_methods)]
    let path = args.0.as_ref().unwrap();
    let mut fd = Cow::Borrowed(path.dir());
    if reopen {
        let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
        let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
        fd = Cow::Owned(safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?.into());
    }

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    safe_fremovexattr(fd.as_fd(), &name).map(|_| request.return_syscall(0))
}

// A helper function to handle lremovexattr(2) syscall.
fn syscall_lremovexattr_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[1])?;

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

    let mut fd = Cow::Borrowed(path.dir());
    let base = match path.typ.as_ref() {
        None => return Err(Errno::ENOENT),
        // Disallow xattrs on magiclinks.
        Some(t) if t.is_magic_link() => return Err(Errno::EPERM),
        Some(t) if t.is_symlink() => path.base(),
        _ => {
            // Open for read-only after access check.
            // Do not follow symbolic links and use RESOLVE_BENEATH.
            // Use O_NOCTTY to avoid acquiring controlling terminal.
            // Use O_NONBLOCK to avoid blocking on pipes/fifos.
            let base = if path.base().is_empty() {
                XPath::from_bytes(b".")
            } else {
                path.base()
            };

            fd = Cow::Owned(
                safe_open(
                    fd.as_fd(),
                    base,
                    OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                    ResolveFlag::empty(),
                )?
                .into(),
            );

            XPath::empty()
        }
    };

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    if base.is_empty() {
        // Working on regular file directly.
        safe_fremovexattr(fd.as_fd(), &name)
    } else {
        // Working on symlink using basename.
        // We must change directory using fchdir(2) for safety.
        // We must not follow symlinks in basename.
        safe_fchdir(fd.as_fd())?;
        safe_lremovexattr(base, &name)
    }
    .map(|_| request.return_syscall(0))
}

// A helper function to handle removexattrat(2) syscall.
fn syscall_removexattrat_handler(
    request: &UNotifyEventRequest,
    sandbox: &SandboxGuard,
    args: PathArgs,
) -> Result<ScmpNotifResp, Errno> {
    // Read extended attribute name.
    let req = request.scmpreq;
    let name = request.read_xattr(req.data.args[3])?;

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

    let mut fd = Cow::Borrowed(path.dir());
    let mut flags = AtFlags::AT_SYMLINK_NOFOLLOW;
    let base = if path.is_magic_link() {
        // Disallow xattrs on magiclinks.
        return Err(Errno::EPERM);
    } else if path.is_symlink() {
        path.base()
    } else {
        // Open for read-only after access check.
        // Do not follow symbolic links and use RESOLVE_BENEATH.
        // Use O_NOCTTY to avoid acquiring controlling terminal.
        // Use O_NONBLOCK to avoid blocking on pipes/fifos.
        let base = if path.base().is_empty() {
            XPath::from_bytes(b".")
        } else {
            path.base()
        };

        fd = Cow::Owned(
            safe_open(
                fd.as_fd(),
                base,
                OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
                ResolveFlag::empty(),
            )?
            .into(),
        );
        flags.insert(AtFlags::AT_EMPTY_PATH);

        XPath::empty()
    };

    if !sandbox.flags.allow_unsafe_xattr() && sandbox.locked_for(req.pid()) {
        // Deny security.*, trusted.* extended attributes.
        // Deny with ENODATA for stealth.
        // Deny only if the Sandbox is locked for the process.
        denyxattr(&name).or(Err(Errno::ENODATA))?;
    }

    safe_removexattrat(fd.as_fd(), base, &name, flags).map(|_| request.return_syscall(0))
}

// Validate XATTR_* flags.
fn to_xattr_flags(arg: u64) -> Result<c_int, Errno> {
    #[expect(clippy::cast_possible_truncation)]
    let flags = arg as libc::c_int;

    if !matches!(flags, 0 | XATTR_CREATE | XATTR_REPLACE) {
        return Err(Errno::EINVAL);
    }

    Ok(flags)
}

// Validate AT_* flags for xattr *at syscalls.
fn to_xattrat_flags(arg: u64) -> Result<AtFlags, Errno> {
    to_atflags(arg, AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH)
}

// Capping length converter, used by *{get,list}xattr*
fn to_len_cap(arg: u64, max: usize) -> Result<usize, Errno> {
    Ok(usize::try_from(arg).or(Err(Errno::E2BIG))?.min(max))
}

// Validating length converter, used by *setxattr*
fn to_len_val(arg: u64, max: usize) -> Result<usize, Errno> {
    match usize::try_from(arg).or(Err(Errno::ERANGE)) {
        Ok(len) if len <= max => Ok(len),
        _ => Err(Errno::ERANGE),
    }
}
