52. faccessat - 相对路径文件访问权限检查 链接到标题

函数介绍 链接到标题

faccessat是一个Linux系统调用,用于检查相对于指定目录文件描述符的文件访问权限。它是access函数的扩展版本,支持相对路径和额外的标志位。

函数原型 链接到标题

#include <fcntl.h>
#include <unistd.h>

int faccessat(int dirfd, const char *pathname, int mode, int flags);

功能 链接到标题

检查相对于目录文件描述符的文件访问权限,支持相对路径和额外控制标志。

参数 链接到标题

  • int dirfd: 目录文件描述符
    • AT_FDCWD: 使用当前工作目录
    • 有效的目录文件描述符:相对于该目录检查
  • const char *pathname: 文件路径名(相对或绝对)
  • int mode: 要检查的权限类型
    • F_OK: 检查文件是否存在
    • R_OK: 检查读权限
    • W_OK: 检查写权限
    • X_OK: 检查执行权限
  • int flags: 控制标志
    • 0: 基本行为
    • AT_EACCESS: 使用有效ID而不是实际ID检查
    • AT_SYMLINK_NOFOLLOW: 不跟随符号链接

返回值 链接到标题

  • 成功时(具备指定权限或文件存在)返回0
  • 失败时返回-1,并设置errno

特殊限制 链接到标题

  • 需要Linux 2.6.16以上内核支持
  • 某些标志需要特定内核版本

相似函数 链接到标题

  • access(): 基础版本
  • stat(), fstat(), lstat(): 文件状态检查
  • openat(): 相对路径文件打开

