56. fchmodat - 相对路径改变文件权限 見出しへのリンク
函数介绍 見出しへのリンク
fchmodat
是一个Linux系统调用,用于相对于指定目录文件描述符改变文件的访问权限。它是chmod
和fchmod
的扩展版本。
函数原型 見出しへのリンク
#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;
}