use crate::filesystem::fsnotify::{self, FsEvent};
use alloc::sync::Arc;
use system_error::SystemError;

use super::{
    fcntl::AtFlags,
    file::{File, FileFlags, PreopenedFile},
    mount::{MountFSInode, MountFlags},
    permission::PermissionMask,
    syscall::{OpenHow, OpenHowResolve},
    utils::{
        should_remove_sgid_on_chown, user_path_at, user_resolved_path_at, OwnedLookupOutcome,
        ResolvedPath,
    },
    vcore::{check_parent_dir_permission_inode, prepare_open_truncate, vfs_open_truncate},
    FileType, FsPermissionPolicy, IndexNode, InodeMode, SetMetadataMask, MAX_PATHLEN,
    VFS_MAX_FOLLOW_SYMLINK_TIMES,
};
use crate::libs::casting::DowncastArc;
use crate::{filesystem::vfs::syscall::UtimensFlags, process::cred::Kgid};
use crate::{
    process::cred::CAPFlags,
    process::cred::GroupInfo,
    time::{syscall::PosixTimeval, PosixTimeSpec},
};
use crate::{process::ProcessManager, syscall::user_access::vfs_check_and_clone_cstr};
use alloc::string::String;

/// 计算创建文件/目录时使用的最终 mode（应用 umask，并丢弃非法位）。
///
/// Linux 语义要点：
/// - umask 仅作用于 0o777（FsStruct 内保证 umask 只包含 S_IRWXUGO）
/// - open/creat 的 `mode` 参数可包含 0o7777（含 suid/sgid/sticky），文件类型位无效
#[inline]
fn apply_umask_for_create(requested: InodeMode, umask: InodeMode) -> InodeMode {
    // 仅保留“权限/特殊位”，不允许用户通过 open() 设定文件类型位。
    let req = InodeMode::from_bits_truncate(requested.bits() & InodeMode::S_IALLUGO.bits());
    // FsStruct::umask() 已保证只含 S_IRWXUGO，因此 !umask 不会影响 suid/sgid/sticky。
    req & !umask
}

/// chmod/fchmod 的核心：保留文件类型位，仅替换权限/特殊位。
#[inline]
fn chmod_preserve_type(old: InodeMode, new_mode: InodeMode) -> InodeMode {
    let old_type = old.bits() & InodeMode::S_IFMT.bits();
    let new_perm = new_mode.bits() & InodeMode::S_IALLUGO.bits();
    InodeMode::from_bits_truncate(old_type | new_perm)
}

pub(super) fn do_faccessat(
    dirfd: i32,
    path: *const u8,
    mode: InodeMode,
    flags: u32,
) -> Result<usize, SystemError> {
    if (mode.bits() & (!InodeMode::S_IRWXO.bits())) != 0 {
        return Err(SystemError::EINVAL);
    }

    if (flags
        & (!((AtFlags::AT_EACCESS | AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH).bits()
            as u32)))
        != 0
    {
        return Err(SystemError::EINVAL);
    }

    // let follow_symlink = flags & AtFlags::AT_SYMLINK_NOFOLLOW.bits() as u32 == 0;

    let path = vfs_check_and_clone_cstr(path, Some(MAX_PATHLEN))?;
    let path = path.to_str().map_err(|_| SystemError::EINVAL)?;
    // log::debug!("do_faccessat path: {:?}", path);

    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;

    // 如果找不到文件，则返回错误码ENOENT
    let inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
    if mode.bits() == 0 {
        return Ok(0);
    }

    let mut mask = PermissionMask::empty();
    if mode.contains(InodeMode::S_IROTH) {
        mask |= PermissionMask::MAY_READ;
    }
    if mode.contains(InodeMode::S_IWOTH) {
        mask |= PermissionMask::MAY_WRITE;
    }
    if mode.contains(InodeMode::S_IXOTH) {
        mask |= PermissionMask::MAY_EXEC;
    }

    let resolved = ResolvedPath::new(inode)?;
    let inode = resolved.inode();
    let metadata = inode.metadata()?;
    match inode.fs().permission_policy() {
        FsPermissionPolicy::Dac => {
            super::permission::check_inode_permission(&inode, &metadata, mask)?;
        }
        FsPermissionPolicy::Remote => match inode.check_access(mask) {
            Ok(()) => {}
            Err(SystemError::ENOSYS) => {}
            Err(e) => return Err(e),
        },
    }

    return Ok(0);
}

