前兩篇介紹了文件 I/O 基礎操作,本章聚焦文件系統為每個文件維護的元數據——即 struct stat 結構體中的屬性信息。掌握這些操作是系統級程式設計、檔案工具開發、權限管理的基石。
stat 家族#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf); // 依路徑獲取,會展開符號鏈接
int fstat(int fd, struct stat *buf); // 依已打開文件描述符獲取
int lstat(const char *path, struct stat *buf); // 依路徑獲取,不展開符號鏈接
命名慣例:fun() 基礎版、ffun() 接受 fd、lfun() 不展開符號鏈接。此模式貫穿 Linux 系統調用(如 chmod/fchmod/lchmod、chown/fchown/lchown)。
struct stat 關鍵字段詳解| 字段 | 類型 | 含義 |
|---|---|---|
st_dev | dev_t | 包含文件的設備 ID |
st_ino | ino_t | inode 號(文件唯一標識) |
st_mode | mode_t | 文件類型 + 權限位圖(見下文) |
st_nlink | nlink_t | 硬鏈接計數 |
st_uid/st_gid | uid_t/gid_t | 擁有者用戶/組 ID |
st_rdev | dev_t | 設備文件的主/次設備號 |
st_size | off_t | 文件位元組大小(邏輯大小) |
st_blksize | blksize_t | 文件系統塊大小(通常 4KB) |
st_blocks | blkcnt_t | 佔用的 512B 塊數(實際磁盤佔用 = st_blocks × 512) |
st_atime/st_mtime/st_ctime | time_t | 最後訪問/修改/元數據修改時間 |
關鍵洞見:文件實際磁盤佔用看
st_blocks,而非st_size。稀疏文件(如lseek跳過再寫入)st_size可達 GB 級,但st_blocks僅佔幾塊。
st_mode 位圖)Linux 7 種文件類型:
d、字符設備 c、塊設備 b、普通文件 -、符號鏈接 l、套接字 s、管道 p對應提取宏(S_IFMT 遮罩 + S_IFREG 等):
struct stat info;
lstat("file.txt", &info);
if (info.st_mode & S_IFMT == S_IFREG) // 普通文件
if (S_ISREG(info.st_mode)) // 推薦:直接用判斷宏
七個判斷宏:S_ISREG、S_ISDIR、S_ISCHR、S_ISBLK、S_ISFIFO、S_ISLNK、S_ISSOCK,傳入 st_mode 回傳 1/0。
st_mode 低 9 位)三組權限:Owner(位 6-8)、Group(位 3-5)、Other(位 0-2),各含 r(4)/w(2)/x(1)。
常用提取宏:
S_IRWXU (0700) 擁有者全權遮罩S_IRUSR (0400)、S_IWUSR (0200)、S_IXUSR (0100)S_IRWXG (0070)、S_IRGRP (0040)…S_IRWXO (0007)、S_IROTH (0004)…| 函數 | 功能 | 關鍵點 |
|---|---|---|
access(path, mode) | 檢查實際用戶權限 | F_OK 存在性、R/W/X_OK 讀寫執行;不展開符號鏈接 |
umask(mask) | 設定進程文件創建掩碼 | 返回舊值;mode & ~umask 為最終權限 |
chmod/fchmod | 修改文件權限 | 支援 S_ISUID/S_ISGID/S_ISVTX |
chown/fchown/lchown | 修改文件擁有者 | 僅 root 可用(防配額繞過) |
truncate/ftruncate | 截斷/擴展文件到指定長度 | 擴展時以 \0 填充(建立稀疏文件) |
link/unlink | 創建/刪除硬鏈接 | 硬鏈接不跨分區、不可對目錄 |
symlink/readlink | 創建/讀取符號鏈接 | 符號鏈接有獨立 inode、可跨分區 |
rename | 重命名/移動文件 | 原子操作(同分區) |
utime | 修改時間戳 | 常用於備份工具保留原時間 |
mkdir/rmdir | 創建/刪除目錄 | rmdir 僅能刪空目錄 |
chdir/fchdir | 改變工作目錄 | 守護進程常用 chdir("/") |
getcwd | 獲取當前工作目錄絕對路徑 | 需提供緩衝區 |
glob() 模式匹配glob_t g;
glob("/home/user/*", 0, NULL, &g); // 非隱藏文件
glob("/home/user/.*", GLOB_APPEND, NULL, &g); // 隱藏文件(追加)
for (size_t i = 0; i < g.gl_pathc; i++) puts(g.gl_pathv[i]);
globfree(&g);
./.. 避免循環)。globfree()。opendir/readdir/closedir 串流式DIR *dp = opendir(path);
struct dirent *ep;
while ((ep = readdir(dp)) != NULL) {
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) continue;
// 處理 ep->d_name
}
closedir(dp);
d_type 判斷 DT_DIR)。telldir/seekdir 可記錄/恢復讀取位置,較少用。du 實現(計算目錄實際磁盤佔用)兩種版本分別用 glob 與 readdir 實現,核心邏輯:
lstat 獲取 st_blocks。st_blocks(除 2 換算 KB)。./.. 防循環。int64_t mydu(const char *path) {
struct stat buf;
lstat(path, &buf);
int64_t sum = buf.st_blocks;
if (S_ISDIR(buf.st_mode)) {
DIR *dp = opendir(path);
struct dirent *ep;
char subpath[1024];
while ((ep = readdir(dp))) {
if (strcmp(ep->d_name, ".")==0 || strcmp(ep->d_name, "..")==0) continue;
snprintf(subpath, sizeof(subpath), "%s/%s", path, ep->d_name);
sum += mydu(subpath);
}
closedir(dp);
}
return sum;
}
掌握文件元數據操作,是從「寫應用」進階到「寫系統工具、中間件、容器運行時」的關鍵門檻。