Linux C中access函数如何正确使用?

99ANYc3cd6
预计阅读时长 18 分钟
位置: 首页 C语言 正文

access() 是一个标准 C 库函数(在 <unistd.h> 头文件中定义),用于检查一个进程对某个文件或目录的访问权限,它提供了一种简单的方式来验证你是否有权执行某个操作,而不会真正地打开文件,这在某些场景下非常有用。

linux c语言 access
(图片来源网络,侵删)

函数原型

#include <unistd.h>
int access(const char *pathname, int mode);

参数说明

pathname

  • 这是一个以 null 结尾的字符串,指定了你要检查的文件或目录的路径。
  • 可以是相对路径(如 ./myfile.txt)或绝对路径(如 /home/user/myfile.txt)。

mode

  • 这是一个整型值,指定了要检查的访问模式,它由以下宏定义组合而成(通过按位或 组合)。
  • 你至少需要指定一个模式,通常使用 F_OK 来检查文件是否存在,然后组合其他模式来检查权限。
含义
F_OK 0 检查文件是否存在,这是最基本的一种检查。
R_OK 4 检查文件是否可读。
W_OK 2 检查文件是否可写。
X_OK 1 检查文件是否可执行。

组合示例:

  • R_OK | W_OK:检查文件是否同时可读和可写。
  • F_OK | R_OK:检查文件是否存在并且可读。

返回值

  • 成功时:返回 0
  • 失败时:返回 -1,并设置 errno 来指示具体的错误原因。

常见的 errno 值:

errno 宏值 含义 描述
EACCES 权限不够 pathname 所请求的访问权限被拒绝。
ENOENT 文件或目录不存在 pathname 指定的路径不存在。
EFAULT 地址错误 pathname 指向了无效的内存地址。
ELOOP 符号链接过多 解析 pathname 时遇到了太多的符号链接。
ENAMETOOLONG 文件名过长 pathname 过长。
ENOMEM 内存不足 内核内存不足,无法完成操作。
ENOTDIR 不是目录 pathname 的一个组成部分不是目录。

使用示例

下面通过几个代码示例来演示 access() 的用法。

示例 1:检查文件是否存在

#include <stdio.h>
#include <unistd.h> // for access()
#include <errno.h>  // for errno
int main() {
    const char *filename = "test.txt";
    // 检查文件是否存在
    if (access(filename, F_OK) == 0) {
        printf("文件 '%s' 存在,\n", filename);
    } else {
        // 使用 perror 打印错误信息,它会自动输出 errno 对应的描述
        perror("文件不存在或发生其他错误");
    }
    return 0;
}

示例 2:检查文件的读写权限

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
    const char *filename = "test.txt";
    // 检查文件是否存在
    if (access(filename, F_OK) != 0) {
        perror("错误:文件不存在");
        return 1;
    }
    // 检查文件是否可读
    if (access(filename, R_OK) == 0) {
        printf("文件 '%s' 可读,\n", filename);
    } else {
        printf("文件 '%s' 不可读,错误码: %d\n", filename, errno);
    }
    // 检查文件是否可写
    if (access(filename, W_OK) == 0) {
        printf("文件 '%s' 可写,\n", filename);
    } else {
        printf("文件 '%s' 不可写,错误码: %d\n", filename, errno);
    }
    return 0;
}

示例 3:检查程序是否有执行权限

这个例子检查当前目录下的 my_program 是否有执行权限。

linux c语言 access
(图片来源网络,侵删)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> // for exit()
int main() {
    const char *program = "./my_program";
    if (access(program, F_OK | X_OK) == 0) {
        printf("程序 '%s' 存在并且有执行权限,\n", program);
        // 在这里可以使用 system() 或 exec 族函数来运行它
        // system(program);
    } else {
        perror("错误:程序不存在或没有执行权限");
        exit(EXIT_FAILURE);
    }
    return 0;
}

重要注意事项和局限性

虽然 access() 很方便,但在某些情况下使用它可能会引入竞态条件

竞态条件问题

access() 的一个典型使用场景是:检查文件是否存在并有写权限,然后打开它进行写入。

// 这是一个有问题的代码示例
if (access(filename, W_OK) == 0) {
    // 此时检查通过,认为可以写入
    FILE *fp = fopen(filename, "w");
    if (fp == NULL) {
        // 但这里可能打开失败!
        perror("fopen failed");
    } else {
        // ... 写入文件 ...
        fclose(fp);
    }
}

问题出在哪里?access()fopen() 这两个系统调用之间,可能发生以下情况:

  1. 你的程序调用 access(filename, W_OK),返回 0,表示你有写权限。
  2. 就在这之后,在 fopen() 被调用之前,另一个进程或用户删除了这个文件,或者修改了它的权限,使得你不再有写入权限。
  3. 你的程序接着调用 fopen(filename, "w"),尝试打开一个你已经没有权限写入的文件(或者一个不存在的文件),导致失败。

这种“检查-使用”(Check-Then-Use)的模式是产生竞态条件的温床。

linux c语言 access
(图片来源网络,侵删)

如何避免竞态条件?

解决这个问题的方法是原子操作,原子操作是指一个操作一旦开始,就不会被中断,直到完成。

  • 对于文件:使用 open() 函数并配合 O_CREATO_EXCL 标志。

    • open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
    • 这个调用的含义是:“以只写方式打开文件 filename,如果文件不存在,则创建它(权限为 0644);如果文件已经存在,则立即失败,并返回 EEXIST 错误。”
    • 这个操作是原子的,它保证了在文件被创建的那一刻,它就是全新的,不会有其他进程同时写入。
  • 对于目录:使用 mkdir()open() 配合 O_DIRECTORYO_EXCL 等标志。


access() vs. euidaccess()

在 Linux 系统中,还有一个相关的函数叫 eaccess()(POSIX 标准)或 euidaccess()(GNU 扩展)。

#include <unistd.h>
int euidaccess(const char *pathname, int mode);
int eaccess(const char *pathname, int mode); // eaccess 是 euidaccess 的别名

区别:

  • access() 检查的是实际用户 ID (Real User ID, RUID)实际组 ID (Real Group ID, RGID) 对应的权限,这通常是你登录时所用的身份。
  • euidaccess() 检查的是有效用户 ID (Effective User ID, EUID) 和**有效组 ID (Effective Group ID, EGID)对应的权限,这通常是程序运行时临时拥有的权限(setuid` 程序)。

什么时候用 euidaccess() 当你编写一个设置了 setuidsetgid 位的程序时,你应该使用 euidaccess() 来检查权限,因为程序的权限(EUID)和运行它的人的权限(RUID)是不同的,对于普通程序,两者效果相同。


特性 描述
功能 检查文件/目录是否存在及权限(读、写、执行)。
头文件 <unistd.h>
优点 简单、轻量,无需打开文件即可检查权限。
最大缺点 存在竞态条件风险,不应用于“检查-使用”模式。
替代方案 对于需要“检查后立即操作”的场景(如创建文件),应使用 open() + O_CREAT + O_EXCL 等原子操作。
特殊函数 euidaccess() 用于检查 setuid/setgid 程序的有效权限。

access() 是一个有用的工具,但你需要清楚地了解它的局限性,避免在需要高安全性的场景下使用它来引入竞态条件。

-- 展开阅读全文 --
头像
织梦SQL如何批量替换文章内容?
« 上一篇 2025-12-19
dede trim函数如何正确使用及去除空格?
下一篇 » 2025-12-19

相关文章

取消
微信二维码
支付宝二维码

目录[+]