pub fn do_fchmodat(dirfd: i32, path: *const u8, mode: InodeMode) -> Result<usize, SystemError> {
    let path = vfs_check_and_clone_cstr(path, Some(MAX_PATHLEN))?;
    let path = path.to_str().map_err(|_| SystemError::EINVAL)?;

    if path.is_empty() {
        return Err(SystemError::ENOENT);
    }

    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;

    let target_inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;

    do_fchmod(target_inode, mode)
}

/// fchmod：对已解析的 inode 进行 chmod（供 `sys_fchmod` 复用）。
pub fn do_fchmod(inode: Arc<dyn IndexNode>, mode: InodeMode) -> Result<usize, SystemError> {
    let cred = ProcessManager::current_pcb().cred();
    let mut metadata = inode.metadata()?;

    // Linux 语义：chmod/fchmod 需要是 inode 所有者，或具备 CAP_FOWNER
    if cred.fsuid.data() != metadata.uid && !cred.has_capability(CAPFlags::CAP_FOWNER) {
        return Err(SystemError::EPERM);
    }

    metadata.mode = chmod_preserve_type(metadata.mode, mode);
    metadata.ctime = PosixTimeSpec::now();
    inode.set_metadata_masked(&metadata, SetMetadataMask::MODE | SetMetadataMask::CTIME)?;
    // fsnotify: attribute change → IN_ATTRIB.
    fsnotify::fsnotify_inode(FsEvent::ATTRIB, &inode);
    Ok(0)
}

pub fn do_fchownat(
    dirfd: i32,
    path: &str,
    uid: usize,
    gid: usize,
    flag: AtFlags,
) -> Result<usize, SystemError> {
    // 检查flag是否合法
    let allowed_flags = AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH;
    if flag.intersects(!allowed_flags) {
        return Err(SystemError::EINVAL);
    }

    let follow_symlink = !flag.contains(AtFlags::AT_SYMLINK_NOFOLLOW);
    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;

    let inode = if follow_symlink {
        inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)
    } else {
        inode.lookup_follow_symlink2(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES, false)
    }?;

    return chown_common(inode, uid, gid);
}

fn chown_common(inode: Arc<dyn IndexNode>, uid: usize, gid: usize) -> Result<usize, SystemError> {
    // The syscall ABI declares these arguments as 32-bit uid_t/gid_t. Truncate
    // register-width inputs before interpreting (uid_t)-1 as "no change".
    let uid = uid as u32 as usize;
    let gid = gid as u32 as usize;
    let mut meta = inode.metadata()?;
    let cred = ProcessManager::current_pcb().cred();
    let current_uid = cred.uid.data();
    let current_gid = cred.gid.data();
    let mut group_info = GroupInfo::default();
    if let Some(info) = cred.group_info.as_ref() {
        group_info = info.clone();
    }

    // Linux semantics: uid/gid passed in as (uid_t)-1/(gid_t)-1 mean "do not change".
    let is_no_change = |id: usize| id == u32::MAX as usize;
    let change_uid = !is_no_change(uid);
    let change_gid = !is_no_change(gid);
    let old_gid = meta.gid;
    let old_mode = meta.mode;

    // 检查权限
    match current_uid {
        0 => {
            if change_uid {
                meta.uid = uid;
            }
            if change_gid {
                meta.gid = gid;
            }
        }
        _ => {
            // 非文件所有者不能更改信息，且不能更改uid
            if current_uid != meta.uid || (change_uid && uid != meta.uid) {
                return Err(SystemError::EPERM);
            }
            if change_gid {
                if gid != current_gid && !group_info.gids.contains(&Kgid::from(gid)) {
                    return Err(SystemError::EPERM);
                }
                meta.gid = gid;
            }
        }
    }

    // Linux 语义：
    // - chown 目录：不清除 suid/sgid（setgid 目录用于组继承）
    // - chown 普通文件：总是清除 suid；sgid 按 Linux setattr_should_drop_sgid 规则清理
    //   - 若 S_IXGRP 置位：无条件清除 sgid
    //   - 否则（mandatory locking 语义）：仅当调用者不在文件所属组且无 CAP_FSETID 时清除
    if meta.file_type != FileType::Dir {
        // suid always must be killed on chown for non-directories
        meta.mode.remove(InodeMode::S_ISUID);

        if should_remove_sgid_on_chown(meta.mode, old_gid, current_gid, &cred, &group_info) {
            meta.mode.remove(InodeMode::S_ISGID);
        }
    }
    let mut mask = SetMetadataMask::empty();
    if change_uid {
        mask.insert(SetMetadataMask::UID);
    }
    if change_gid {
        mask.insert(SetMetadataMask::GID);
    }
    if meta.mode != old_mode {
        mask.insert(SetMetadataMask::MODE);
    }
    // Linux chown always updates ctime, even for chown(-1, -1). A ctime-only
    // update is not, however, exposed as IN_ATTRIB.
    meta.ctime = PosixTimeSpec::now();
    mask.insert(SetMetadataMask::CTIME);
    inode.set_metadata_masked(&meta, mask)?;
    if mask.intersects(SetMetadataMask::UID | SetMetadataMask::GID | SetMetadataMask::MODE) {
        fsnotify::fsnotify_inode(FsEvent::ATTRIB, &inode);
    }

    return Ok(0);
}

