首页
/ SerenityOS access() 系统调用解析:文件访问权限检查的实现与使用指南

SerenityOS access() 系统调用解析:文件访问权限检查的实现与使用指南

2026-09-09 15:00:24作者:平淮齐Percy

本指南围绕 SerenityOS 用户手册中 access 手册页(Base/usr/share/man/man2/access.md)展开,系统讲解 access() 的语义、四种模式标志、返回值与错误处理,并结合内核源码(Kernel/Syscalls/faccessat.cppKernel/FileSystem/VirtualFileSystem.cpp)揭示其底层实现原理。读完本文,你将掌握如何在 SerenityOS 用户态程序中使用 access() 检查文件可访问性,并理解从 LibC 封装到 VFS 权限校验的完整调用链。

函数概览

access() 用于检查指定路径上的文件是否存在,以及当前用户是否可按给定模式访问该文件。

头文件与原型

#include <unistd.h>

int access(const char* path, int mode);
  • path:要检查的文件路径。
  • mode:要检查的访问模式,可取下列标志之一(或按位组合,见下节)。
  • 返回值:文件可按指定模式访问时返回 0;否则返回 -1 并设置 errno 描述错误原因。

在 SerenityOS 中,该函数声明位于 Userland/Libraries/LibC/unistd.h,POSIX 兼容实现位于 Userland/Libraries/LibC/unistd.cpp

mode 参数:四种访问模式标志

mode 参数支持以下四个标志,定义于 Kernel/API/POSIX/unistd.h

标志 数值 含义
F_OK 0 检查文件本身是否可访问(即是否存在)
R_OK 4 检查文件是否可读
W_OK 2 检查文件是否可写
X_OK 1 检查文件是否可作为程序执行

从源码可见这些标志按位定义,R_OKW_OKX_OK 之间可以按位或组合使用(例如 access(path, R_OK | W_OK) 同时检查读写权限),而 F_OK 数值为 0,仅用于单独判断文件是否存在。

if (access("/etc/passwd", R_OK) == 0) {
    // 当前用户可以读取该文件
}

if (access("/bin/sh", X_OK) == 0) {
    // 当前用户可以执行该程序
}

返回值与错误处理

  • 文件确实可按指定 mode 访问时,access() 返回 0
  • 否则返回 -1,并通过 errno 说明具体错误。

常见 errno 含义包括:

  • EACCES:权限不足,无法按请求的模式访问文件。
  • ENOENT:路径不存在。
  • EROFS:文件所在文件系统只读,无法写入(仅在使用 W_OK 时可能出现,见下文内核实现)。

源码级实现:从 LibC 到内核

access() 并非独立系统调用,而是建立在 faccessat() 之上的封装,最终经由 VFS(虚拟文件系统)完成权限判定。下面按调用链自顶向下展开。

第一步:LibC 层封装

Userland/Libraries/LibC/unistd.cpp 中,access() 直接转发给 faccessat()

int access(char const* pathname, int mode)
{
    return faccessat(AT_FDCWD, pathname, mode, 0);
}

int faccessat(int dirfd, char const* pathname, int mode, int flags)
{
    if (!pathname) {
        errno = EFAULT;
        return -1;
    }

    Syscall::SC_faccessat_params params { dirfd, { pathname, strlen(pathname) }, mode, flags };
    int rc = syscall(SC_faccessat, &params);
    __RETURN_WITH_ERRNO(rc, rc, -1);
}

关键点:

  • access()AT_FDCWD 作为目录描述符,即以进程当前工作目录为基准解析相对路径。
  • 传入的空指针会被提前拦截并返回 EFAULT,避免进入内核。
  • 最终通过 SC_faccessat 系统调用号进入内核。

第二步:内核系统调用入口

内核侧对应实现为 sys$faccessat(),位于 Kernel/Syscalls/faccessat.cpp

ErrorOr<FlatPtr> Process::sys$faccessat(Userspace<Syscall::SC_faccessat_params const*> user_params)
{
    VERIFY_NO_PROCESS_BIG_LOCK(this);
    TRY(require_promise(Pledge::rpath));
    auto params = TRY(copy_typed_from_user(user_params));
    auto pathname = TRY(get_syscall_path_argument(params.pathname));

    if ((params.flags & ~(AT_SYMLINK_NOFOLLOW | AT_EACCESS)) != 0)
        return EINVAL;

    auto flags = AccessFlags::None;
    if (params.flags & AT_SYMLINK_NOFOLLOW)
        flags |= AccessFlags::DoNotFollowSymlinks;
    if (params.flags & AT_EACCESS)
        flags |= AccessFlags::EffectiveAccess;

    CustodyBase base(params.dirfd, pathname->view());
    TRY(VirtualFileSystem::access(vfs_root_context(), credentials(), pathname->view(), params.mode, base, flags));
    return 0;
}

