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;
}