文件时间戳
stat 结构的 st_atime、st_mtime 和 st_ctime 字段所含为文件时间戳,分别记录了对文件的上次访问时间、上次修改时间,以及文件状态(即文件 i 节点内信息)上次发生变更的时间。对时间戳的记录形式为自 1970 年 1 月 1 日(参见 10.1 节)以来所历经的秒数。
下表总结了:各种函数对文件时间戳的影响
使用 utime()和 utimes()来改变文件时间戳
使用utime()或者与之相关的系统调用集之一,可以显示改变存储于文件i文件中的文件上次访问时间戳和上次修改时间戳。解压文件时,tar(1)和unzip(1)之类的程序会使用这些系统调去重置时间戳:
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
参数pathname就是要修改的文件路径。若该参数为符号链接,则会进一步解除引用
参数times可以是null,也可以是:
struct utimbuf {
time_t actime; /* access time */
time_t modtime; /* modification time */
};
该结构中的字段记录了自 Epoch(见 10.1 节)以来的秒数
- 如果 buf 为 NULL,那么会将文件的上次访问和修改时间同时置为当前时间。这时,进程要么具有特权级别(CAP_FOWNER 或 CAP_DAC_OVERRIDE),要么其有效用户 ID 与该文件的用户 ID(属主)相匹配,且对文件有写权限(逻辑上,对文件拥有写权限的进程在调用其他系统调用时,可能会于无意间改变这些时间戳)
- 若将 buf 指定为指向 utimbuf 结构的指针,则会使用该结构的相应字段去更新文件的上次访问和修改时间。此时,要么调用程序具有特权级别(CAP_FOWNER),要么进程的有效用户 ID 必需匹配文件的用户 ID(仅对文件拥有写权限是不够的)。
为更改文件时间戳中的一项,可以先利用 stat()来获取两个时间,并使用其中之一来初始化 utimbuf 结构,然后再将另一时间置为期望值。下列代码演示了这一操作,将文件的上次修改时间改为与上次访问时间相同:
struct stat sb;
struct utimbuf utb;
if(stat(pathname, &sb) == -1){
perror("stat");
exit(EXIT_FAILURE);
}
utb.actime = sb.st_atime;
utb.modtime = sb.st_atime;
if(utime(pathname, &utb) == -1){
perror("utime");
exit(EXIT_FAILURE);
}
Linux 还提供了源于 BSD 的 utimes()系统调用,其功用类似于 utime()。utime()与 utimes()之间最显著的差别在于后者可以以微秒级精度来指定时间值。新的文件访问时间在 tv[0]中指定,新的文件修改时间在 tv[1]中指定:
int
main(int argc, char *argv[])
{
if (argc != 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s file\n", argv[0]);
struct stat sb;
if (stat(argv[1], &sb) == -1){
perror("stat");
exit(EXIT_FAILURE);
}
struct timeval tv[2];
tv[0].tv_sec = sb.st_atime; /* Leave atime seconds unchanged */
tv[0].tv_usec = 223344; /* Change microseconds for atime */
tv[1].tv_sec = sb.st_atime; /* mtime seconds == atime seconds */
tv[1].tv_usec = 667788; /* mtime microseconds */
if (utimes(argv[1], tv) == -1){
perror("utimes");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
futimes()和 lutimes()库函数的功能与 utimes()大同小异。前两者与后者之间的差异在于,用来指定要更改时间戳文件的参数不同
#include <sys/time.h>
int futimes(int fd, const struct timeval tv[2]);
int lutimes(const char *filename, const struct timeval tv[2]);
- 调用 futimes()时,使用打开文件描述符 fd 来指定文件。
- 调用 lutimes()时,使用路径名来指定文件,有别于调用 utimes()的是:对于 lutimes(),若
路径名指向一符号链接,则调用不会对该链接进行解引用,而是更改链接自身的时间戳。
glibc 自 2.3 版本开始支持 futimes()函数,自 2.6 版本开始支持 lutimes()函数
使用 utimensat()和 futimens()改变文件时间戳
utimensat()系统调用(内核自 2.6.22 版本开始支持)和 futimens()库函数(glibc 自版本 2.6开始支持)为设置对文件的上次访问和修改时间戳提供了扩展功能。以下对这两个编程接口的优点列举一二
- 可按纳秒级精度设置时间戳。相对于提供微秒级精度的 utimes(),这是重大改进
- 可独立设置某一时间戳。如前所述,要使用旧编程接口去改变时间戳之一,需要首先调用 stat()获取另一时间戳的值。然后再将获取值与打算变更的时间戳一同指定。(若另一进程在这两步之间执行了更新时间戳的操作,将会导致竞争状态。
- 可独立将任一时间戳置为当前时间。要使用旧编程接口将一个时间戳改为当前时间,需要调用 stat()去获取那些保持不变的时间戳的设置情况,并调用 gettimeofday()以获得当前时间
在 SUSv3 中并未定义以上两个接口,但 SUSv4 将其纳入规范。
#include <fcntl.h> /* Definition of AT_* constants */
#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]);
utimensat()系统调用会把由 pathname 指定文件的时间戳更新为由数组 times 指定的值
- 若将 times 指定为 NULL,则会将以上两个文件时间戳都更新为当前时间。
- 若 times 值为非 NULL,则会针对指定文件在 times[0]中放置新的上次访问时间,在 times[1]中放置新的上
次修改时间。
数组 times 所含的每一元素都是如下格式的一个结构:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
结构所含的字段分别指定自 Epoch (10.1 节)以来的秒数和纳秒数。
- 若有意将时间戳之一置为当前时间,则可将相应的 tv_nsec 字段指定为特殊值UTIME_NOW。
- 若希望某一时间戳保持不变,则需把相应的 tv_nsec 字段指定为特殊值UTIME_OMIT。
无论是上述哪一种情况,都将忽略相应 tv_sec 字段中的值。
可以将 dirfd 参数指定为 AT_FDCWD,此时对 pathname 参数的解读与 utimes()相类似。或者,也可以将其指定为指代目录的文件描述符。
flags 参数可以为 0,或者 AT_SYMLINK_NOFOLLOW,意即当 pathname 为符号链接时,不会对其解引用(也就是说,改变的是符号链接自身的时间戳)。相形之下,utimes()总是对符号链接进行解引用
以下代码片段在将对文件的上次访问时间置为当前时间的同时,上次修改时间则保持不变
使用 futimens()库函数可更新打开文件描述符 fd 所指代文件的各个文件时间戳。其中,times 参数的使用方法与 utimensat()相同。