示例代码 链接到标题

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main() {
    int dirfd, result;
    
    printf("=== Facccessat 函数示例 ===\n");
    
    // 示例1: 基本使用(相对于当前目录)
    printf("\n示例1: 基本使用(相对于当前目录)\n");
    
    // 创建测试文件
    int fd = open("test_faccessat.txt", O_CREAT | O_WRONLY, 0644);
    if (fd != -1) {
        write(fd, "test content", 12);
        close(fd);
        printf("创建测试文件: test_faccessat.txt\n");
    }
    
    // 使用AT_FDCWD表示当前目录
    result = faccessat(AT_FDCWD, "test_faccessat.txt", F_OK, 0);
    if (result == 0) {
        printf("文件存在检查成功\n");
    } else {
        printf("文件存在检查失败: %s\n", strerror(errno));
    }
    
    result = faccessat(AT_FDCWD, "test_faccessat.txt", R_OK | W_OK, 0);
    if (result == 0) {
        printf("文件读写权限检查成功\n");
    } else {
        printf("文件读写权限检查失败: %s\n", strerror(errno));
    }
    
    // 检查不存在的文件
    result = faccessat(AT_FDCWD, "nonexistent.txt", F_OK, 0);
    if (result == -1) {
        if (errno == ENOENT) {
            printf("不存在文件检查正确返回ENOENT\n");
        }
    }
    
    // 示例2: 相对于指定目录
    printf("\n示例2: 相对于指定目录\n");
    
    // 创建测试目录
    if (mkdir("test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
    } else {
        printf("创建测试目录: test_dir\n");
        
        // 在目录中创建文件
        fd = open("test_dir/file_in_dir.txt", O_CREAT | O_WRONLY, 0644);
        if (fd != -1) {
            write(fd, "content in directory", 20);
            close(fd);
            printf("在目录中创建文件: file_in_dir.txt\n");
        }
        
        // 打开目录获取文件描述符
        dirfd = open("test_dir", O_RDONLY);
        if (dirfd != -1) {
            printf("获取目录文件描述符: %d\n", dirfd);
            
            // 相对于目录文件描述符检查文件
            result = faccessat(dirfd, "file_in_dir.txt", F_OK, 0);
            if (result == 0) {
                printf("相对路径文件存在检查成功\n");
            }
            
            result = faccessat(dirfd, "file_in_dir.txt", R_OK, 0);
            if (result == 0) {
                printf("相对路径文件读权限检查成功\n");
            }
            
            // 检查不存在的文件
            result = faccessat(dirfd, "nonexistent.txt", F_OK, 0);
            if (result == -1) {
                if (errno == ENOENT) {
                    printf("相对路径不存在文件检查正确返回ENOENT\n");
                }
            }
            
            close(dirfd);
        }
    }
    
    // 示例3: 使用标志位
    printf("\n示例3: 使用标志位\n");
    
    // 创建符号链接进行测试
    if (symlink("test_faccessat.txt", "symlink_to_test.txt") == -1 && errno != EEXIST) {
        perror("创建符号链接失败");
    } else {
        printf("创建符号链接: symlink_to_test.txt -> test_faccessat.txt\n");
        
        // 默认行为(跟随符号链接)
        result = faccessat(AT_FDCWD, "symlink_to_test.txt", F_OK, 0);
        if (result == 0) {
            printf("默认行为(跟随符号链接)检查成功\n");
        }
        
#ifdef AT_SYMLINK_NOFOLLOW
        // 不跟随符号链接
        result = faccessat(AT_FDCWD, "symlink_to_test.txt", F_OK, AT_SYMLINK_NOFOLLOW);
        if (result == 0) {
            printf("不跟随符号链接检查成功\n");
        }
#endif
        
        unlink("symlink_to_test.txt");
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效的目录文件描述符
    result = faccessat(999, "test.txt", F_OK, 0);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效目录文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的权限模式
    result = faccessat(AT_FDCWD, "test_faccessat.txt", 0777, 0);
    if (result == -1) {
        if (errno == EINVAL) {
            printf("无效权限模式错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的标志
    result = faccessat(AT_FDCWD, "test_faccessat.txt", F_OK, 0x1000);
    if (result == -1) {
        printf("无效标志处理: %s\n", strerror(errno));
    }
    
    // 示例5: 与access函数对比
    printf("\n示例5: 与access函数对比\n");
    
    printf("access() vs faccessat() 对比:\n");
    printf("access(\"file.txt\", R_OK):\n");
    printf("  - 只能使用绝对路径或相对路径\n");
    printf("  - 基于当前工作目录\n");
    printf("  - 不支持额外标志\n\n");
    
    printf("faccessat(AT_FDCWD, \"file.txt\", R_OK, 0):\n");
    printf("  - 功能相同(AT_FDCWD时)\n");
    printf("  - 支持相对目录文件描述符\n");
    printf("  - 支持额外标志\n\n");
    
    printf("faccessat(dirfd, \"file.txt\", R_OK, 0):\n");
    printf("  - 相对于指定目录检查\n");
    printf("  - 更灵活的路径处理\n");
    printf("  - 支持原子性操作\n\n");
    
    // 示例6: 实际应用场景
    printf("示例6: 实际应用场景\n");
    
    printf("faccessat的典型使用场景:\n");
    printf("1. 安全的相对路径检查\n");
    printf("2. 沙箱环境中的文件访问验证\n");
    printf("3. 避免TOCTOU竞争条件\n");
    printf("4. 多目录操作的安全检查\n");
    printf("5. 与openat系列函数配合使用\n\n");
    
    // 演示安全的相对路径检查
    printf("安全相对路径检查示例:\n");
    printf("int check_file_in_directory(int dirfd, const char* filename) {\n");
    printf("    // 避免路径遍历攻击\n");
    printf("    if (strstr(filename, \"../\") != NULL) {\n");
    printf("        return -1; // 拒绝不安全路径\n");
    printf("    }\n");
    printf("    return faccessat(dirfd, filename, R_OK, 0);\n");
    printf("}\n\n");
    
    // 示例7: TOCTOU竞争条件说明
    printf("示例7: TOCTOU竞争条件说明\n");
    
    printf("TOCTOU (Time-of-Check to Time-of-Use) 问题:\n");
    printf("传统方式:\n");
    printf("  if (access(\"file.txt\", W_OK) == 0) {  // 检查\n");
    printf("      fd = open(\"file.txt\", O_WRONLY);   // 使用\n");
    printf("  }\n");
    printf("问题: 检查和使用之间可能被其他进程修改\n\n");
    
    printf("改进方式:\n");
    printf("  fd = openat(dirfd, \"file.txt\", O_WRONLY);\n");
    printf("  if (fd != -1) {\n");
    printf("      // 直接使用,避免竞争\n");
    printf("  }\n\n");
    
    printf("或者使用faccessat进行更安全的检查:\n");
    printf("  if (faccessat(dirfd, \"file.txt\", W_OK, 0) == 0) {\n");
    printf("      fd = openat(dirfd, \"file.txt\", O_WRONLY);\n");
    printf("  }\n\n");
    
    // 示例8: 权限模型说明
    printf("示例8: 权限模型说明\n");
    
    printf("faccessat使用的权限检查:\n");
    printf("1. 基于进程的实际用户ID和组ID\n");
    printf("2. 考虑文件的权限位\n");
    printf("3. 考虑目录的搜索权限\n");
    printf("4. 考虑特殊权限位(setuid/setgid)\n\n");
    
    // 示例9: 性能考虑
    printf("示例9: 性能考虑\n");
    
    printf("faccessat性能特点:\n");
    printf("1. 比直接open更轻量\n");
    printf("2. 避免不必要的文件打开\n");
    printf("3. 减少文件描述符使用\n");
    printf("4. 支持批量权限检查\n\n");
    
    printf("使用建议:\n");
    printf("1. 优先使用faccessat而不是access\n");
    printf("2. 合理使用目录文件描述符\n");
    printf("3. 注意错误处理\n");
    printf("4. 考虑TOCTOU问题\n");
    printf("5. 在安全敏感场景中谨慎使用\n\n");
    
    // 清理测试文件
    unlink("test_faccessat.txt");
    unlink("test_dir/file_in_dir.txt");
    rmdir("test_dir");
    
    printf("总结:\n");
    printf("faccessat是access的现代扩展版本\n");
    printf("支持相对目录文件描述符\n");
    printf("提供额外的控制标志\n");
    printf("更安全的路径处理\n");
    printf("适用于现代Linux应用开发\n");
    
    return 0;
}