pub fn ksys_fchown(fd: i32, uid: usize, gid: usize) -> Result<usize, SystemError> {
    let fd_table = &ProcessManager::current_pcb().fd_table();
    let fd_table = fd_table.read();

    let file = fd_table.get_file_by_fd(fd).ok_or(SystemError::EBADF)?;

    // Linux 语义：对 O_PATH fd 执行 fchown 应返回 EBADF
    if file.flags().contains(FileFlags::O_PATH) {
        return Err(SystemError::EBADF);
    }

    let inode = file.inode();

    drop(fd_table);
    return chown_common(inode, uid, gid);
}

pub fn do_sys_open(
    dfd: i32,
    path: &str,
    o_flags: FileFlags,
    mode: InodeMode,
) -> Result<usize, SystemError> {
    let how = OpenHow::new(o_flags, mode, OpenHowResolve::empty());

    return do_sys_openat2(dfd, path, how);
}

fn do_sys_openat2(dirfd: i32, path: &str, how: OpenHow) -> Result<usize, SystemError> {
    // log::debug!("openat2: dirfd: {}, path: {}, how: {:?}",dirfd, path, how);
    // Linux 6.6 rejects this contradictory creation request before lookup, so
    // the result must not depend on whether the pathname already exists.
    if how.o_flags.contains(FileFlags::O_CREAT) && how.o_flags.contains(FileFlags::O_DIRECTORY) {
        return Err(SystemError::EINVAL);
    }
    // Match Linux's get_unused_fd_flags() ordering: reserve the descriptor
    // before pathname lookup and before create/truncate/open side effects.
    let cloexec = how.o_flags.contains(FileFlags::O_CLOEXEC);
    let fd_table = ProcessManager::current_pcb().fd_table();
    let reservation = fd_table.write().reserve_fd(cloexec)?;
    let open_result = (|| -> Result<File, SystemError> {
        let path = path.trim();
        // Linux makes O_CREAT|O_EXCL imply O_NOFOLLOW for the final component.
        let follow_symlink = !(how.o_flags.contains(FileFlags::O_NOFOLLOW)
            || how.o_flags.contains(FileFlags::O_CREAT) && how.o_flags.contains(FileFlags::O_EXCL));
        // Check for an empty path
        if path.is_empty() {
            return Err(SystemError::ENOENT);
        }

        // Check for a trailing slash on the path: if it ends with a slash, the target must be a directory
        let path_ends_with_slash = path.ends_with('/');

        let (start_path, path) =
            user_resolved_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
        let inode_begin = start_path.inode();
        let resolved = inode_begin.lookup_follow_symlink_or_missing_owned(
            &start_path,
            &path,
            VFS_MAX_FOLLOW_SYMLINK_TIMES,
            follow_symlink,
        );
        let mut created = false;
        let mut preopened: Option<PreopenedFile> = None;
        let resolved = match resolved {
            Ok(OwnedLookupOutcome::Found(resolved)) => resolved,
            Ok(OwnedLookupOutcome::MissingFinal {
                parent: parent_resolved,
                name: filename,
                must_be_dir,
            }) => {
                // The file does not exist and must be created
                if how.o_flags.contains(FileFlags::O_CREAT)
                    && !how.o_flags.contains(FileFlags::O_DIRECTORY)
                {
                    // A trailing slash may come from the expanded symlink target,
                    // not only from the original userspace pathname.
                    if must_be_dir {
                        return Err(SystemError::EISDIR);
                    }

                    // Check the filename length
                    if filename.len() > crate::filesystem::vfs::NAME_MAX {
                        return Err(SystemError::ENAMETOOLONG);
                    }
                    let parent_inode = parent_resolved.inode();
                    let parent_md = parent_inode.metadata()?;
                    // The parent must be a directory
                    if parent_md.file_type != FileType::Dir {
                        return Err(SystemError::ENOTDIR);
                    }
                    // Linux semantics: creating a file requires write+search (W+X) permission on the parent directory
                    check_parent_dir_permission_inode(&parent_inode, &parent_md)?;

                    // Compute the creation mode: apply umask, following open/creat semantics
                    let pcb = ProcessManager::current_pcb();
                    let umask = pcb.fs_struct().umask();
                    let create_mode = apply_umask_for_create(how.mode, umask);
                    // Let filesystems with an atomic create/open operation carry
                    // the returned handle directly into the new File.  ENOSYS
                    // preserves the generic create-then-open fallback.
                    let mut create_flags = how.o_flags;
                    if create_flags.contains(FileFlags::O_EXCL) {
                        create_flags.remove(FileFlags::O_TRUNC);
                    }
                    let notify = || {
                        fsnotify::fsnotify(
                            FsEvent::CREATE,
                            Some((&parent_inode, &filename)),
                            None,
                            0,
                        );
                    };
                    let inode: Arc<dyn IndexNode> = if let Some(mounted) =
                        parent_inode.clone().downcast_arc::<MountFSInode>()
                    {
                        let (inode, opened) = mounted.create_file_with_post_commit(
                            &filename,
                            create_mode,
                            &create_flags,
                            notify,
                        )?;
                        preopened = opened;
                        inode
                    } else {
                        let inode = match parent_inode.create_and_open(
                            &filename,
                            create_mode,
                            &create_flags,
                        ) {
                            Ok(opened) => {
                                let inode = opened.inode();
                                preopened = Some(opened);
                                inode
                            }
                            Err(SystemError::ENOSYS) => {
                                parent_inode.create(&filename, FileType::File, create_mode)?
                            }
                            Err(err) => return Err(err),
                        };
                        fsnotify::fsnotify(
                            FsEvent::CREATE,
                            Some((&parent_inode, &filename)),
                            Some(&inode),
                            0,
                        );
                        inode
                    };
                    created = true;
                    let created_path = ResolvedPath::new(inode)?;
                    drop(parent_resolved);
                    created_path
                } else {
                    return Err(SystemError::ENOENT);
                }
            }
            Err(errno) => return Err(errno),
        };
        drop(start_path);
        let inode = resolved.inode();
        let metadata = inode.metadata()?;
        let file_type: FileType = metadata.file_type;

        if !how.o_flags.contains(FileFlags::O_PATH)
            && (file_type == FileType::CharDevice || file_type == FileType::BlockDevice)
            && inode.mount_flags().contains(MountFlags::NODEV)
        {
            return Err(SystemError::EACCES);
        }

        // If the path ends with a slash but the target is not a directory, return ENOTDIR
        if path_ends_with_slash && file_type != FileType::Dir {
            return Err(SystemError::ENOTDIR);
        }
        // The file already exists and O_CREAT|O_EXCL was specified
        if how.o_flags.contains(FileFlags::O_CREAT)
            && how.o_flags.contains(FileFlags::O_EXCL)
            && !created
        {
            return Err(SystemError::EEXIST);
        }
        if how.o_flags.contains(FileFlags::O_NOFOLLOW)
            && !how.o_flags.contains(FileFlags::O_PATH)
            && file_type == FileType::SymLink
        {
            return Err(SystemError::ELOOP);
        }
        // Using O_CREAT on an existing directory is treated as an error
        if how.o_flags.contains(FileFlags::O_CREAT) && !created && file_type == FileType::Dir {
            return Err(SystemError::EISDIR);
        }
        // Directory checks
        if file_type == FileType::Dir {
            // O_TRUNC is not supported on directories
            if how.o_flags.contains(FileFlags::O_TRUNC) {
                return Err(SystemError::EISDIR);
            }
            // Write access is not allowed on directories
            let acc_mode = how.o_flags.access_flags();
            if acc_mode == FileFlags::O_WRONLY || acc_mode == FileFlags::O_RDWR {
                return Err(SystemError::EISDIR);
            }
        }
        // Non-O_PATH opens must check access permission (read/write/truncate)
        // Linux semantics: if this open() triggered creation, it must not reject this
        // open()'s access mode because of the "new inode's mode"; it takes effect on a subsequent reopen().
        if !how.o_flags.contains(FileFlags::O_PATH) && !created {
            let acc_mode = how.o_flags.access_flags();
            let mut need = PermissionMask::empty();
            match acc_mode {
                FileFlags::O_RDONLY => need.insert(PermissionMask::MAY_READ),
                FileFlags::O_WRONLY => need.insert(PermissionMask::MAY_WRITE),
                FileFlags::O_RDWR => {
                    need.insert(PermissionMask::MAY_READ | PermissionMask::MAY_WRITE)
                }
                _ => {}
            }
            if how.o_flags.contains(FileFlags::O_TRUNC) {
                need.insert(PermissionMask::MAY_WRITE);
            }
            if !need.is_empty() {
                super::permission::check_inode_permission(&inode, &metadata, need)?;
            }
        }

        // If a directory was to be opened but the target is not a directory
        if how.o_flags.contains(FileFlags::O_DIRECTORY) && file_type != FileType::Dir {
            return Err(SystemError::ENOTDIR);
        }

        // Linux resolves path/file-type and ordinary DAC errors before applying
        // O_NOATIME's owner/CAP_FOWNER restriction. In particular, O_NOFOLLOW on
        // a final symlink must report ELOOP rather than an ownership-based EPERM.
        if how.o_flags.contains(FileFlags::O_NOATIME) {
            super::permission::check_noatime_permission(&metadata)?;
        }

        // Linux rejects unsupported direct I/O from do_dentry_open(), after
        // may_open() has enforced O_NOATIME ownership.
        if file_type == FileType::Dir && how.o_flags.contains(FileFlags::O_DIRECT) {
            return Err(SystemError::EINVAL);
        }

        // Linux reaches the socket file operation only after DAC and O_NOATIME
        // checks; sock_no_open() then rejects pathname-based opens with ENXIO.
        if file_type == FileType::Socket {
            return Err(SystemError::ENXIO);
        }

        // Existing regular files opened with O_TRUNC are modified after the
        // filesystem open succeeds. Newly created files are already empty and
        // Linux clears O_TRUNC for this phase.
        let do_truncate =
            !created && file_type == FileType::File && how.o_flags.contains(FileFlags::O_TRUNC);
        let truncate_metadata = do_truncate.then(|| metadata.clone());
        let (inode, mount_guard, operation_guard) = resolved.into_parts();
        let file: File = match preopened {
            Some(opened) => File::new_preopened_with_mount_guard(
                opened,
                how.o_flags,
                mount_guard,
                operation_guard,
            )?,
            None => File::new_with_mount_guard(inode, how.o_flags, mount_guard, operation_guard)?,
        };

        // Linux emits OPEN from do_dentry_open() before handle_truncate().
        file.notify_open_event();
        if let Some(metadata_before_open) = truncate_metadata {
            let context = prepare_open_truncate(metadata_before_open);
            vfs_open_truncate(&file, &context)?;
        }
        Ok(file)
    })();

    let file = match open_result {
        Ok(file) => file,
        Err(error) => {
            fd_table.write().release_reserved_fd(reservation);
            return Err(error);
        }
    };
    let result = fd_table
        .write()
        .install_reserved_fd(reservation, file)
        .map(|fd| fd as usize);
    result
}

