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