66. fchown - 修改文件所有者(通过文件描述符) 链接到标题

1. 函数介绍 链接到标题

fchown 是一个 Linux 系统调用,用于修改已打开文件的所有者(owner)和所属组(group)。与 chown 不同,fchown 通过文件描述符而不是文件路径来指定要修改的文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

你可以把它想象成通过"门把手"而不是"地址"来找到房子并更换房主,这样即使房子的地址变了,你仍然可以通过门把手找到它。

2. 函数原型 链接到标题

#include <unistd.h>

int fchown(int fd, uid_t owner, gid_t group);

3. 功能 链接到标题

修改通过文件描述符指定的文件的所有者和所属组。如果只想修改所有者或所属组中的一个,可以将另一个参数设置为 -1。

4. 参数 链接到标题

  • int fd: 文件描述符,通过 open() 等函数获得
  • uid_t owner: 新的所有者用户 ID
    • 有效的用户 ID:设置为指定用户所有
    • (uid_t)-1:保持当前所有者不变
  • gid_t group: 新的所属组 ID
    • 有效的组 ID:设置为指定组所有
    • (gid_t)-1:保持当前所属组不变

5. 返回值 链接到标题

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

6. 相似函数,或关联函数 链接到标题

  • chown(): 通过文件路径修改文件所有者
  • fchownat(): 相对于目录文件描述符修改文件所有者
  • lchown(): 修改符号链接本身的所有者(而不是链接指向的文件)
  • getuid(): 获取当前用户 ID
  • getgid(): 获取当前组 ID

7. 示例代码 链接到标题

示例1:基本使用 - 修改文件所有者 链接到标题

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