/// 为exec打开可执行文件
///
/// 这个函数复用do_sys_openat2的路径解析和权限检查逻辑，
/// 返回Arc<File>和文件描述符，并将文件加入fd table。
///
/// ## 参数
/// - `dirfd`: 目录文件描述符
/// - `path`: 文件路径
///
/// ## 返回值
/// - `Ok((Arc<File>, i32))`: 成功打开的文件和分配的文件描述符
/// - `Err(SystemError)`: 错误
pub fn do_open_execat(dirfd: i32, path: &str) -> Result<Arc<File>, SystemError> {
    do_open_execat_with_flags(dirfd, path, AtFlags::empty())
}

/// 为 execve/execveat 打开可执行文件，保留 `AT_EMPTY_PATH` / `AT_SYMLINK_NOFOLLOW` 语义。
pub fn do_open_execat_with_flags(
    dirfd: i32,
    path: &str,
    flags: AtFlags,
) -> Result<Arc<File>, SystemError> {
    if flags.contains(!(AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH)) {
        return Err(SystemError::EINVAL);
    }

    if path.is_empty() && !flags.contains(AtFlags::AT_EMPTY_PATH) {
        return Err(SystemError::ENOENT);
    }

    let inode = if path.is_empty() {
        ProcessManager::current_pcb()
            .fd_table()
            .read()
            .get_file_by_fd(dirfd)
            .ok_or(SystemError::EBADF)?
            .inode()
    } else {
        let (inode_begin, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
        if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
            let inode =
                inode_begin.lookup_follow_symlink2(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES, false)?;
            if inode.metadata()?.file_type == FileType::SymLink {
                return Err(SystemError::ELOOP);
            }
            inode
        } else {
            inode_begin.lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?
        }
    };

    let metadata = inode.metadata()?;
    let file_type = metadata.file_type;

    // 必须是普通文件
    if file_type != FileType::File {
        return Err(SystemError::EACCES);
    }

    if inode.mount_flags().contains(MountFlags::NOEXEC) {
        return Err(SystemError::EACCES);
    }

    super::permission::check_inode_permission(&inode, &metadata, PermissionMask::MAY_EXEC)?;

    // 创建File对象，使用O_RDONLY | O_CLOEXEC
    let file = File::new(inode, FileFlags::O_RDONLY | FileFlags::O_CLOEXEC)?;
    file.notify_open_event();

    Ok(Arc::new(file))
}

