55. fchmod - 通过文件描述符改变文件权限 链接到标题

函数介绍 链接到标题

fchmod是一个Linux系统调用,用于通过文件描述符来改变文件的访问权限。它是chmod函数的文件描述符版本,避免了路径名解析。

函数原型 链接到标题

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

int fchmod(int fd, mode_t mode);

功能 链接到标题

通过文件描述符改变文件的访问权限(读、写、执行权限)。

参数 链接到标题

  • int fd: 已打开文件的文件描述符
  • mode_t mode: 新的文件权限模式
    • 八进制表示:如0644, 0755, 0600
    • 符号常量组合:如S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH

返回值 链接到标题

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

特殊限制 链接到标题

  • 需要对文件有适当的权限
  • 某些文件系统可能不支持
  • 只能修改调用者拥有的文件(除非是root)

相似函数 链接到标题

  • chmod(): 通过路径名改变文件权限
  • fchmodat(): 相对路径版本
  • 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 <pwd.h>
#include <grp.h>

// 将权限模式转换为可读字符串
void print_permissions(mode_t mode) {
    char perms[11];
    strcpy(perms, "----------");
    
    // 用户权限
    if (mode & S_IRUSR) perms[1] = 'r';
    if (mode & S_IWUSR) perms[2] = 'w';
    if (mode & S_IXUSR) perms[3] = 'x';
    
    // 组权限
    if (mode & S_IRGRP) perms[4] = 'r';
    if (mode & S_IWGRP) perms[5] = 'w';
    if (mode & S_IXGRP) perms[6] = 'x';
    
    // 其他用户权限
    if (mode & S_IROTH) perms[7] = 'r';
    if (mode & S_IWOTH) perms[8] = 'w';
    if (mode & S_IXOTH) perms[9] = 'x';
    
    // 特殊位
    if (mode & S_ISUID) perms[3] = (perms[3] == 'x') ? 's' : 'S';
    if (mode & S_ISGID) perms[6] = (perms[6] == 'x') ? 's' : 'S';
    if (mode & S_ISVTX) perms[9] = (perms[9] == 'x') ? 't' : 'T';
    
    printf("%s", perms);
}

// 获取文件详细信息
void print_file_info(const char* filename, int fd) {
    struct stat sb;
    
    if (fstat(fd, &sb) == -1) {
        perror("fstat失败");
        return;
    }
    
    printf("文件 '%s' 的信息:\n", filename);
    printf("  inode: %ld\n", sb.st_ino);
    printf("  权限: ");
    print_permissions(sb.st_mode);
    printf(" (八进制: %o)\n", sb.st_mode & 0777);
    printf("  大小: %ld 字节\n", sb.st_size);
    printf("  所有者: %d", sb.st_uid);
    
    struct passwd *pw = getpwuid(sb.st_uid);
    if (pw) {
        printf(" (%s)", pw->pw_name);
    }
    printf("\n");
    
    printf("  所属组: %d", sb.st_gid);
    struct group *gr = getgrgid(sb.st_gid);
    if (gr) {
        printf(" (%s)", gr->gr_name);
    }
    printf("\n\n");
}