int main() {
    int fd;
    int ret;
    
    // 创建测试文件
    fd = open("test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建文件,文件描述符: %d\n", fd);
    
    // 写入一些内容
    const char *content = "这是一个测试文件\n";
    write(fd, content, strlen(content));
    
    // 修改文件所有者为 root (uid=0),组为 root (gid=0)
    // 注意:这需要适当的权限(通常是 root 权限)
    ret = fchown(fd, 0, 0);
    if (ret == -1) {
        if (errno == EPERM) {
            printf("权限不足:需要 root 权限才能将文件所有者改为 root\n");
        } else {
            perror("fchown 调用失败");
        }
    } else {
        printf("成功将文件所有者修改为 root:root\n");
    }
    
    // 只修改所有者,保持组不变
    ret = fchown(fd, getuid(), -1);  // -1 表示不修改组
    if (ret == -1) {
        perror("修改所有者失败");
    } else {
        printf("成功将文件所有者修改为当前用户\n");
    }
    
    // 只修改组,保持所有者不变
    ret = fchown(fd, -1, getgid());  // -1 表示不修改所有者
    if (ret == -1) {
        perror("修改组失败");
    } else {
        printf("成功将文件组修改为当前组\n");
    }
    
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

示例2:检查权限并安全修改所有者 链接到标题

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

int main() {
    int fd;
    int ret;
    struct stat file_info;
    
    // 打开系统文件进行测试
    fd = open("/etc/passwd", O_RDONLY);
    if (fd == -1) {
        perror("打开 /etc/passwd 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功打开文件,文件描述符: %d\n", fd);
    
    // 获取文件当前信息
    if (fstat(fd, &file_info) == -1) {
        perror("获取文件信息失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("文件当前信息:\n");
    printf("  所有者 UID: %d\n", file_info.st_uid);
    printf("  所属组 GID: %d\n", file_info.st_gid);
    printf("  文件大小: %ld 字节\n", file_info.st_size);
    
    // 尝试修改所有者(这通常会失败,除非我们有适当权限)
    ret = fchown(fd, 1000, 1000);  // 尝试修改为 UID 1000, GID 1000
    if (ret == -1) {
        switch (errno) {
            case EPERM:
                printf("权限拒绝:修改文件所有者需要适当权限\n");
                break;
            case EBADF:
                printf("无效的文件描述符\n");
                break;
            case EROFS:
                printf("文件位于只读文件系统上\n");
                break;
            case EIO:
                printf("I/O 错误\n");
                break;
            default:
                printf("其他错误: %s\n", strerror(errno));
                break;
        }
    } else {
        printf("成功修改文件所有者\n");
    }
    
    close(fd);
    printf("文件已关闭\n");
    
    return 0;
}

67. fchownat - 相对于目录文件描述符修改文件所有者 链接到标题

1. 函数介绍 链接到标题

fchownatfchown 的扩展版本,允许相对于指定目录文件描述符修改文件的所有者。它结合了 fchownchownat 的特性,提供了更灵活的文件所有者修改方式。

这个函数特别适用于处理相对路径和在受限环境中操作文件,比如容器或沙箱环境。

2. 函数原型 链接到标题

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

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

3. 功能 链接到标题

修改相对于目录文件描述符的文件所有者和所属组。支持相对路径和额外的控制标志。

4. 参数 链接到标题

  • int dirfd: 目录文件描述符
    • AT_FDCWD: 使用当前工作目录
    • 有效的目录文件描述符:相对于该目录进行操作
  • const char *pathname: 文件路径名(相对或绝对)
  • uid_t owner: 新的所有者用户 ID
    • 有效的用户 ID:设置为指定用户所有
    • (uid_t)-1:保持当前所有者不变
  • gid_t group: 新的所属组 ID
    • 有效的组 ID:设置为指定组所有
    • (gid_t)-1:保持当前所属组不变
  • int flags: 控制标志
    • 0: 基本行为
    • AT_SYMLINK_NOFOLLOW: 不跟随符号链接(修改符号链接本身)

5. 返回值 链接到标题

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

6. 相似函数,或关联函数 链接到标题

  • fchown(): 通过文件描述符修改文件所有者
  • chown(): 通过路径修改文件所有者
  • lchown(): 修改符号链接本身的所有者
  • fchmodat(): 相对于目录文件描述符修改文件权限
  • openat(): 相对于目录文件描述符打开文件

7. 示例代码 链接到标题

示例1:基本使用 - 相对于目录修改文件所有者 链接到标题

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

int main() {
    int dirfd;
    int ret;
    
    // 创建测试目录
    if (mkdir("test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    // 在测试目录中创建文件
    int fd = open("test_dir/test_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    
    const char *content = "测试文件内容\n";
    write(fd, content, strlen(content));
    close(fd);
    
    // 打开目录获取文件描述符
    dirfd = open("test_dir", O_RDONLY);
    if (dirfd == -1) {
        perror("打开目录失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功打开目录,目录文件描述符: %d\n", dirfd);
    
    // 相对于目录文件描述符修改文件所有者
    // 注意:这需要适当的权限
    ret = fchownat(dirfd, "test_file.txt", -1, -1, 0);
    if (ret == -1) {
        if (errno == EPERM) {
            printf("权限不足:需要适当权限才能修改文件所有者\n");
        } else if (errno == ENOTDIR) {
            printf("dirfd 不是目录文件描述符\n");
        } else {
            perror("fchownat 调用失败");
        }
    } else {
        printf("成功修改文件所有者\n");
    }
    
    // 使用 AT_FDCWD(当前工作目录)修改文件所有者
    ret = fchownat(AT_FDCWD, "test_dir/test_file.txt", -1, -1, 0);
    if (ret == -1) {
        perror("使用 AT_FDCWD 修改所有者失败");
    } else {
        printf("使用 AT_FDCWD 成功修改文件所有者\n");
    }
    
    close(dirfd);
    printf("目录已关闭\n");
    
    return 0;
}

示例2:处理符号链接 链接到标题

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

int main() {
    int ret;
    
    // 创建测试文件
    int fd = open("target_file.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建目标文件失败");
        exit(EXIT_FAILURE);
    }
    close(fd);
    
    // 创建指向目标文件的符号链接
    if (symlink("target_file.txt", "link_to_file") == -1) {
        if (errno != EEXIST) {
            perror("创建符号链接失败");
            exit(EXIT_FAILURE);
        }
    }
    
    printf("创建了符号链接 link_to_file -> target_file.txt\n");
    
    // 默认行为:跟随符号链接,修改目标文件的所有者
    ret = fchownat(AT_FDCWD, "link_to_file", -1, -1, 0);
    if (ret == -1) {
        perror("修改符号链接指向的文件失败");
    } else {
        printf("修改了符号链接指向的文件的所有者\n");
    }
    
    // 使用 AT_SYMLINK_NOFOLLOW:修改符号链接本身的所有者
    ret = fchownat(AT_FDCWD, "link_to_file", -1, -1, AT_SYMLINK_NOFOLLOW);
    if (ret == -1) {
        if (errno == EOPNOTSUPP) {
            printf("当前文件系统不支持修改符号链接的所有者\n");
        } else {
            perror("修改符号链接本身失败");
        }
    } else {
        printf("修改了符号链接本身的所有者\n");
    }
    
    // 清理测试文件
    unlink("target_file.txt");
    unlink("link_to_file");
    
    return 0;
}

总结 链接到标题

fchownfchownat 是用于修改文件所有者和所属组的重要系统调用:

  1. fchown 通过文件描述符操作,避免了路径相关的竞态条件
  2. fchownat 提供了更灵活的操作方式,支持相对路径和额外标志
  3. 这些函数通常需要适当的权限(如 root 权限)才能修改文件所有者
  4. 正确处理返回值和错误情况对于编写健壮的程序非常重要
  5. 在容器和沙箱环境中,这些函数提供了更安全的文件操作方式