该入口值得注意的实现细节:

  • pledge 承诺:调用需要进程已承诺 rpath(读取路径解析)权限,这体现了 SerenityOS 的 pledge 安全模型——即使拿到系统调用入口,未被承诺的进程也会被拒绝。
  • flags 校验:除 AT_SYMLINK_NOFOLLOW(不跟随符号链接)与 AT_EACCESS(使用有效 ID 而非实际 ID 检查)外,其余 flags 一律返回 EINVAL
  • 路径参数通过 get_syscall_path_argument() 安全地从用户态拷贝,防止内核直接解引用用户指针。

第三步:VFS 权限判定

真正的权限检查落在 Kernel/FileSystem/VirtualFileSystem.cppVirtualFileSystem::access()

ErrorOr<void> VirtualFileSystem::access(VFSRootContext const& vfs_root_context, Credentials const& credentials, StringView path, int mode, CustodyBase const& base, AccessFlags access_flags)
{
    auto should_follow_symlinks = !has_flag(access_flags, AccessFlags::DoNotFollowSymlinks);
    auto custody = TRY(resolve_path(vfs_root_context, credentials, path, base, nullptr, should_follow_symlinks ? 0 : O_NOFOLLOW_NOERROR));

    auto& inode = custody->inode();
    auto metadata = inode.metadata();
    auto use_effective_ids = has_flag(access_flags, AccessFlags::EffectiveAccess) ? UseEffectiveIDs::Yes : UseEffectiveIDs::No;
    if (mode & R_OK) {
        if (!metadata.may_read(credentials, use_effective_ids))
            return EACCES;
    }
    if (mode & W_OK) {
        if (!metadata.may_write(credentials, use_effective_ids))
            return EACCES;
        if (custody->is_readonly())
            return EROFS;
    }
    if (mode & X_OK) {
        if (!metadata.may_execute(credentials, use_effective_ids))
            return EACCES;
    }
    return {};
}

该实现揭示了 access() 的完整判定逻辑:

  1. 路径解析:先通过 resolve_path() 将路径解析为 inode。默认跟随符号链接;若指定 AT_SYMLINK_NOFOLLOW,则以 O_NOFOLLOW_NOERROR 语义解析。
  2. 按位检查:对 mode 中设置的每一位分别调用 inode 元数据上的 may_read / may_write / may_execute 方法(这些方法内部综合权限位、属主、属组与补充组进行判定)。
  3. 写权限特判:即使文件权限允许写入,只要所在挂载点或 custody 为只读(is_readonly()),就返回 EROFS——这正是只读文件系统上 access(path, W_OK) 失败的来源。
  4. 有效 ID 与真实 ID:默认按进程真实 UID/GID 判定;指定 AT_EACCESS 时改为按有效 ID 判定,与 POSIX 语义一致。

由于 mode 采用位标志,F_OK(数值 0)不会命中上述任一分支,因此该函数天然将"纯存在性检查"实现为仅做路径解析与元数据读取,权限位检查被跳过。

使用场景与注意事项

典型用法

access() 常见于以下场景:

  • 程序启动时探测配置文件、数据目录是否存在且可读写;
  • 检查外部命令是否在可执行路径上(X_OK);
  • 在尝试打开文件前预判是否具备读取权限。

注意:检查与使用之间存在 TOCTOU 竞态

access() 基于调用时的路径与凭据做检查,检查通过与实际 open()/exec() 之间文件状态可能发生变化(即 time-of-check to time-of-use 竞态)。对于安全敏感场景,更稳妥的做法是直接尝试打开文件并根据 errno 处理错误,而不是先 access() 再操作。

与 pledge 的配合

如前所述,内核入口要求进程持有 Pledge::rpath 承诺。在 SerenityOS 中编写使用 access() 的程序时,需确保进程 pledge 了 rpath,否则系统调用会被拒绝。这一设计将"检查文件可访问性"归入路径读取类能力,与其他只读路径操作保持一致。

小结

  • access(path, mode) 检查当前用户对文件的访问能力,mode 可取 F_OK / R_OK / W_OK / X_OK 或其组合。
  • 成功返回 0,失败返回 -1 并设置 errno(典型为 EACCESENOENTEROFS)。
  • 在 SerenityOS 中,access()faccessat(AT_FDCWD, ...) 的封装,最终由 SC_faccessat 系统调用进入内核,经 VFS 层对 inode 元数据逐位校验权限。
  • 源码参考:LibC 封装系统调用入口VFS 权限判定模式标志定义
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395