fchownat系统调用及示例

fchownat函数详解

fchownat函数是Linux系统中用于改变文件所有者和组的高级函数,它是chown()和fchown()函数的增强版本。可以把fchownat想象成一个”精确的所有权修改器”,它不仅能够修改文件的所有者和组,还支持相对路径操作和符号链接控制。
fchownat的主要优势在于它提供了更多的控制选项,特别是相对于指定目录文件描述符的路径解析能力。这使得在复杂目录结构中进行批量文件所有权修改变得更加安全和高效。

本文链接

1. 函数介绍

fchownat函数是Linux系统中用于改变文件所有者和组的高级函数,它是chown()和fchown()函数的增强版本。可以把fchownat想象成一个”精确的所有权修改器”,它不仅能够修改文件的所有者和组,还支持相对路径操作和符号链接控制。

fchownat的主要优势在于它提供了更多的控制选项,特别是相对于指定目录文件描述符的路径解析能力。这使得在复杂目录结构中进行批量文件所有权修改变得更加安全和高效。

使用场景:

  • 系统管理工具中的文件权限管理
  • 批量修改目录树中文件的所有权
  • 安全的相对路径文件操作
  • 系统恢复和维护工具
  • 容器和虚拟化环境中的权限管理

2. 函数原型

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

int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);

3. 功能

fchownat函数的主要功能是修改指定文件的所有者和组。它支持相对于目录文件描述符的路径解析,并提供了控制符号链接行为的选项。

4. 参数

  • dirfd: 目录文件描述符
    • 类型:int
    • 含义:基准目录的文件描述符,用于相对路径解析
    • 特殊值:AT_FDCWD表示使用当前工作目录
  • pathname: 文件路径名
    • 类型:const char*
    • 含义:要修改所有权的文件路径名
    • 可以是相对路径(相对于dirfd)或绝对路径
  • owner: 新的所有者用户ID
    • 类型:uid_t
    • 含义:文件的新所有者用户ID
    • -1表示不改变所有者
  • group: 新的组ID
    • 类型:gid_t
    • 含义:文件的新组ID
    • -1表示不改变组
  • flags: 操作标志
    • 类型:int
    • 含义:控制操作行为的标志位
    • 常用值:
      • 0:默认行为
      • AT_SYMLINK_NOFOLLOW:不跟随符号链接(修改符号链接本身)
      • AT_EMPTY_PATH:允许空路径名(Linux 2.6.39+)

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno错误码
    • EACCES:权限不足
    • EBADF:dirfd无效或不是目录
    • EFAULT:pathname指向无效内存
    • EIO:I/O错误
    • ELOOP:符号链接循环
    • ENAMETOOLONG:路径名过长
    • ENOENT:文件或路径不存在
    • ENOTDIR:路径前缀不是目录
    • EPERM:操作不被允许
    • EROFS:文件系统只读

6. 相似函数或关联函数

  • chown(): 改变文件所有权
  • fchown(): 通过文件描述符改变文件所有权
  • lchown(): 改变符号链接所有权(不跟随链接)
  • chmod(): 改变文件权限
  • fchmodat(): 相对于目录文件描述符改变文件权限
  • openat(): 相对于目录文件描述符打开文件
  • readlinkat(): 相对于目录文件描述符读取符号链接

7. 示例代码

示例1:基础fchownat使用 – 简单所有权修改

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

