56. fchmodat - 相对路径改变文件权限 链接到标题

函数介绍 链接到标题

fchmodat是一个Linux系统调用,用于相对于指定目录文件描述符改变文件的访问权限。它是chmodfchmod的扩展版本。

函数原型 链接到标题

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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

功能 链接到标题

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

参数 链接到标题

  • int dirfd: 目录文件描述符
    • AT_FDCWD: 使用当前工作目录
  • const char *pathname: 文件路径名(相对或绝对)
  • mode_t mode: 新的文件权限模式
  • int flags: 控制标志
    • 0: 基本行为
    • AT_SYMLINK_NOFOLLOW: 不跟随符号链接

返回值 链接到标题

  • 成功时返回0
  • 失败时返回-1,并设置errno

特殊限制 链接到标题

  • 需要Linux 2.6.16以上内核支持
  • 某些标志需要特定内核版本
  • 需要适当的文件权限

相似函数 链接到标题

  • chmod(): 基础版本(通过路径名)
  • fchmod(): 文件描述符版本
  • chmodat(): 已废弃的版本

示例代码 链接到标题

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

// 创建测试环境
int setup_test_environment() {
    // 创建测试目录
    if (mkdir("fchmodat_test", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        return -1;
    }
    printf("创建测试目录: fchmodat_test\n");
    
    // 在目录中创建测试文件
    int fd = open("fchmodat_test/file1.txt", O_CREAT | O_WRONLY, 0666);
    if (fd != -1) {
        write(fd, "test file 1", 11);
        close(fd);
        printf("创建测试文件: fchmodat_test/file1.txt\n");
    }
    
    fd = open("fchmodat_test/file2.txt", O_CREAT | O_WRONLY, 0666);
    if (fd != -1) {
        write(fd, "test file 2", 11);
        close(fd);
        printf("创建测试文件: fchmodat_test/file2.txt\n");
    }
    
    // 创建子目录
    if (mkdir("fchmodat_test/subdir", 0755) == -1 && errno != EEXIST) {
        perror("创建子目录失败");
    } else {
        printf("创建子目录: fchmodat_test/subdir\n");
        
        fd = open("fchmodat_test/subdir/file3.txt", O_CREAT | O_WRONLY, 0666);
        if (fd != -1) {
            write(fd, "test file 3", 11);
            close(fd);
            printf("创建测试文件: fchmodat_test/subdir/file3.txt\n");
        }
    }
    
    return 0;
}

// 显示文件权限
void show_file_permissions(const char* filepath) {
    struct stat sb;
    if (stat(filepath, &sb) == 0) {
        printf("  %s: ", filepath);
        printf("%o (", sb.st_mode & 0777);
        
        // 简单权限显示
        if (sb.st_mode & S_IRUSR) printf("r"); else printf("-");
        if (sb.st_mode & S_IWUSR) printf("w"); else printf("-");
        if (sb.st_mode & S_IXUSR) printf("x"); else printf("-");
        if (sb.st_mode & S_IRGRP) printf("r"); else printf("-");
        if (sb.st_mode & S_IWGRP) printf("w"); else printf("-");
        if (sb.st_mode & S_IXGRP) printf("x"); else printf("-");
        if (sb.st_mode & S_IROTH) printf("r"); else printf("-");
        if (sb.st_mode & S_IWOTH) printf("w"); else printf("-");
        if (sb.st_mode & S_IXOTH) printf("x"); else printf("-");
        
        printf(")\n");
    }
}

int main() {
    int dirfd, result;
    
    printf("=== Fchmodat 函数示例 ===\n");
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 设置测试环境
    if (setup_test_environment() == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 显示初始权限
    printf("初始文件权限:\n");
    show_file_permissions("fchmodat_test/file1.txt");
    show_file_permissions("fchmodat_test/file2.txt");
    show_file_permissions("fchmodat_test/subdir/file3.txt");
    
    // 示例2: 相对于当前目录
    printf("\n示例2: 相对于当前目录\n");
    
    // 使用AT_FDCWD表示当前目录
    result = fchmodat(AT_FDCWD, "fchmodat_test/file1.txt", 0400, 0);
    if (result == 0) {
        printf("成功修改文件权限为只读 (0400)\n");
        show_file_permissions("fchmodat_test/file1.txt");
    } else {
        printf("修改权限失败: %s\n", strerror(errno));
    }
    
    // 示例3: 相对于指定目录
    printf("\n示例3: 相对于指定目录\n");
    
    // 打开测试目录获取文件描述符
    dirfd = open("fchmodat_test", O_RDONLY);
    if (dirfd == -1) {
        perror("打开测试目录失败");
        goto cleanup;
    }
    printf("获取目录文件描述符: %d\n", dirfd);
    
    // 相对于目录文件描述符修改文件权限
    result = fchmodat(dirfd, "file2.txt", 0600, 0);
    if (result == 0) {
        printf("相对于目录描述符修改文件权限成功\n");
        show_file_permissions("fchmodat_test/file2.txt");
    } else {
        printf("相对路径修改权限失败: %s\n", strerror(errno));
    }
    
    // 修改子目录中的文件
    result = fchmodat(dirfd, "subdir/file3.txt", 0755, 0);
    if (result == 0) {
        printf("修改子目录中文件权限成功\n");
        show_file_permissions("fchmodat_test/subdir/file3.txt");
    } else {
        printf("修改子目录文件权限失败: %s\n", strerror(errno));
    }
    
    close(dirfd);
    
    // 示例4: 使用标志位
    printf("\n示例4: 使用标志位\n");
    
    // 创建符号链接进行测试
    if (symlink("fchmodat_test/file1.txt", "symlink_to_file1") == -1 && errno != EEXIST) {
        perror("创建符号链接失败");
    } else {
        printf("创建符号链接: symlink_to_file1 -> fchmodat_test/file1.txt\n");
        
        // 默认行为(跟随符号链接)
        result = fchmodat(AT_FDCWD, "symlink_to_file1", 0644, 0);
        if (result == 0) {
            printf("默认行为(跟随符号链接)修改成功\n");
        } else {
            printf("默认行为修改失败: %s\n", strerror(errno));
        }
        
#ifdef AT_SYMLINK_NOFOLLOW
        // 不跟随符号链接(这个标志在fchmodat中可能不被支持)
        printf("注意: AT_SYMLINK_NOFOLLOW在fchmodat中可能不被支持\n");
        printf("因为fchmodat主要用于修改权限,而不是链接本身\n");
#endif
        
        unlink("symlink_to_file1");
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 使用无效的目录文件描述符
    result = fchmodat(999, "file.txt", 0644, 0);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效目录文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 修改不存在的文件
    result = fchmodat(AT_FDCWD, "fchmodat_test/nonexistent.txt", 0644, 0);
    if (result == -1) {
        if (errno == ENOENT) {
            printf("修改不存在文件错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的权限模式(虽然不会报错,但可能被截断)
    result = fchmodat(AT_FDCWD, "fchmodat_test/file1.txt", 07777, 0);
    if (result == 0) {
        printf("使用大权限值可能被截断\n");
        show_file_permissions("fchmodat_test/file1.txt");
    }
    
    // 使用无效的标志
    result = fchmodat(AT_FDCWD, "fchmodat_test/file1.txt", 0644, 0x1000);
    if (result == -1) {
        printf("无效标志处理: %s\n", strerror(errno));
    }
    
    // 示例6: 与相关函数对比
    printf("\n示例6: 与相关函数对比\n");
    
    printf("chmod() vs fchmod() vs fchmodat() 对比:\n");
    printf("chmod(\"path/file.txt\", 0644):\n");
    printf("  - 通过路径名指定文件\n");
    printf("  - 需要完整的路径解析\n");
    printf("  - 可能受符号链接影响\n\n");
    
    printf("fchmod(fd, 0644):\n");
    printf("  - 通过文件描述符指定文件\n");
    printf("  - 直接操作已打开的文件\n");
    printf("  - 最高效,但需要先打开文件\n\n");
    
    printf("fchmodat(dirfd, \"file.txt\", 0644, 0):\n");
    printf("  - 相对于目录文件描述符\n");
    printf("  - 支持相对路径\n");
    printf("  - 更灵活的路径处理\n");
    printf("  - 支持额外标志\n\n");
    
    printf("fchmodat(AT_FDCWD, \"path/file.txt\", 0644, 0):\n");
    printf("  - 等同于chmod()但支持标志\n");
    printf("  - 更现代的接口\n\n");
    
    // 示例7: 实际应用场景
    printf("示例7: 实际应用场景\n");
    
    printf("批量文件权限修改:\n");
    printf("int batch_chmod_in_directory(const char* dirname, mode_t mode) {\n");
    printf("    int dirfd = open(dirname, O_RDONLY);\n");
    printf("    if (dirfd == -1) return -1;\n");
    printf("    \n");
    printf("    DIR* dir = fdopendir(dirfd);\n");
    printf("    if (!dir) {\n");
    printf("        close(dirfd);\n");
    printf("        return -1;\n");
    printf("    }\n");
    printf("    \n");
    printf("    struct dirent* entry;\n");
    printf("    while ((entry = readdir(dir)) != NULL) {\n");
    printf("        if (entry->d_name[0] != '.') {  // 跳过隐藏文件\n");
    printf("            fchmodat(dirfd, entry->d_name, mode, 0);\n");
    printf("        }\n");
    printf("    }\n");
    printf("    \n");
    printf("    closedir(dir);\n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    printf("安全的相对路径操作:\n");
    printf("int secure_chmod_in_sandbox(int sandbox_dirfd, \n");
    printf("                           const char* filename, mode_t mode) {\n");
    printf("    // 避免路径遍历攻击\n");
    printf("    if (strstr(filename, \"../\") != NULL) {\n");
    printf("        return -1; // 拒绝不安全路径\n");
    printf("    }\n");
    printf("    \n");
    printf("    return fchmodat(sandbox_dirfd, filename, mode, 0);\n");
    printf("}\n\n");
    
    // 示例8: 相对路径优势
    printf("示例8: 相对路径优势\n");
    
    printf("fchmodat相对路径的优势:\n");
    printf("1. 避免重复路径解析\n");
    printf("2. 在chroot环境中更安全\n");
    printf("3. 支持原子性目录操作\n");
    printf("4. 减少字符串操作\n");
    printf("5. 更好的错误隔离\n\n");
    
    // 演示相对路径优势
    printf("相对路径优势演示:\n");
    dirfd = open("fchmodat_test", O_RDONLY);
    if (dirfd != -1) {
        printf("使用目录文件描述符进行批量操作:\n");
        
        // 修改多个文件权限
        const char* files[] = {"file1.txt", "file2.txt", "subdir/file3.txt"};
        mode_t modes[] = {0644, 0600, 0755};
        
        for (int i = 0; i < 3; i++) {
            result = fchmodat(dirfd, files[i], modes[i], 0);
            if (result == 0) {
                printf("  修改 %s 权限为 %o 成功\n", files[i], modes[i]);
            } else {
                printf("  修改 %s 权限失败: %s\n", files[i], strerror(errno));
            }
        }
        
        close(dirfd);
    }
    
    // 示例9: 权限管理最佳实践
    printf("示例9: 权限管理最佳实践\n");
    
    printf("使用fchmodat的最佳实践:\n");
    printf("1. 优先使用AT_FDCWD进行简单操作\n");
    printf("2. 复杂目录操作使用目录文件描述符\n");
    printf("3. 合理验证路径安全性\n");
    printf("4. 及时处理错误情况\n");
    printf("5. 避免硬编码绝对路径\n");
    printf("6. 使用适当的权限模式\n\n");
    
    printf("权限设置建议:\n");
    printf("私密文件: 0600 (rw-------)\n");
    printf("用户文件: 0644 (rw-r--r--)\n");
    printf("可执行文件: 0755 (rwxr-xr-x)\n");
    printf("目录: 0755 (rwxr-xr-x)\n");
    printf("临时文件: 0600 (rw-------)\n\n");
    
    // 示例10: 性能考虑
    printf("示例10: 性能考虑\n");
    
    printf("fchmodat性能特点:\n");
    printf("1. 相对于chmod减少路径解析\n");
    printf("2. 相对于fchmod避免文件打开/关闭\n");
    printf("3. 目录文件描述符可重复使用\n");
    printf("4. 批量操作效率更高\n");
    printf("5. 减少系统调用开销\n\n");
    
    // 清理测试环境
    cleanup:
    printf("清理测试环境...\n");
    unlink("fchmodat_test/subdir/file3.txt");
    rmdir("fchmodat_test/subdir");
    unlink("fchmodat_test/file1.txt");
    unlink("fchmodat_test/file2.txt");
    rmdir("fchmodat_test");
    
    printf("\n总结:\n");
    printf("fchmodat是chmod的现代扩展版本\n");
    printf("支持相对目录文件描述符\n");
    printf("提供额外的控制标志\n");
    printf("更安全的路径处理\n");
    printf("适用于复杂的文件权限管理场景\n");
    printf("是现代Linux应用开发的重要工具\n");
    
    return 0;
}