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;
}
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");
#endifunlink("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;
}