// 创建测试文件
int create_test_file(const char* filename, const char* content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (write(fd, content, strlen(content)) == -1) {
        perror("写入文件失败");
        close(fd);
        return -1;
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件所有权信息
void show_file_ownership(const char* filename) {
    struct stat file_stat;
    
    if (stat(filename, &file_stat) == -1) {
        perror("获取文件状态失败");
        return;
    }
    
    // 获取用户名
    struct passwd* pwd = getpwuid(file_stat.st_uid);
    struct group* grp = getgrgid(file_stat.st_gid);
    
    printf("文件 %s 的所有权信息:\n", filename);
    printf("  用户ID: %d", (int)file_stat.st_uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  组ID: %d", (int)file_stat.st_gid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
    
    printf("  权限: %o\n", file_stat.st_mode & 0777);
}

// 获取当前用户和组信息
void show_current_user_info() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    
    struct passwd* pwd = getpwuid(uid);
    struct group* grp = getgrgid(gid);
    
    printf("当前用户信息:\n");
    printf("  用户ID: %d", (int)uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  组ID: %d", (int)gid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
}

int main() {
    printf("=== 基础fchownat使用示例 ===\n");
    
    // 检查权限
    if (geteuid() != 0) {
        printf("警告: 修改文件所有权通常需要root权限\n");
        printf("某些操作可能失败\n");
    }
    
    show_current_user_info();
    
    // 创建测试文件
    const char* test_file = "fchownat_test.txt";
    if (create_test_file(test_file, "这是fchownat测试文件的内容\n") == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 显示初始所有权
    printf("\n1. 初始文件所有权:\n");
    show_file_ownership(test_file);
    
    // 获取root用户信息(用于测试)
    struct passwd* root_pwd = getpwnam("root");
    struct group* root_grp = getgrnam("root");
    
    uid_t root_uid = root_pwd ? root_pwd->pw_uid : 0;
    gid_t root_gid = root_grp ? root_grp->gr_gid : 0;
    
    // 使用fchownat修改所有权为root
    printf("\n2. 使用fchownat修改所有权为root:\n");
    if (fchownat(AT_FDCWD, test_file, root_uid, root_gid, 0) == 0) {
        printf("✓ 所有权修改成功\n");
    } else {
        printf("✗ 所有权修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 使用fchownat只修改用户ID
    printf("\n3. 使用fchownat只修改用户ID:\n");
    uid_t current_uid = getuid();
    if (fchownat(AT_FDCWD, test_file, current_uid, -1, 0) == 0) {
        printf("✓ 用户ID修改成功\n");
    } else {
        printf("✗ 用户ID修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 使用fchownat只修改组ID
    printf("\n4. 使用fchownat只修改组ID:\n");
    gid_t current_gid = getgid();
    if (fchownat(AT_FDCWD, test_file, -1, current_gid, 0) == 0) {
        printf("✓ 组ID修改成功\n");
    } else {
        printf("✗ 组ID修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 演示错误处理
    printf("\n5. 错误处理演示:\n");
    
    // 尝试修改不存在的文件
    if (fchownat(AT_FDCWD, "nonexistent.txt", 0, 0, 0) == -1) {
        printf("修改不存在文件的所有权: %s (预期行为)\n", strerror(errno));
    }
    
    // 尝试使用无效的dirfd
    if (fchownat(999, test_file, 0, 0, 0) == -1) {
        printf("使用无效dirfd: %s (预期行为)\n", strerror(errno));
    }
    
    // 清理测试文件
    unlink(test_file);
    
    printf("\n=== 基础fchownat演示完成 ===\n");
    
    return 0;
}

示例2:相对路径和符号链接控制

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

// 创建测试目录结构
int create_test_directory_structure() {
    // 创建测试目录
    if (mkdir("fchownat_test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        return -1;
    }
    
    if (mkdir("fchownat_test_dir/subdir", 0755) == -1 && errno != EEXIST) {
        perror("创建子目录失败");
        return -1;
    }
    
    // 创建测试文件
    int fd = open("fchownat_test_dir/test_file.txt", 
                  O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "测试文件内容", 12);
        close(fd);
        printf("创建测试文件: fchownat_test_dir/test_file.txt\n");
    }
    
    // 创建子目录中的文件
    fd = open("fchownat_test_dir/subdir/sub_file.txt", 
              O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "子目录文件内容", 14);
        close(fd);
        printf("创建子目录文件: fchownat_test_dir/subdir/sub_file.txt\n");
    }
    
    // 创建符号链接
    if (symlink("test_file.txt", "fchownat_test_dir/link_to_file") == 0) {
        printf("创建符号链接: fchownat_test_dir/link_to_file\n");
    }
    
    return 0;
}

// 显示目录内容和所有权
void show_directory_ownership(const char* dir_path) {
    printf("目录 %s 的内容和所有权:\n", dir_path);
    
    DIR* dir = opendir(dir_path);
    if (dir == NULL) {
        perror("打开目录失败");
        return;
    }
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        char full_path[512];
        snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
        
        struct stat file_stat;
        if (stat(full_path, &file_stat) == 0) {
            struct passwd* pwd = getpwuid(file_stat.st_uid);
            struct group* grp = getgrgid(file_stat.st_gid);
            
            char type_char = '?';
            if (S_ISREG(file_stat.st_mode)) type_char = 'f';
            else if (S_ISDIR(file_stat.st_mode)) type_char = 'd';
            else if (S_ISLNK(file_stat.st_mode)) type_char = 'l';
            
            printf("  %c %s", type_char, entry->d_name);
            if (pwd) printf(" (用户:%s", pwd->pw_name);
            if (grp) printf(" 组:%s", grp->gr_name);
            printf(")\n");
        }
    }
    
    closedir(dir);
}

int main() {
    printf("=== fchownat相对路径和符号链接控制示例 ===\n");
    
    if (geteuid() != 0) {
        printf("警告: 此演示需要root权限以修改文件所有权\n");
    }
    
    // 创建测试目录结构
    printf("1. 创建测试目录结构:\n");
    if (create_test_directory_structure() == -1) {
        exit(EXIT_FAILURE);
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 演示相对路径操作
    printf("\n2. 演示相对路径操作:\n");
    
    // 打开基准目录
    int base_dirfd = open("fchownat_test_dir", O_RDONLY);
    if (base_dirfd == -1) {
        perror("打开基准目录失败");
        exit(EXIT_FAILURE);
    }
    
    printf("打开基准目录 fd=%d\n", base_dirfd);
    
    // 相对于基准目录修改文件所有权
    printf("相对于基准目录修改文件所有权:\n");
    
    // 修改test_file.txt的所有权
    if (fchownat(base_dirfd, "test_file.txt", 0, 0, 0) == 0) {
        printf("  ✓ 修改 test_file.txt 成功\n");
    } else {
        printf("  ✗ 修改 test_file.txt 失败: %s\n", strerror(errno));
    }
    
    // 修改子目录中文件的所有权
    if (fchownat(base_dirfd, "subdir/sub_file.txt", 0, 0, 0) == 0) {
        printf("  ✓ 修改 subdir/sub_file.txt 成功\n");
    } else {
        printf("  ✗ 修改 subdir/sub_file.txt 失败: %s\n", strerror(errno));
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 演示AT_FDCWD的使用
    printf("\n3. 演示AT_FDCWD的使用:\n");
    
    // 切换到测试目录
    if (chdir("fchownat_test_dir") == -1) {
        perror("切换目录失败");
        close(base_dirfd);
        exit(EXIT_FAILURE);
    }
    
    printf("切换到测试目录\n");
    
    // 使用AT_FDCWD修改文件所有权
    if (fchownat(AT_FDCWD, "test_file.txt", 1, 1, 0) == 0) {
        printf("  ✓ 使用AT_FDCWD修改所有权成功\n");
    } else {
        printf("  ✗ 使用AT_FDCWD修改所有权失败: %s\n", strerror(errno));
    }
    
    // 切换回原目录
    chdir("..");
    show_directory_ownership("fchownat_test_dir");
    
    // 演示符号链接控制
    printf("\n4. 演示符号链接控制:\n");
    
    // 显示符号链接信息
    struct stat link_stat, target_stat;
    if (lstat("fchownat_test_dir/link_to_file", &link_stat) == 0 &&
        stat("fchownat_test_dir/link_to_file", &target_stat) == 0) {
        printf("符号链接信息:\n");
        printf("  链接文件所有权: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
        printf("  目标文件所有权: UID=%d, GID=%d\n", 
               (int)target_stat.st_uid, (int)target_stat.st_gid);
    }
    
    // 默认行为:修改目标文件所有权
    printf("默认行为(修改目标文件):\n");
    if (fchownat(base_dirfd, "link_to_file", 2, 2, 0) == 0) {
        printf("  ✓ 修改符号链接目标成功\n");
        // 检查目标文件所有权
        if (stat("fchownat_test_dir/link_to_file", &target_stat) == 0) {
            printf("  目标文件新所有权: UID=%d, GID=%d\n", 
                   (int)target_stat.st_uid, (int)target_stat.st_gid);
        }
    }
    
    // 使用AT_SYMLINK_NOFOLLOW:修改符号链接本身
    printf("使用AT_SYMLINK_NOFOLLOW(修改链接本身):\n");
    if (fchownat(base_dirfd, "link_to_file", 3, 3, AT_SYMLINK_NOFOLLOW) == 0) {
        printf("  ✓ 修改符号链接本身成功\n");
        // 检查链接文件所有权
        if (lstat("fchownat_test_dir/link_to_file", &link_stat) == 0) {
            printf("  链接文件新所有权: UID=%d, GID=%d\n", 
                   (int)link_stat.st_uid, (int)link_stat.st_gid);
        }
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 清理资源
    close(base_dirfd);
    
    // 清理测试文件
    unlink("fchownat_test_dir/link_to_file");
    unlink("fchownat_test_dir/test_file.txt");
    unlink("fchownat_test_dir/subdir/sub_file.txt");
    rmdir("fchownat_test_dir/subdir");
    rmdir("fchownat_test_dir");
    
    printf("\n=== 相对路径和符号链接控制演示完成 ===\n");
    
    return 0;
}

示例3:批量文件所有权管理

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

#define MAX_FILES 1000

// 文件信息结构
typedef struct {
    char path[512];
    uid_t old_uid;
    gid_t old_gid;
    uid_t new_uid;
    gid_t new_gid;
    int changed;
} file_ownership_info_t;

file_ownership_info_t file_list[MAX_FILES];
int file_count = 0;

// 创建批量测试文件
int create_batch_test_files(const char* base_dir) {
    printf("创建批量测试文件...\n");
    
    // 创建基础目录
    if (mkdir(base_dir, 0755) == -1 && errno != EEXIST) {
        perror("创建基础目录失败");
        return -1;
    }
    
    // 创建子目录结构
    char dirs[][256] = {
        "documents", "images", "videos", "music", "archives"
    };
    
    for (int i = 0; i < 5; i++) {
        char full_path[512];
        snprintf(full_path, sizeof(full_path), "%s/%s", base_dir, dirs[i]);
        if (mkdir(full_path, 0755) == -1 && errno != EEXIST) {
            printf("创建目录失败 %s: %s\n", full_path, strerror(errno));
        }
    }
    
    // 创建测试文件
    srand(time(NULL));
    const char* extensions[] = {".txt", ".pdf", ".jpg", ".mp3", ".zip"};
    const char* contents[] = {"文档内容", "PDF内容", "图片数据", "音频数据", "压缩数据"};
    
    for (int i = 0; i < 50; i++) {
        int dir_index = rand() % 6;  // 0-4是子目录,5是根目录
        int ext_index = rand() % 5;
        
        char file_path[512];
        if (dir_index < 5) {
            snprintf(file_path, sizeof(file_path), "%s/%s/file_%03d%s", 
                     base_dir, dirs[dir_index], i, extensions[ext_index]);
        } else {
            snprintf(file_path, sizeof(file_path), "%s/root_file_%03d%s", 
                     base_dir, i, extensions[ext_index]);
        }
        
        int fd = open(file_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd != -1) {
            write(fd, contents[ext_index], strlen(contents[ext_index]));
            close(fd);
            printf("创建文件: %s\n", file_path);
        }
    }
    
    return 0;
}

// 递归扫描目录中的文件
int scan_directory_files(int dirfd, const char* base_path) {
    DIR* dir = fdopendir(dirfd);
    if (dir == NULL) {
        close(dirfd);
        return -1;
    }
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 构造完整路径
        char full_path[1024];
        if (base_path[0] == '\0') {
            snprintf(full_path, sizeof(full_path), "%s", entry->d_name);
        } else {
            snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
        }
        
        // 获取文件状态
        struct stat file_stat;
        if (fstatat(dirfd, entry->d_name, &file_stat, AT_SYMLINK_NOFOLLOW) == 0) {
            if (file_count < MAX_FILES) {
                strncpy(file_list[file_count].path, full_path, 
                        sizeof(file_list[file_count].path) - 1);
                file_list[file_count].old_uid = file_stat.st_uid;
                file_list[file_count].old_gid = file_stat.st_gid;
                file_list[file_count].new_uid = -1;  // 保持不变
                file_list[file_count].new_gid = -1;  // 保持不变
                file_list[file_count].changed = 0;
                file_count++;
            }
            
            // 如果是目录,递归扫描
            if (S_ISDIR(file_stat.st_mode)) {
                int sub_dirfd = openat(dirfd, entry->d_name, O_RDONLY);
                if (sub_dirfd != -1) {
                    scan_directory_files(sub_dirfd, full_path);
                }
            }
        }
    }
    
    closedir(dir);
    return 0;
}

// 显示文件列表
void show_file_list(int max_show) {
    printf("文件列表 (共 %d 个文件):\n", file_count);
    printf("%-40s %-8s %-8s %-8s %-8s %-8s\n", 
           "路径", "旧UID", "旧GID", "新UID", "新GID", "状态");
    printf("%-40s %-8s %-8s %-8s %-8s %-8s\n", 
           "----", "------", "------", "------", "------", "----");
    
    int show_count = (max_show > 0 && max_show < file_count) ? max_show : file_count;
    
    for (int i = 0; i < show_count; i++) {
        const file_ownership_info_t* info = &file_list[i];
        printf("%-40s %-8d %-8d %-8d %-8d %-8s\n",
               info->path,
               (int)info->old_uid, (int)info->old_gid,
               (int)info->new_uid, (int)info->new_gid,
               info->changed ? "已修改" : "未修改");
    }
    
    if (show_count < file_count) {
        printf("... (还有 %d 个文件)\n", file_count - show_count);
    }
    printf("\n");
}

// 批量修改文件所有权
int batch_change_ownership(int base_dirfd, uid_t new_uid, gid_t new_gid, 
                          const char* pattern) {
    printf("批量修改文件所有权:\n");
    printf("  新UID: %d, 新GID: %d\n", (int)new_uid, (int)new_gid);
    if (pattern) {
        printf("  匹配模式: %s\n", pattern);
    }
    
    int changed_count = 0;
    
    for (int i = 0; i < file_count; i++) {
        file_ownership_info_t* info = &file_list[i];
        
        // 检查是否匹配模式(如果指定了模式)
        if (pattern && strstr(info->path, pattern) == NULL) {
            continue;
        }
        
        // 确定新的UID和GID
        uid_t target_uid = (new_uid == (uid_t)-1) ? info->old_uid : new_uid;
        gid_t target_gid = (new_gid == (gid_t)-1) ? info->old_gid : new_gid;
        
        // 只有当所有权需要改变时才执行
        if (target_uid != info->old_uid || target_gid != info->old_gid) {
            if (fchownat(base_dirfd, info->path, target_uid, target_gid, 0) == 0) {
                info->new_uid = target_uid;
                info->new_gid = target_gid;
                info->changed = 1;
                changed_count++;
                printf("  修改成功: %s (UID:%d->%d, GID:%d->%d)\n",
                       info->path, (int)info->old_uid, (int)target_uid,
                       (int)info->old_gid, (int)target_gid);
            } else {
                printf("  修改失败: %s (%s)\n", info->path, strerror(errno));
            }
        }
    }
    
    printf("总共修改了 %d 个文件的所有权\n", changed_count);
    return changed_count;
}

// 按文件类型修改所有权
int change_ownership_by_type(int base_dirfd, const char* extension, 
                            uid_t new_uid, gid_t new_gid) {
    printf("按文件类型修改所有权: %s\n", extension);
    
    int changed_count = 0;
    
    for (int i = 0; i < file_count; i++) {
        file_ownership_info_t* info = &file_list[i];
        
        // 检查文件扩展名
        char* dot = strrchr(info->path, '.');
        if (dot && strcmp(dot, extension) == 0) {
            if (fchownat(base_dirfd, info->path, new_uid, new_gid, 0) == 0) {
                info->new_uid = new_uid;
                info->new_gid = new_gid;
                info->changed = 1;
                changed_count++;
                printf("  修改成功: %s\n", info->path);
            } else {
                printf("  修改失败: %s (%s)\n", info->path, strerror(errno));
            }
        }
    }
    
    printf("按类型修改了 %d 个文件的所有权\n", changed_count);
    return changed_count;
}

int main() {
    printf("=== 批量文件所有权管理示例 ===\n");
    
    if (geteuid() != 0) {
        printf("警告: 批量所有权修改需要root权限\n");
    }
    
    const char* test_dir = "batch_ownership_test";
    
    // 创建批量测试文件
    printf("1. 创建批量测试文件:\n");
    if (create_batch_test_files(test_dir) == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 扫描文件
    printf("\n2. 扫描目录中的文件:\n");
    int base_dirfd = open(test_dir, O_RDONLY);
    if (base_dirfd == -1) {
        perror("打开测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    file_count = 0;
    scan_directory_files(dup(base_dirfd), "");  // dup因为scan_directory_files会关闭fd
    
    printf("扫描完成,找到 %d 个文件\n", file_count);
    show_file_list(10);
    
    // 批量修改所有文件的所有权为root
    printf("3. 批量修改所有文件所有权为root:\n");
    batch_change_ownership(base_dirfd, 0, 0, NULL);
    
    show_file_list(10);
    
    // 按文件类型修改所有权
    printf("\n4. 按文件类型修改所有权:\n");
    struct passwd* test_user = getpwnam("nobody");
    struct group* test_group = getgrnam("nobody");
    uid_t test_uid = test_user ? test_user->pw_uid : 65534;
    gid_t test_gid = test_group ? test_group->gr_gid : 65534;
    
    change_ownership_by_type(base_dirfd, ".txt", test_uid, test_gid);
    change_ownership_by_type(base_dirfd, ".jpg", test_uid + 1, test_gid + 1);
    
    // 按目录修改所有权
    printf("\n5. 按目录修改所有权:\n");
    batch_change_ownership(base_dirfd, test_uid + 2, test_gid + 2, "documents/");
    batch_change_ownership(base_dirfd, test_uid + 3, test_gid + 3, "images/");
    
    // 显示最终结果
    printf("\n6. 最终文件所有权状态:\n");
    show_file_list(20);
    
    // 统计修改情况
    int changed_count = 0;
    int unchanged_count = 0;
    for (int i = 0; i < file_count; i++) {
        if (file_list[i].changed) {
            changed_count++;
        } else {
            unchanged_count++;
        }
    }
    
    printf("统计信息:\n");
    printf("  已修改文件: %d\n", changed_count);
    printf("  未修改文件: %d\n", unchanged_count);
    printf("  总文件数: %d\n", file_count);
    
    // 清理测试文件
    printf("\n7. 清理测试文件:\n");
    
    // 由于是递归目录,需要特殊清理
    DIR* dir = opendir(test_dir);
    if (dir) {
        struct dirent* entry;
        while ((entry = readdir(dir)) != NULL) {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                char full_path[512];
                snprintf(full_path, sizeof(full_path), "%s/%s", test_dir, entry->d_name);
                
                if (entry->d_type == DT_DIR) {
                    // 递归删除目录
                    DIR* subdir = opendir(full_path);
                    if (subdir) {
                        struct dirent* subentry;
                        while ((subentry = readdir(subdir)) != NULL) {
                            if (strcmp(subentry->d_name, ".") != 0 && 
                                strcmp(subentry->d_name, "..") != 0) {
                                char sub_path[512];
                                snprintf(sub_path, sizeof(sub_path), "%s/%s", 
                                         full_path, subentry->d_name);
                                unlink(sub_path);
                            }
                        }
                        closedir(subdir);
                        rmdir(full_path);
                    }
                } else {
                    unlink(full_path);
                }
            }
        }
        closedir(dir);
        rmdir(test_dir);
        printf("清理完成\n");
    }
    
    close(base_dirfd);
    
    printf("\n=== 批量文件所有权管理演示完成 ===\n");
    
    return 0;
}

示例4:高级所有权管理和安全特性

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

// 安全的所有权修改结构
typedef struct {
    char path[512];
    uid_t current_uid;
    gid_t current_gid;
    uid_t target_uid;
    gid_t target_gid;
    mode_t current_mode;
    time_t modify_time;
    int success;
    char error_msg[128];
} ownership_change_record_t;

#define MAX_RECORDS 1000
ownership_change_record_t change_records[MAX_RECORDS];
int record_count = 0;

// 创建安全测试环境
int create_secure_test_environment() {
    printf("创建安全测试环境...\n");
    
    // 创建测试目录
    if (mkdir("secure_ownership_test", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        return -1;
    }
    
    // 创建敏感文件
    const char* sensitive_files[] = {
        "secure_ownership_test/config.txt",
        "secure_ownership_test/private.key",
        "secure_ownership_test/database.db",
        "secure_ownership_test/logs/app.log"
    };
    
    // 创建日志目录
    if (mkdir("secure_ownership_test/logs", 0755) == -1 && errno != EEXIST) {
        perror("创建日志目录失败");
    }
    
    // 创建敏感文件
    for (int i = 0; i < 4; i++) {
        int fd = open(sensitive_files[i], O_CREAT | O_WRONLY | O_TRUNC, 
                     (i == 1) ? 0600 : 0644);  // private.key使用更严格的权限
        if (fd != -1) {
            const char* content = (i == 1) ? "PRIVATE KEY DATA" : "SENSITIVE DATA";
            write(fd, content, strlen(content));
            close(fd);
            printf("创建敏感文件: %s\n", sensitive_files[i]);
        }
    }
    
    // 创建符号链接
    if (symlink("config.txt", "secure_ownership_test/link_to_config") == 0) {
        printf("创建符号链接\n");
    }
    
    return 0;
}

// 安全的所有权修改函数
int secure_chownat(int dirfd, const char* pathname, uid_t owner, gid_t group, 
                   int flags, const char* reason) {
    if (record_count >= MAX_RECORDS) {
        fprintf(stderr, "记录缓冲区已满\n");
        return -1;
    }
    
    ownership_change_record_t* record = &change_records[record_count];
    strncpy(record->path, pathname, sizeof(record->path) - 1);
    record->target_uid = owner;
    record->target_gid = group;
    record->modify_time = time(NULL);
    record->success = 0;
    record->error_msg[0] = '\0';
    
    // 获取当前状态
    struct stat current_stat;
    if (fstatat(dirfd, pathname, &current_stat, 
                (flags & AT_SYMLINK_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) == 0) {
        record->current_uid = current_stat.st_uid;
        record->current_gid = current_stat.st_gid;
        record->current_mode = current_stat.st_mode;
    } else {
        snprintf(record->error_msg, sizeof(record->error_msg), 
                 "获取当前状态失败: %s", strerror(errno));
        record_count++;
        return -1;
    }
    
    // 执行所有权修改
    if (fchownat(dirfd, pathname, owner, group, flags) == 0) {
        record->success = 1;
        printf("✓ 安全修改所有权: %s (%s)\n", pathname, reason ? reason : "无原因");
    } else {
        snprintf(record->error_msg, sizeof(record->error_msg), 
                 "%s", strerror(errno));
        printf("✗ 修改所有权失败: %s (%s) - %s\n", 
               pathname, reason ? reason : "无原因", strerror(errno));
    }
    
    record_count++;
    return record->success ? 0 : -1;
}

// 验证所有权修改的安全性
int verify_ownership_change(int dirfd, const char* pathname, 
                           uid_t expected_uid, gid_t expected_gid) {
    struct stat verify_stat;
    if (fstatat(dirfd, pathname, &verify_stat, 0) == 0) {
        if (verify_stat.st_uid == expected_uid && verify_stat.st_gid == expected_gid) {
            printf("✓ 所有权验证通过: %s (UID:%d, GID:%d)\n", 
                   pathname, (int)expected_uid, (int)expected_gid);
            return 1;
        } else {
            printf("✗ 所有权验证失败: %s (期望UID:%d,GID:%d 实际UID:%d,GID:%d)\n", 
                   pathname, (int)expected_uid, (int)expected_gid,
                   (int)verify_stat.st_uid, (int)verify_stat.st_gid);
            return 0;
        }
    } else {
        printf("✗ 验证失败,无法获取文件状态: %s\n", strerror(errno));
        return 0;
    }
}

// 显示修改记录
void show_change_records() {
    printf("\n=== 所有权修改记录 ===\n");
    printf("%-30s %-8s %-8s %-8s %-8s %-10s %s\n",
           "路径", "原UID", "原GID", "新UID", "新GID", "状态", "时间");
    printf("%-30s %-8s %-8s %-8s %-8s %-10s %s\n",
           "----", "------", "------", "------", "------", "----", "----");
    
    for (int i = 0; i < record_count; i++) {
        const ownership_change_record_t* record = &change_records[i];
        char time_str[32];
        strftime(time_str, sizeof(time_str), "%H:%M:%S", 
                 localtime(&record->modify_time));
        
        printf("%-30s %-8d %-8d %-8d %-8d %-10s %s\n",
               record->path,
               (int)record->current_uid, (int)record->current_gid,
               (int)record->target_uid, (int)record->target_gid,
               record->success ? "成功" : "失败",
               time_str);
        
        if (!record->success && record->error_msg[0] != '\0') {
            printf("  错误信息: %s\n", record->error_msg);
        }
    }
    printf("========================\n\n");
}

// 模拟权限检查
int check_permission_for_chown(uid_t current_uid, uid_t file_uid) {
    // Root用户可以修改任何文件的所有权
    if (current_uid == 0) {
        return 1;
    }
    
    // 文件所有者可以修改自己文件的组
    if (current_uid == file_uid) {
        return 1;
    }
    
    // 需要CAP_CHOWN能力的其他情况
    printf("警告: 非root用户修改他人文件所有权可能失败\n");
    return 0;
}

int main() {
    printf("=== 高级所有权管理和安全特性示例 ===\n");
    
    uid_t current_uid = getuid();
    printf("当前用户ID: %d (%s)\n", (int)current_uid, 
           current_uid == 0 ? "root" : "非root");
    
    // 创建安全测试环境
    printf("\n1. 创建安全测试环境:\n");
    if (create_secure_test_environment() == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 打开测试目录
    int test_dirfd = open("secure_ownership_test", O_RDONLY);
    if (test_dirfd == -1) {
        perror("打开测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    // 显示初始状态
    printf("\n2. 初始文件状态:\n");
    DIR* dir = fdopendir(dup(test_dirfd));
    if (dir) {
        struct dirent* entry;
        while ((entry = readdir(dir)) != NULL) {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                struct stat file_stat;
                if (fstatat(test_dirfd, entry->d_name, &file_stat, AT_SYMLINK_NOFOLLOW) == 0) {
                    struct passwd* pwd = getpwuid(file_stat.st_uid);
                    struct group* grp = getgrgid(file_stat.st_gid);
                    printf("  %s: UID=%d(%s) GID=%d(%s) 权限=%o\n",
                           entry->d_name,
                           (int)file_stat.st_uid, pwd ? pwd->pw_name : "unknown",
                           (int)file_stat.st_gid, grp ? grp->gr_name : "unknown",
                           file_stat.st_mode & 0777);
                }
            }
        }
        closedir(dir);
    }
    
    // 演示安全的所有权修改
    printf("\n3. 安全所有权修改演示:\n");
    
    // 获取测试用户
    struct passwd* test_user1 = getpwnam("nobody");
    struct passwd* test_user2 = getpwnam("daemon");
    uid_t user1_uid = test_user1 ? test_user1->pw_uid : 65534;
    uid_t user2_uid = test_user2 ? test_user2->pw_uid : 1;
    
    struct group* test_group1 = getgrnam("nobody");
    struct group* test_group2 = getgrnam("daemon");
    gid_t group1_gid = test_group1 ? test_group1->gr_gid : 65534;
    gid_t group2_gid = test_group2 ? test_group2->gr_gid : 1;
    
    // 安全修改配置文件所有权
    secure_chownat(test_dirfd, "config.txt", user1_uid, group1_gid, 0, 
                   "配置文件所有权转移");
    verify_ownership_change(test_dirfd, "config.txt", user1_uid, group1_gid);
    
    // 安全修改日志文件所有权
    secure_chownat(test_dirfd, "logs/app.log", user2_uid, group2_gid, 0, 
                   "日志文件所有权转移");
    verify_ownership_change(test_dirfd, "logs/app.log", user2_uid, group2_gid);
    
    // 演示符号链接处理
    printf("\n4. 符号链接处理演示:\n");
    
    // 获取符号链接当前状态
    struct stat link_stat, target_stat;
    if (fstatat(test_dirfd, "link_to_config", &link_stat, AT_SYMLINK_NOFOLLOW) == 0 &&
        fstatat(test_dirfd, "link_to_config", &target_stat, 0) == 0) {
        printf("符号链接状态:\n");
        printf("  链接文件: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
        printf("  目标文件: UID=%d, GID=%d\n", 
               (int)target_stat.st_uid, (int)target_stat.st_gid);
    }
    
    // 默认行为:修改目标文件
    secure_chownat(test_dirfd, "link_to_config", user1_uid + 1, group1_gid + 1, 0, 
                   "修改符号链接目标");
    verify_ownership_change(test_dirfd, "config.txt", user1_uid + 1, group1_gid + 1);
    
    // 使用AT_SYMLINK_NOFOLLOW:修改链接本身
    secure_chownat(test_dirfd, "link_to_config", user2_uid + 1, group2_gid + 1, 
                   AT_SYMLINK_NOFOLLOW, "修改符号链接本身");
    
    // 验证符号链接状态
    if (fstatat(test_dirfd, "link_to_config", &link_stat, AT_SYMLINK_NOFOLLOW) == 0) {
        printf("修改后链接文件所有权: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
    }
    
    // 演示错误处理和权限检查
    printf("\n5. 错误处理和权限检查演示:\n");
    
    // 尝试修改不存在的文件
    secure_chownat(test_dirfd, "nonexistent.txt", 0, 0, 0, "测试不存在文件");
    
    // 尝试使用无效的UID/GID
    secure_chownat(test_dirfd, "config.txt", 999999, 999999, 0, "测试无效UID/GID");
    
    // 尝试在只读文件系统上修改(如果可能)
    // 这个测试需要特殊的环境设置
    
    // 显示所有修改记录
    show_change_records();
    
    // 统计成功和失败的修改
    int success_count = 0, failure_count = 0;
    for (int i = 0; i < record_count; i++) {
        if (change_records[i].success) {
            success_count++;
        } else {
            failure_count++;
        }
    }
    
    printf("修改统计:\n");
    printf("  成功: %d\n", success_count);
    printf("  失败: %d\n", failure_count);
    printf("  总计: %d\n", record_count);
    
    // 清理测试环境
    printf("\n6. 清理测试环境:\n");
    
    // 删除文件
    const char* test_files[] = {
        "secure_ownership_test/link_to_config",
        "secure_ownership_test/config.txt",
        "secure_ownership_test/private.key",
        "secure_ownership_test/database.db",
        "secure_ownership_test/logs/app.log"
    };
    
    for (int i = 0; i < 5; i++) {
        if (unlink(test_files[i]) == 0) {
            printf("删除文件: %s\n", test_files[i]);
        }
    }
    
    // 删除目录
    rmdir("secure_ownership_test/logs");
    rmdir("secure_ownership_test");
    printf("删除测试目录\n");
    
    close(test_dirfd);
    
    printf("\n=== 高级所有权管理演示完成 ===\n");
    
    return 0;
}

编译和运行

# 编译示例1
sudo gcc -o fchownat_example1 fchownat_example1.c
sudo ./fchownat_example1

# 编译示例2
sudo gcc -o fchownat_example2 fchownat_example2.c
sudo ./fchownat_example2

# 编译示例3
sudo gcc -o fchownat_example3 fchownat_example3.c
sudo ./fchownat_example3

# 编译示例4
sudo gcc -o fchownat_example4 fchownat_example4.c
sudo ./fchownat_example4

重要注意事项

  1. 权限要求: 修改文件所有权通常需要root权限或CAP_CHOWN能力
  2. 相对路径: 使用dirfd参数支持相对路径操作
  3. 符号链接: 默认修改目标文件,使用AT_SYMLINK_NOFOLLOW修改链接本身
  4. 错误处理: 必须检查返回值并适当处理错误
  5. 安全性: 验证输入参数以防止安全问题
  6. 原子性: fchownat操作是原子的
  7. 审计: 建议记录所有权修改操作

最佳实践

  1. 权限检查: 在修改前检查必要的权限
  2. 输入验证: 验证路径和ID参数的有效性
  3. 错误处理: 完善的错误处理和日志记录
  4. 原子操作: 利用fchownat的原子性特性
  5. 安全审计: 记录所有权修改操作
  6. 批量操作: 使用相对路径提高批量操作效率
  7. 符号链接: 明确处理符号链接的行为

通过这些示例,你可以理解fchownat在文件所有权管理方面的强大功能,它为Linux系统提供了灵活、安全的文件权限控制能力,特别适用于系统管理、安全控制和批量文件操作等场景。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注