/// On Linux, futimens() is a library function implemented on top of
/// the utimensat() system call.  To support this, the Linux
/// utimensat() system call implements a nonstandard feature: if
/// pathname is NULL, then the call modifies the timestamps of the
/// file referred to by the file descriptor dirfd (which may refer to
/// any type of file).
pub fn do_utimensat(
    dirfd: i32,
    pathname: Option<String>,
    times: Option<[PosixTimeSpec; 2]>,
    flags: UtimensFlags,
) -> Result<usize, SystemError> {
    const UTIME_NOW: i64 = (1i64 << 30) - 1i64;
    const UTIME_OMIT: i64 = (1i64 << 30) - 2i64;
    // log::debug!("do_utimensat: dirfd:{}, pathname:{:?}, times:{:?}, flags:{:?}", dirfd, pathname, times, flags);

    // Linux semantics: if both timestamps are UTIME_OMIT, the call succeeds
    // without accessing the file at all (OmitNoop test).
    if let Some([atime, mtime]) = times {
        if atime.tv_nsec == UTIME_OMIT && mtime.tv_nsec == UTIME_OMIT {
            return Ok(0);
        }
    }

    let inode = match pathname {
        Some(path) => {
            let (inode_begin, path) =
                user_path_at(&ProcessManager::current_pcb(), dirfd, path.as_str())?;
            let inode = if flags.contains(UtimensFlags::AT_SYMLINK_NOFOLLOW) {
                inode_begin.lookup(path.as_str())?
            } else {
                inode_begin.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?
            };
            inode
        }
        None => {
            // Linux-specific extension: pathname == NULL means operate on the
            // file referred to by dirfd (futimens). However, some combinations
            // are invalid and must return specific errors.

            // When dirfd is AT_FDCWD and pathname is NULL, Linux returns EFAULT.
            if dirfd == AtFlags::AT_FDCWD.bits() {
                return Err(SystemError::EFAULT);
            }

            let binding = ProcessManager::current_pcb().fd_table();
            let fd_table_guard = binding.write();
            let file = fd_table_guard
                .get_file_by_fd(dirfd)
                .ok_or(SystemError::EBADF)?;

            // If the file descriptor was opened with O_PATH, futimesat must fail
            // with EBADF instead of operating on it.
            if file.flags().contains(FileFlags::O_PATH) {
                return Err(SystemError::EBADF);
            }

            file.inode()
        }
    };
    let now = PosixTimeSpec::now();
    let mut meta = inode.metadata()?;

    let both_omit =
        times.is_some_and(|times| times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT);
    if both_omit {
        return Ok(0);
    }
    let both_now =
        times.is_none_or(|times| times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == UTIME_NOW);
    check_timestamp_update_permission(&inode, &meta, both_now)?;

    if let Some([atime, mtime]) = times {
        let mut mask = SetMetadataMask::empty();
        if atime.tv_nsec == UTIME_NOW {
            meta.atime = now;
            mask.insert(SetMetadataMask::ATIME);
        } else if atime.tv_nsec != UTIME_OMIT {
            meta.atime = atime;
            mask.insert(SetMetadataMask::ATIME);
        }
        if mtime.tv_nsec == UTIME_NOW {
            meta.mtime = now;
            mask.insert(SetMetadataMask::MTIME);
        } else if mtime.tv_nsec != UTIME_OMIT {
            meta.mtime = mtime;
            mask.insert(SetMetadataMask::MTIME);
        }
        if !mask.is_empty() {
            meta.ctime = now;
            mask.insert(SetMetadataMask::CTIME);
            if both_now {
                mask.insert(SetMetadataMask::TIMES_BY_WRITE);
            }
        }
        inode.set_metadata_masked(&meta, mask)?;
    } else {
        meta.atime = now;
        meta.mtime = now;
        meta.ctime = now;
        inode.set_metadata_masked(
            &meta,
            SetMetadataMask::ATIME
                | SetMetadataMask::MTIME
                | SetMetadataMask::CTIME
                | SetMetadataMask::TIMES_BY_WRITE,
        )?;
    }
    // fsnotify: timestamp change → IN_ATTRIB.
    fsnotify::fsnotify_inode(FsEvent::ATTRIB, &inode);
    return Ok(0);
}

