利用系统调用 stat()、 lstat()以及 fstat(),可获取与文件有关的信息,其中大部分提取自文件 i 节点。
include<sys/stat.h>
int stat(const char *pathname,struct stat *statbuf);
int lstat(const char *pathname,struct stat *statbuf);
int fstat(int fd,struct stat *statbuf);
以上 3 个系统调用之间仅有的区别在于对文件的描述方式不同。 stat()会返回所命名文件的相关信息。 lstat()与 stat()类似,区别在于如果文件属于符号链接,那么所返回的信息针对的是符号链接自身(而非符号链接所指向的文件)。 fstat()则会返回由某个打开文件描述符所指代文件的相关信息。 系 统 调 用 stat() 和 lstat() 无需对其所操作的文件本身拥有任何权限, 但 针对 指 定pathname 的父目录要有执行(搜索)权限。而只要供之以有效的文件描述符, fstat()系统调用总是成功
上述所有系统调用都会在缓冲区中返回一个由 statbuf 指向的 stat 结构,其格式如下
struct stat{
dev_t st_dev;/*文件所驻留的设备,设备主、辅ID*/
ino_t st_ino;/*i-node节点号*/
mode_t st_mode;/*文件类型与权限*/
nlink_t st_nlink;/*硬链接数*/
uid_t st_uid;/*属主ID*/
gid_t st_gid;/*组ID*/
dev_t st_rdev;/*硬盘特殊文件ID*/
off_t st_size;/*文件大小,单位字节*/
blksize_t st_blksize;/*块大小*/
blkcnt_t st_blocks;/*文件使用的总块数*/
time_t st_atime;/*最后访问时间*/
time_t st_mtime;/*最后更新时间*/
time_t st_ctime;/*最后状态更改时间*/
}
设备 ID 和 i 节点号
st_dev 字段标识文件所驻留的设备。 st_ino 字段则包含了文件的 i 节点号。利用以上两者,可在所有文件系统中唯一标识某个文件。 dev_t 类型记录了设备的主、辅 ID。 如果是针对设备的 i 节点,那么 st_rdev 字段则包含设备的主、辅 ID。 利用宏 major()和 minor(), 可提取 dev_t 值的主、 辅 ID。 获取对两个宏声明的头文件则随 UNIX实现而各异。在 Linux 系统上,若定义了_BSD_SOURCE 宏,则两个宏定义于<sys/types.h>中。 由 major()和 minor()所返回的整形值大小也随 UNIX 实现的不同而各不相同。为保证可移植性,打印时应总是将返回值强制转换为 long。
文件所有权
st_uid 和 st_gid 字段分别标识文件的属主(用户 ID)和属组(组 ID)。
链接数
st_nlink 字段包含了指向文件的(硬)链接数。本书第 18 章将详细介绍链接。
文件类型及权限
st_mode 字段内含有位掩码,起标识文件类型和指定文件权限的双重作用。 所示为该字段所含各位的布局情况
与常量 S_IFMT 相与(&),可从该字段中析取文件类型。将计算结果与一系列常量进行比较,即可确定文件类型,如下所示:
st_mode 字段的低 12 位定义了文件权限,会在 15.4 节介绍。目前,只要知道其中最低 9位分别用来表示文件属主、属组以及其他用户的读、写、执行权限。
文件大小、已分配块以及最优 I/O 块大小
对于常规文件, st_size 字段表示文件的字节数。对于符号链接,则表示链接所指路径名的长度,以字节为单位。对于共享内存对象(见第 54 章),该字段则表示对象的大小。st_blocks 字段表示分配给文件的总块数,块大小为 512 字节,其中包括了为指针块所分配的 空间(参见图 14-2)。
之所以选择 512 字节大小的块作为度量单位,有其历史原因——对于UNIX 所实现的任何文件系统而言,最小的块大小即为 512 字节。更为现代的 UNIX 文件系统则使用更大尺寸的逻辑块。例如,对于 ext2 文件系统,取决于其逻辑块大小为 1024、 2048 还是 4096 字节, st_blocks 的取值将总是 2、 4、 8 的倍数。 SUSv3 并未定义度量 st_blocks 时所使用的单位,故而 UNIX 实现可以不使用 512 字节作为其单位。大多数 UNIX 实现使用 512 字节作为 st_blocks 字段的单位。 st_blocks 字段记录了实际分配给文件的磁盘块数量。如果文件内含空洞(见 4.7 节),该值将小于从相应文件字节数字段( st_size)的值。 (执行显示磁盘使用情况的 du –k file 命令,便可获悉分配给文件的实际空间,单位为 KB。亦即,得自对文件 st_blocks 值,而非 st_size值的计算结果。 ) st_blksize 字段的命名多少有些令人费解。其所指并非底层文件系统的块大小,而是针对文件系统上文件进行 I/O 操作时的最优块大小。若 I/O 所采用的块大小小于该值,被视为低效。一般而言, st_blksize 的返回值为4096。
文件时间戳
st_atime、 st_mtime 和 st_ctime 字段,分别记录了对文件的上次访问时间、上次修改时间,以及文件状态发生改变的上次时间。这 3 个字段的类型均属 time_t,是标准的 UNIX 时间格式,记录了自 Epoch 以来的秒数。
15.2 文件时间戳
stat 结构的 st_atime、 st_mtime 和 st_ctime 字段所含为文件时间戳,分别记录了对文件的上次访问时间、上次修改时间,以及文件状态(即文件 i 节点内信息)上次发生变更的时间。 对时间戳的记录形式为自 1970 年 1 月 1 日(参见 10.1 节)以来所历经的秒数。 大多数原生 Linux 和 UNIX 文件系统都支持上述所有的时间戳字段, 但某些非 UNIX 文件系统则未必如此。 表 15-2 总结了本书所述各种系统调用及库函数所改变的相应时间戳字段(有时则是指父目录的类似字段)。本表标题中的 a、 m 和 c 分别表示 st_atime、 st_mtime 和 st_ctime 字段。在大多数情况下,系统调用会将相关时间戳置为当前时间。但 utime()及类似调用则不在此列,可利用这些系统调用显式将对文件的上次访问时间和上次修 改时间设定为任意值。
纳秒时间戳
对于 stat 结构所含的 3 个时间戳字段, Linux 从 2.6 版本将其精度提升至纳秒级。纳秒级分辨率将提高某些程序的精度,因为此类程序需要根据文件时间戳的先后顺序来作决定(比如, make(1))。 SUSv3 并未强制要求 stat 结构对纳秒级时间戳的支持,但 SUSv4 对此则有明文规定。 并非所有文件系统都支持纳秒级精度的时间戳。 JFS、 XFS、 ext4,以及 Btrfs 文件系统都支持,但 ext2、 ext3 以及 Reiserfs 文件系统则不然。 glibc API(自版本 2.3 起)将每个时间戳字段都定义为 timespec 结构(本节稍后在介绍utimensat()时将会讲解该结构),此结构是以秒和纳秒为单位来表示时间的一种组件。使用恰当的宏定义,该组件的秒级部分可见诸于传统字段( st_atime、 st_mtime,以及 st_ctime)中。而对其纳秒级部分的访问则会采用如下手法:通过诸如 st_atim.tv_nsec 之类的字段名来获取文件上次访问时间的纳秒级部分。
15.2.1 使用 utime()和 utimes()来改变文件时间戳
使用 utime()或与之相关的系统调用集之一,可显式改变存储于文件 i 节点中的文件上次访问时间戳和上次修改时间戳。解压文件时, tar(1)和 unzip(1)之类的程序会使用这些系统调用去重置文件的时间戳
include<utime.h> int utime(const char *pathname,const struct utimbuf *buf); struct utimbuf{ time_t atime;/*访问时间*/ time_t modtime;/*修改时间*/ }
该结构中的字段记录了自 Epoch(见 10.1 节)以来的秒数。 utime()的运作方式则视以下两种不同情况而定。 如果 buf 为 NULL,那么会将文件的上次访问和修改时间同时置为当前时间。这时,进程要么具有特权级别( CAP_FOWNER 或 CAP_DAC_OVERRIDE),要么其有效用户 ID 与该文件的用户 ID(属主)相匹配,且对文件有写权限(逻辑上,对文件拥有写权限的进程在调用其他系统调用时, 可能会于无意间改变这些时间戳)。(准确地说,如 9.5 节所述,在 Linux 系统中,用来与文件用户 ID 做比对的是进程的文件系统用户 ID,而非其有效用户 ID。 ) 若将 buf 指定为指向 utimbuf 结构的指针,则会使用该结构的相应字段去更新文件的上次访问和修改时间。此时,要么调用程序具有特权级别( CAP_FOWNER),要么进程的有效用户 ID 必需匹配文件的用户 ID(仅对文件拥有写权限是不够的)。 为更改文件时间戳中的一项,可以先利用 stat()来获取两个时间,并使用其中之一来初始化 utimbuf 结构,然后再将另一时间置为期望值。下列代码演示了这一操作,将文件的上次修改时间改为与上次访问时间相同
struct stat sb; struct utimbuf utb; if(stat("filename",&sb)==-1) errExit("stat") utb.actime=sb.st_atime utb.modtime=sb.st_atime if(utime("filename",&utb)==-1) errExit("utime")
只要调用 utime()成功,总会将文件的上次状态更改时间置为当前时间。
Linux 还提供了源于 BSD 的 utimes()系统调用,其功用类似于 utime()。
#include<sys/time.h> int utimes(const char *pathname,const struct timeval tv[2]); int futimes(int fd,const struct timeval tv[2]); int lutimes(const char *pathname,const struct timeval tv[2]);
utime()与 utimes()之间最显著的差别在于后者可以以微秒级精度来指定时间值( timeval结构请见 10.1 节)。 Linux 2.6 为文件时间戳提供了纳秒级的精度支持, 在这里也部分得以体现。新的文件访问时间在 tv[0]中指定,新的文件修改时间在 tv[1]中指定。
futimes()和 lutimes()库函数的功能与 utimes()大同小异。前两者与后者之间的差异在于,用来指定要更改时间戳文件的参数不同
调用 lutimes()时,使用路径名来指定文件,有别于调用 utimes()的是:对于 lutimes(),若路径名指向一符号链接,则调用不会对该链接进行解引用,而是更改链接自身的时间戳。 glibc 自 2.3 版本开始支持 futimes()函数,自 2.6 版本开始支持 lutimes()函数。
15.2.2 使用 utimensat()和 futimens()改变文件时间戳
utimensat()系统调用(内核自 2.6.22 版本开始支持)和 futimens()库函数( glibc 自版本 2.6开始支持)为设置对文件的上次访问和修改时间戳提供了扩展功能。以下对这两个编程接口的优点列举一二。 1.可按纳秒级精度设置时间戳。相对于提供微秒级精度的 utimes(),这是重大改进。 2.可独立设置某一时间戳(一次只设置其一)。如前所述,要使用旧编程接口去改变时间戳之一,需要首先调用 stat()获取另一时间戳的值。然后再将获取值与打算变更的时间戳一同指定。(若另一进程在这两步之间执行了更新时间戳的操作, 将会导致竞争状态。 ) 3.可独立将任一时间戳置为当前时间。要使用旧编程接口将一个时间戳改为当前时间, 需要调用 stat()去获取那些保持不变的时间戳的设置情况,并调用 gettimeofday()以获得当前时间
utimensat()系统调用会把由 pathname 指定文件的时间戳更新为由数组 times 指定的值
#include<sys/stat.h> int utimensat(int dirfd,const char *pathname,const struct timespec times[2],int flags); int futimens(int fd,const struct timespec times[2]);
若将 times 指定为 NULL,则会将以上两个文件时间戳都更新为当前时间。若 times 值为非 NULL,则会针对指定文件在 times[0]中放置新的上次访问时间,在 times[1]中放置新的上次修改时间。数组 times 所含的每一元素都是如下格式的一个结构:
struct timespec{ time_t tv_sec;/*秒*/ long tv_nsec;/*纳秒*/ }
结构所含的字段分别指定自 Epoch (10.1 节)以来的秒数和纳秒数。 若 有 意 将 时 间 戳 之 一 置 为 当 前 时 间 , 则 可 将 相 应 的 tv_nsec 字 段 指 定 为 特 殊 值UTIME_NOW。若希望某一时间戳保持不变,则需把相应的 tv_nsec 字段指定为特殊值UTIME_OMIT。无论是上述哪一种情况,都将忽略相应 tv_sec 字段中的值。 可以将 dirfd 参数指定为 AT_FDCWD,此时对 pathname 参数的解读与 utimes()相类似。或者,也可以将其指定为指代目录的文件描述符, 18.11 节将描述这一用法的目的所在。 flags 参数可以为 0,或者 AT_SYMLINK_NOFOLLOW,意即当 pathname 为符号链接时,不会对其解引用(也就是说,改变的是符号链接自身的时间戳)。相形之下, utimes()总是对符号链接进行解引用。 以下代码片段在将对文件的上次访问时间置为当前时间的同时,上次修改时间则保持不变
struct timespec times[2]; times[0].tv_sec=0; times[0].tv_nsec=UTIME_NOW; times[1].tv_sec=0; times[1].tv_nsec=UTIME_OMIT; if(utimensat(AT_FDCWD,"myfile",times,0)==-1) errExit("utimensat")
利用 utimensat()(和 futimens())改变时间戳时所遵循的权限规则与旧有 API 函数相类似,utimensat(2)手册页对此有详细讨论。 使用 futimens()库函数可更新打开文件描述符 fd 所指代文件的各个文件时间戳。 其中, times 参数的使用方法与 utimensat()相同