int main() {
    int fd;
    int result;
    
    printf("=== Fchmod 函数示例 ===\n");
    printf("当前用户 UID: %d\n", getuid());
    printf("当前有效 UID: %d\n", geteuid());
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 创建测试文件
    fd = open("test_fchmod.txt", O_CREAT | O_RDWR | O_TRUNC, 0666);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    printf("创建测试文件: test_fchmod.txt\n");
    
    // 写入测试数据
    const char* test_data = "This is test data for fchmod demonstration.\n";
    write(fd, test_data, strlen(test_data));
    
    // 显示初始权限
    print_file_info("test_fchmod.txt", fd);
    
    // 示例2: 改变文件权限
    printf("示例2: 改变文件权限\n");
    
    // 设置为只读模式 (0444)
    result = fchmod(fd, 0444);
    if (result == -1) {
        perror("设置只读权限失败");
    } else {
        printf("成功设置只读权限 (0444)\n");
        print_file_info("test_fchmod.txt", fd);
    }
    
    // 尝试写入(应该失败)
    const char* more_data = "More data";
    ssize_t bytes_written = write(fd, more_data, strlen(more_data));
    if (bytes_written == -1) {
        printf("写入失败(预期): %s\n", strerror(errno));
    }
    
    // 设置为读写模式 (0644)
    result = fchmod(fd, 0644);
    if (result == -1) {
        perror("设置读写权限失败");
    } else {
        printf("成功设置读写权限 (0644)\n");
        print_file_info("test_fchmod.txt", fd);
    }
    
    // 现在应该可以写入
    bytes_written = write(fd, more_data, strlen(more_data));
    if (bytes_written != -1) {
        printf("写入成功,写入 %ld 字节\n", bytes_written);
    }
    
    // 设置为可执行模式 (0755)
    result = fchmod(fd, 0755);
    if (result == -1) {
        perror("设置可执行权限失败");
    } else {
        printf("成功设置可执行权限 (0755)\n");
        print_file_info("test_fchmod.txt", fd);
    }
    
    // 示例3: 使用符号常量
    printf("\n示例3: 使用符号常量\n");
    
    // 设置为用户读写,组和其他用户只读
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    result = fchmod(fd, mode);
    if (result == -1) {
        perror("使用符号常量设置权限失败");
    } else {
        printf("使用符号常量设置权限成功\n");
        printf("  模式: S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH\n");
        print_file_info("test_fchmod.txt", fd);
    }
    
    // 设置特殊位
    mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_ISUID;
    result = fchmod(fd, mode);
    if (result == -1) {
        perror("设置特殊位失败");
    } else {
        printf("设置setuid位成功\n");
        print_file_info("test_fchmod.txt", fd);
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效的文件描述符
    result = fchmod(999, 0644);
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 权限不足的情况(需要非root用户测试)
    if (geteuid() != 0) {
        printf("非root用户权限测试:\n");
        // 创建一个文件并关闭它,然后尝试修改(应该失败)
        int temp_fd = open("temp_file.txt", O_CREAT | O_WRONLY, 0644);
        if (temp_fd != -1) {
            close(temp_fd);
            // 重新以只读方式打开
            temp_fd = open("temp_file.txt", O_RDONLY);
            if (temp_fd != -1) {
                result = fchmod(temp_fd, 0777);
                if (result == -1) {
                    if (errno == EPERM || errno == EACCES) {
                        printf("权限不足错误处理正确: %s\n", strerror(errno));
                    }
                }
                close(temp_fd);
            }
            unlink("temp_file.txt");
        }
    }
    
    // 示例5: 与chmod的对比
    printf("\n示例5: 与chmod的对比\n");
    
    printf("fchmod vs chmod 对比:\n");
    printf("chmod(\"file.txt\", 0644):\n");
    printf("  - 通过路径名指定文件\n");
    printf("  - 需要路径解析\n");
    printf("  - 可能受符号链接影响\n\n");
    
    printf("fchmod(fd, 0644):\n");
    printf("  - 通过文件描述符指定文件\n");
    printf("  - 直接操作已打开的文件\n");
    printf("  - 不受路径变化影响\n");
    printf("  - 更高效,避免路径解析\n\n");
    
    // 示例6: 实际应用场景
    printf("示例6: 实际应用场景\n");
    
    printf("安全文件创建模式:\n");
    printf("int create_secure_file(const char* filename) {\n");
    printf("    int fd = open(filename, O_CREAT | O_WRONLY, 0600);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 确保权限正确设置\n");
    printf("    if (fchmod(fd, 0600) == -1) {\n");
    printf("        close(fd);\n");
    printf("        return -1;\n");
    printf("    }\n");
    printf("    \n");
    printf("    return fd;\n");
    printf("}\n\n");
    
    printf("临时文件权限管理:\n");
    printf("int create_temp_file() {\n");
    printf("    int fd = open(\"temp.dat\", O_CREAT | O_RDWR, 0600);\n");
    printf("    if (fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 使用过程中保持私密权限\n");
    printf("    // ... 处理敏感数据 ...\n");
    printf("    \n");
    printf("    // 完成后调整权限\n");
    printf("    fchmod(fd, 0444);  // 只读\n");
    printf("    \n");
    printf("    return fd;\n");
    printf("}\n\n");
    
    // 示例7: 原子性优势
    printf("示例7: 原子性优势\n");
    
    printf("fchmod的原子性优势:\n");
    printf("1. 直接通过文件描述符操作\n");
    printf("2. 避免路径解析过程中的竞态条件\n");
    printf("3. 不受文件重命名影响\n");
    printf("4. 在文件移动后仍然有效\n\n");
    
    // 演示原子性优势
    printf("原子性演示:\n");
    int atomic_fd = open("atomic_test.txt", O_CREAT | O_RDWR, 0666);
    if (atomic_fd != -1) {
        write(atomic_fd, "atomic test", 11);
        printf("创建文件并获取文件描述符: %d\n", atomic_fd);
        
        // 重命名文件
        if (rename("atomic_test.txt", "atomic_test_renamed.txt") == 0) {
            printf("文件已重命名为: atomic_test_renamed.txt\n");
            
            // 仍然可以通过原来的文件描述符修改权限
            if (fchmod(atomic_fd, 0400) == 0) {
                printf("通过原文件描述符成功修改权限\n");
                print_file_info("atomic_test_renamed.txt", atomic_fd);
            }
        }
        
        close(atomic_fd);
        unlink("atomic_test_renamed.txt");
    }
    
    // 示例8: 权限安全最佳实践
    printf("示例8: 权限安全最佳实践\n");
    
    printf("文件权限安全建议:\n");
    printf("1. 私密文件使用 0600 (rw-------)\n");
    printf("2. 日志文件使用 0640 (rw-r-----)\n");
    printf("3. 配置文件使用 0644 (rw-r--r--)\n");
    printf("4. 可执行文件使用 0755 (rwxr-xr-x)\n");
    printf("5. 目录使用 0755 (rwxr-xr-x)\n");
    printf("6. 临时文件使用 0600 (rw-------)\n\n");
    
    printf("使用fchmod的安全模式:\n");
    printf("1. 创建时设置保守权限\n");
    printf("2. 根据需要调整权限\n");
    printf("3. 完成后收紧权限\n");
    printf("4. 验证权限更改结果\n");
    printf("5. 及时处理错误情况\n\n");
    
    // 示例9: 性能考虑
    printf("示例9: 性能考虑\n");
    
    printf("fchmod性能优势:\n");
    printf("1. 避免路径解析开销\n");
    printf("2. 直接操作内核文件结构\n");
    printf("3. 减少系统调用次数\n");
    printf("4. 在循环中修改多个文件时更高效\n\n");
    
    // 示例10: 错误恢复
    printf("示例10: 错误恢复\n");
    
    printf("健壮的权限管理:\n");
    printf("int safe_chmod(int fd, mode_t new_mode) {\n");
    printf("    struct stat old_stat;\n");
    printf("    if (fstat(fd, &old_stat) == -1) {\n");
    printf("        return -1;\n");
    printf("    }\n");
    printf("    \n");
    printf("    mode_t old_mode = old_stat.st_mode;\n");
    printf("    if (fchmod(fd, new_mode) == -1) {\n");
    printf("        // 可以选择恢复原权限\n");
    printf("        // fchmod(fd, old_mode);\n");
    printf("        return -1;\n");
    printf("    }\n");
    printf("    \n");
    printf("    return 0;\n");
    printf("}\n\n");
    
    // 清理资源
    close(fd);
    unlink("test_fchmod.txt");
    
    printf("总结:\n");
    printf("fchmod是通过文件描述符修改文件权限的函数\n");
    printf("相比chmod具有更好的性能和原子性\n");
    printf("避免了路径解析和符号链接问题\n");
    printf("适用于需要频繁权限管理的场景\n");
    printf("是安全文件操作的重要工具\n");
    
    return 0;
}