pub fn do_utimes(path: &str, times: Option<[PosixTimeval; 2]>) -> Result<usize, SystemError> {
    // log::debug!("do_utimes: path:{:?}, times:{:?}", path, times);
    let (inode_begin, path) = user_path_at(
        &ProcessManager::current_pcb(),
        AtFlags::AT_FDCWD.bits(),
        path,
    )?;
    let inode = inode_begin.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
    let mut meta = inode.metadata()?;
    check_timestamp_update_permission(&inode, &meta, times.is_none())?;

    if let Some([atime, mtime]) = times {
        meta.atime = PosixTimeSpec::from(atime);
        meta.mtime = PosixTimeSpec::from(mtime);
        meta.ctime = PosixTimeSpec::now();
        inode.set_metadata_masked(
            &meta,
            SetMetadataMask::ATIME | SetMetadataMask::MTIME | SetMetadataMask::CTIME,
        )?;
    } else {
        let now = PosixTimeSpec::now();
        meta.atime = now;
        meta.mtime = now;
        meta.ctime = now;
        inode.set_metadata_masked(
            &meta,
            SetMetadataMask::ATIME
                | SetMetadataMask::MTIME
                | SetMetadataMask::CTIME
                | SetMetadataMask::TIMES_BY_WRITE,
        )?;
    }
    // fsnotify: a successful utimes(2) timestamp update → IN_ATTRIB.
    fsnotify::fsnotify_inode(FsEvent::ATTRIB, &inode);
    return Ok(0);
}

fn check_timestamp_update_permission(
    inode: &Arc<dyn IndexNode>,
    metadata: &super::Metadata,
    may_use_write_permission: bool,
) -> Result<(), SystemError> {
    let cred = ProcessManager::current_pcb().cred();
    if cred.fsuid.data() == metadata.uid || cred.has_capability(CAPFlags::CAP_FOWNER) {
        return Ok(());
    }
    if may_use_write_permission {
        return super::permission::check_inode_permission(
            inode,
            metadata,
            PermissionMask::MAY_WRITE,
        );
    }
    Err(SystemError::EPERM)
}
