第 15 章 文件属性 (File Attributes)
核心结论
-
stat() 系列:
stat(path, &sb)查 pathname 文件;lstat(path, &sb)不跟随符号链接;fstat(fd, &sb)查已打开的 fd;返回 struct stat 包含文件全部元数据。 -
struct stat 字段:st_dev(设备)、st_ino(i-node 号)、st_mode(类型+权限)、st_nlink(硬链接数)、st_uid/st_gid、st_size、st_atime/mtime/ctime、st_blksize、st_blocks。
-
文件类型:从 st_mode 提取——S_ISREG/S_ISDIR/S_ISCHR/S_ISBLK/S_ISLNK/S_ISFIFO/S_ISSOCK 宏;S_IFMT 掩码后比较。
-
文件权限:9 位 (rwxrwxrwx) + 3 特殊位 (setuid/setgid/sticky);用 S_IRUSR 等宏比较;chmod/fchmod 改变。
-
文件时间戳:atime(访问)、mtime(数据修改)、ctime(i-node 状态变更);utime/utimes 修改;utimensat 纳秒精度。
-
i-node 标志 (chattr):ext2 扩展属性——append-only (a)、immutable (i)、no-dump (d)、sync (s) 等;lsattr 查看。
-
设备 ID 提取:major() 和 minor() 宏提取设备 major/minor(需 _BSD_SOURCE)。
|
本章主旨
本章是文件元数据查询的核心。核心:(1) stat/lstat/fstat 三个系统调用;(2) struct stat 的所有字段含义;(3) 文件类型与权限的提取;(4) 时间戳的修改与影响。本章不展开 ACL(详见第 17 章)、硬链接/符号链接(详见第 18 章)、扩展属性(详见第 16 章)。 |
一、核心概念
本章围绕 6 个核心概念展开:从「stat 系统调用」到「struct stat 字段」到「权限与时间戳」。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
stat/lstat/fstat |
|
§15.1; |
struct stat 字段 |
st_dev、st_ino、st_mode、st_nlink、st_uid/st_gid、st_size、st_atime/mtime/ctime、st_blksize、st_blocks。 |
§15.1;st_dev + st_ino 唯一标识文件。 |
文件类型提取 |
|
§15.1;类型有 7 种:reg/dir/lnk/chr/blk/fifo/sock。 |
权限位与特殊位 |
9 位权限(owner/group/other × rwx)+ 3 特殊位(setuid/setgid/sticky);用 S_IRUSR 等宏比较。 |
§15.4;特殊位有 ls 时的 s/S 标记。 |
文件时间戳 |
atime 访问、mtime 数据修改、ctime i-node 变更;utime/utimes 修改 atime+mtime。 |
§15.2;O_NOATIME flag 可禁止 atime 更新。 |
i-node 标志 |
ext2 扩展属性:append-only (a)、immutable (i)、no-dump (d) 等;chattr 设置、lsattr 查看。 |
§15.5;仅 root 可设; |
二、详细笔记
15.1 stat() 系统调用
What:stat/lstat/fstat 是查询文件元数据的核心系统调用;返回 struct stat,包含几乎所有文件属性。
Why:理解 stat 是理解所有文件元数据相关操作(ls、find、chmod)的基础;写程序需要「文件是什么类型」「多大」「什么时候修改的」时用 stat。
How:
// 摘自《The Linux Programming Interface》第 15 章
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
// 查 pathname;跟随符号链接
int lstat(const char *pathname, struct stat *statbuf);
// 查 pathname;不跟随符号链接(用于查看链接本身)
int fstat(int fd, struct stat *statbuf);
// 查已打开的 fd
// 全部成功返回 0;失败返回 -1
权限要求(§15.1):
-
stat/lstat:需要父目录的 execute (search) 权限;不需要文件本身的权限。
-
fstat:只要 fd 有效。
When:
-
想看符号链接指向的文件——
stat。 -
想看符号链接本身——
lstat。 -
已经打开文件——
fstat(避免路径名解析)。
Example:
// 摘自《The Linux Programming Interface》第 15 章 files/t_stat.c
struct stat sb;
if (stat(argv[1], &sb) == -1) errExit("stat");
printf("File type: ");
if (S_ISREG(sb.st_mode)) printf("regular\n");
else if (S_ISDIR(sb.st_mode)) printf("directory\n");
else if (S_ISLNK(sb.st_mode)) printf("symlink\n");
// ...
printf("Size: %lld bytes\n", (long long) sb.st_size);
printf("Inode: %ld\n", (long) sb.st_ino);
15.2 struct stat 字段详解
What:struct stat 是 stat 系统调用的返回结构;包含文件所有元数据。
Why:理解每个字段的含义是正确使用 stat 的前提。
How:字段定义(§15.1):
struct stat {
dev_t st_dev; // 文件所在设备
ino_t st_ino; // i-node 号
mode_t st_mode; // 文件类型 + 权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // owner UID
gid_t st_gid; // owner GID
dev_t st_rdev; // 设备文件专用:设备 major/minor
off_t st_size; // 文件大小(字节)
blksize_t st_blksize; // 最优 I/O 块大小(字节)
blkcnt_t st_blocks; // 分配的 512 字节块数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后数据修改时间
time_t st_ctime; // 最后 i-node 状态变更时间
};
| 字段 | 关键点 |
|---|---|
|
跨 FS 唯一标识文件 |
|
编码文件类型 + 权限 + 特殊位 |
|
硬链接数;为 0 时数据被释放 |
|
owner;可被 chown 改变 |
|
字节数;普通文件为文件大小;符号链接为目标路径长度 |
|
推荐 I/O 块大小(不是 FS 块大小) |
|
实际分配块数(512 字节单位);空洞文件 < size/512 |
|
访问时间; |
|
数据修改时间 |
|
i-node 状态变更时间(不可直接修改) |
提取 major/minor(§15.1):
#define _BSD_SOURCE
#include <sys/types.h>
#include <sys/sysmacros.h>
unsigned int major(dev_t dev);
unsigned int minor(dev_t dev);
// 返回值类型随实现变化;打印时强转 unsigned int
When:
-
唯一标识文件——
(st_dev, st_ino)。 -
看磁盘使用——
st_blocks * 512(或du -k)。 -
检测空洞文件——
st_blocks * 512 < st_size。 -
看设备——
major/minor(st_rdev)(设备文件)。
Example:
// 摘自《The Linux Programming Interface》第 15 章
struct stat sb;
stat(path, &sb);
printf("Device: %d:%d\n", major(sb.st_dev), minor(sb.st_dev));
printf("Inode: %ld\n", (long) sb.st_ino);
printf("Size: %lld, Allocated: %lld*512\n",
(long long) sb.st_size, (long long) sb.st_blocks);
15.3 文件类型提取
What:st_mode 字段编码了文件类型;通过 S_IFMT 掩码后与常量比较,或用 S_ISXXX 宏判断。
Why:程序需要根据文件类型决定操作(如「是目录就递归」「是设备文件就用 read/write」)——需要正确提取类型。
How:
// 摘自《The Linux Programming Interface》第 15 章
// 类型掩码和常量
if ((sb.st_mode & S_IFMT) == S_IFREG) /* 普通文件 */
if (S_ISREG(sb.st_mode)) /* 同上,宏更易读 */
7 种文件类型(§15.1 表 15-1):
| 类型 | S_IFXXX 宏 |
|---|---|
S_ISXXX 宏 |
普通文件 |
|
|
目录 |
|
|
字符设备 |
|
|
块设备 |
|
|
FIFO(命名管道) |
|
|
套接字 |
|
|
符号链接 |
|
|
When:
-
ls -l 实现——用 S_ISXXX 宏判断类型并显示对应字符。
-
写文件浏览器——根据类型显示图标或展开目录。
-
写文件处理工具——只处理特定类型。
Example:
// 摘自《The Linux Programming Interface》第 15 章
struct stat sb;
if (lstat(path, &sb) == -1) errExit("lstat");
switch (sb.st_mode & S_IFMT) {
case S_IFREG: printf("regular file\n"); break;
case S_IFDIR: printf("directory\n"); break;
case S_IFLNK: printf("symbolic link\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFBLK: printf("block device\n"); break;
case S_IFIFO: printf("FIFO\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n");
}
15.4 文件权限
What:st_mode 的低 12 位编码文件权限:9 位 rwx + 3 位特殊(setuid/setgid/sticky)。
Why:理解权限是写安全程序、理解 set-UID 程序、设计文件共享方案的前提。
How:权限位(§15.4):
| 位 | 含义 |
|---|---|
对目录 |
|
owner 读 |
可列出文件 |
|
owner 写 |
可添加/删除/重命名 |
|
owner 执行 |
可搜索(cd/访问内文件) |
|
group 读 |
同上 |
|
group 执行 |
同上 |
|
other 读 |
同上 |
|
other 执行 |
同上 |
|
set-user-ID |
不适用 |
|
set-group-ID |
目录内文件继承组 |
|
sticky |
权限检查算法:
-
如果进程的 effective UID == 文件 st_uid → 用 owner 权限。
-
否则如果进程的 effective GID 或任一 supplementary GID == 文件 st_gid → 用 group 权限。
-
否则用 other 权限。
chmod/fchmod(§15.4):
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
// 成功返回 0;进程需是 file owner 或有 CAP_FOWNER
When:
-
ls -l 显示权限。
-
写文件前检查——
access(path, R_OK)或 open 失败。 -
协作组共享——chmod g+rw,chown :group。
-
/tmp 防止互删——chmod +t。
Example:
// 摘自《The Linux Programming Interface》第 15 章
if (chmod("file", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
errExit("chmod");
// 等价于 chmod 644 file
// 打开 set-UID
if (chmod("prog", S_ISUID | S_IXUSR) == -1)
errExit("chmod");
// 等价于 chmod u+s prog
15.5 文件时间戳
What:每个文件有 3 个时间戳:atime(访问)、mtime(数据修改)、ctime(i-node 状态变更)。
Why:时间戳是 make(1)、日志轮转、备份策略、文件同步的基础。
How:
| 字段 | 何时更新 |
|---|---|
谁能直接改 |
|
|
utime/utimes/utimensat |
|
|
同上 |
|
任何 i-node 状态变更(权限、owner、链接数等) |
内核自动;不可直接改 |
修改时间戳(§15.2):
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
// 精度 1 秒;times == NULL → 设为当前时间
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
// 精度微秒;times[0] = atime, times[1] = mtime
// Linux 特有
#include <fcntl.h>
int utimensat(int fd, const char *path, const struct timespec times[2], int flags);
// 精度纳秒;可与 AT_SYMLINK_NOFOLLOW 一起用
// times[0] = atime, times[1] = mtime
O_NOATIME flag:
// 摘自《The Linux Programming Interface》第 4 章
int fd = open("file", O_RDONLY | O_NOATIME);
// read 不更新 atime;减少磁盘写
// 需要 file owner 或 CAP_FOWNER
When:
-
make 判断文件新鲜度——mtime vs 依赖文件 mtime。
-
日志轮转——按 mtime 删旧日志。
-
cp -p 保留时间戳——
cp --preserve=timestamps。 -
高性能读取——O_NOATIME 减少元数据写。
Example:
// 摘自《The Linux Programming Interface》第 15 章
// 设置 atime 和 mtime 为特定时间(纳秒精度)
struct timespec times[2];
times[0].tv_sec = 1234567890; times[0].tv_nsec = 0; // atime
times[1].tv_sec = 1234567891; times[1].tv_nsec = 0; // mtime
if (utimensat(AT_FDCWD, "file", times, 0) == -1)
errExit("utimensat");
15.6 i-node 标志 (chattr)
What:i-node 标志(也称 ext2 扩展属性)是文件级标志,控制内核对文件的特殊处理;仅 ext2/3/4 支持。
Why:append-only 标志让日志文件只能追加;immutable 标志让文件不可修改——安全和数据完整性保护。
How:
| 标志 | 含义 |
|---|---|
|
只能追加;不能覆盖/截断 |
|
不可修改(任何方式);只能 root 解除 |
|
dump(8) 跳过 |
|
修改同步写(不经过 page cache) |
|
不更新 atime |
|
透明压缩(内核支持时) |
|
允许恢复删除的文件 |
chattr 命令设置;lsattr 查看;需 root 权限:
# 摘自《The Linux Programming Interface》第 15 章
# 设置 append-only
$ chattr +a /var/log/important.log
# 设置 immutable(root 自己也改不了)
$ chattr +i /etc/passwd
# 查看
$ lsattr /etc/passwd
----i-------e- /etc/passwd
When:
-
日志文件——
+a防止误覆盖/篡改。 -
关键配置——
+i防止误删。 -
高完整性需求——
+s强制同步写。
三、关键图表
|
非可视化条目(stat API 速查)
|
四、思维导图
mindmap
root((第 15 章 文件属性))
stat 系列
stat 跟随链接
lstat 不跟随
fstat 已打开
struct stat
st_dev st_ino
st_mode
st_uid st_gid
st_size st_blocks
atime mtime ctime
文件类型
7 种类型
S_IFMT 掩码
S_ISXXX 宏
权限
9 位 rwx
3 特殊位
setuid setgid sticky
chmod fchmod
时间戳
atime 访问
mtime 数据
ctime 状态
utime utimes
utimensat 纳秒
O_NOATIME
i node 标志
chattr
a append
i immutable
s sync
仅 root
五、重点与易错点
-
st_dev + st_ino 唯一标识文件:跨文件系统也唯一(除非设备复用 i-node 号)。
-
符号链接的 st_size 是路径长度:不是目标大小。
-
lstat vs stat:lstat 看链接本身;stat 看链接指向——写 find、ls 必须正确选择。
-
st_blocks 单位是 512 字节:即使 FS 块大小是 4096。
-
chmod 需要 owner 或 root:普通用户不能 chmod 别人的文件。
-
ctime 不能直接修改:ctime 由内核在 i-node 状态变更时自动更新。
-
O_NOATIME 需要 owner 权限:否则 EPERM;防止读取他人文件隐藏访问时间。
-
st_blksize ≠ FS 块大小:是「最优 I/O 块大小」,可能因 FS 而异。
-
chattr +i 后即使 root 也改不了:需要先 chattr -i(但 root 不能被锁在外面)。
-
stat 比 ls 解析更可靠:ls 输出格式可能变;stat 字段稳定。
-
跨章衔接:第 14 章文件系统;本章文件属性;第 16 章扩展属性;第 17 章 ACL;第 18 章链接与目录。