1 File Times
每个文件会维护三个时间字段,每个字段代表的时间都不同。如下表所示:
字段说明:
- st_mtim(the modification time)记录了文件内容最后一次被修改的时间。
- st_ctim(the changed-status time)记录了文件的i-node最后一次被修改的时间,如修改文件权限位,修改文件所有者ID,修改关联到该文件的link数目。
i-node中的信息和文件的实际内容是分离的,所以当更新i-node时,需要更新的时st_ctim(the changed-status time),而不是st_mtim(the modification time)。
命令ls各个参数排序时使用的时间标准:
- 参数-l和-t:使用st_mtim(the modification time of the file)
- 参数-u:使用st_atim(the last-access time of file)
- 参数-c:使用st_ctim(the last-change time of i-node status)
下表总结了会影响这三种时间的部分函数,在看到表之前,我们需要了解:
- 目录就是一个文件,该文件包含了目录索引:目录包含的文件名列表和关联的i-node数目。
- 新建,删除或者修改目录中包含的索引,会更新该目录的三种时间。
- 例如:新建一个文件,会更新包含该文件的目录中相应的时间,和该文件的i-node。而读写文件,则只会影响该文件的时间,而不会影响包含该文件的目录。
2 futimens,utimensat和utimes函数
函数作用:修改文件的access time和modification time。
函数声明:
#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd ,const char *path, const struct timespec times[2], int flag);
参数说明:
strcut timespec结构体中至少包含两个时间字段:time_t tv_sec(秒)和long tv_nsec(毫微秒)。
数组times包含两个时间:第一个元素是access time,第二个元素是modification time。
函数行为受参数times的取值影响,需要参考时可以自行查询。
同时,执行上面的一对函数也会对权限有要求,要求如下:
- 如果times参数为非空指针,或者tv_nsec字段为UTIME_NOW,则调用该函数要求:进程的the effective user ID必须等于文件所有者的ID,或进程有对该文件的写权限,或者进程是一个管理员(super user)进程。
- 如果times参数为非空指针,并且tv_nsec字段的取值不是UTIME_NOW和UTIME_OMIT,则调用该函数要求:进程的effective user ID必须等于该文件的所有者ID,或者进程是一个管理员进程。和上面一条不同的一点是,此种情况下仅仅对文件有写权限是不够的。
- 如果times参数为非空指针,并且tv_nsec字段的取值为UTIME_OMIT,则没有权限检查。
函数细节:
- 函数futimens要求文件必须打开,获取fd之后才可以修改文件相应的时间。
- 函数futimensat则可以通过制定一个文件的绝对路径来修改文件的时间属性,而此时fd参数被忽略。
- 同时,当目标文件为一个符号链接时,默认的函数行为是追踪该链接至真实文件,参数flag可以修改该默认行为,flag取值为AT_SYMLINK_NOFOLLOW时,修改的文件为该符号链接本身。
函数utimes通过制定一个文件路径pathname,来修改文件的相关时间。
函数声明:
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
strcut timeval {
time_t tv_sec;
long tv_usec;
};
我们不能指定修改时间字段st_ctim(changed-status time),但是在调用utimes函数时,该字段被自动更新。
Example:
例子情景:
- 通过open的O_TRUNC选项来使得一个文件为空,长度为0;
- 为了不修改文件的access time和modification time,先使用stat函数获取修改之前的时间;
- 截取文件长度至0,然后使用futimes函数重置文件时间字段为修改前的时间。
Code:
#include "apue.h"
#include <fcntl.h>
int
main(int argc, char *argv[])
{
int i, fd;
struct stat statbuf;
struct timespec times[2];
for (i = 1; i < argc; i++) {
if (stat(argv[i], &statbuf) < 0) { /* fetch current times */
err_ret("%s: stat error", argv[i]);
continue;
}
if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { /* truncate */
err_ret("%s: open error", argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) /* reset times */
err_ret("%s: futimens error", argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) /* reset times */
err_ret("%s: futimens error", argv[i]);
close(fd);
}
exit(0);
}
运行结果:(由于我用的mac os不支持的原因,并没有编译成功该例,所以直接用书上的结果)
从结果中可以看到,last-modification time和last-access time没有改变,而changed-status time发生了改变。
3 mkdir、mkdirat和rmdir函数
mkdir和mkdirat函数创建一个新的空的目录,rmdir函数用来删除目录。
函数声明:
#include <sys/stat.h>
int mkdir(const char* pathname, mode_t mode);
int mkdirat(int fd, const char* pathname, mode_t mode);
参数mode取值为基于在前面一篇提到过的文件创建掩码(file creation mask of process)。
rmdir函数用来删除一个空的目录,空目录中只含有两个记录(dot和dot-dot)。
函数声明:
#include <sys/stat.h>
int rmdir(const char* pathname);
上面的三个函数,调用成功返回0,失败返回-1.
4 读取目录文件(reading directories)
对于目录文件,只要有相应的权限,任何人都可以读取目录文件的内容。但是为了保证系统正常工作,只有内核可以对目录文件执行写操作。
在前面的章节中,我们了解到,目录文件的写权限位和执行权限位决定了我们是否可以在该目录下创建和删除文件,但是它们并不能允许我们写目录文件本身。
读文件操作依赖于系统实现。相关函数声明如下:
#include <dirent.h>
DIR *opendir(const char* pathname);
DIR *fdopendir(int fd); // return : pointer if OK, NULL on error
struct dirent *readdir(DIR *dp); // return : pointer if OK, at end of directory or error
void rewinddir(DIR *dp);
int closed(DIR *dp); // return : 0 if OK, -1 on error
long telluride(DIR *dp); // return : current location in directory associated with dp
void seekdir(DIR *dp, long loc);
细节说明:
- 结构体struct dirent定义在头文件<dirent.h>中,依赖于具体的实现,但是至少包含两个成员:ino_t d_ino(inode number)和char d_name[](null-terminated filename)。
- 结构体DIR会在后面的章节详细讨论。
- opendir和fdopendir返回一个指向DIR结构体的指针,该指针会用在另外五个函数中。
- readdir返回目录的第一条记录,当readdir使用fdopendir返回的指针时,读取的记录取决于传给fdopendir的参数fd的偏移量。
5 chdir、fchdir和getcwd函数
每个进程都有一个工作目录(current working directory),工作目录是进程的一个属性。
函数声明:
#include <unistd.h>
int chdir(const char* pathname);
int fchdir(int fd);
比较简单,不做赘述。
如果我们希望获取当前工作目录的完整信息(绝对路径),无法直接从内核获取,因为内核只是维护了一个指向该目录的指针。
如果我们要获取绝对路径,需要通过dot-dot进入上级目录,读取该级目录的信息,获取目录名,然后再依次访问上级目录一直到根目录,最后拼出绝对路径。
函数getcwd就是实现了这个功能。
函数声明:
#include <unistd.h>
char* getcwd(char *buf, size_t size);
需要注意的一点是,这里的buf需要足够大,size为它的大小。buf需要容纳绝对路径加上一个字节的null终止符。
Example:
函数功能:修改当前工作目录(chdir)并获取该工作目录绝对路径(getcwd)
Code:
#include "apue.h"
int
main(void)
{
char *ptr;
size_t size;
if (chdir("/usr/") < 0)
err_sys("chdir failed");
ptr = path_alloc(&size); /* our own function */
if (getcwd(ptr, size) == NULL)
err_sys("getcwd failed");
printf("cwd = %s\n", ptr);
exit(0);
}
切换工作目录到/usr/并打印工作目录。
6 文件权限位总结
如下表所示
7 小结
这一章包括了三篇内容,主要围绕stat函数,了解了:
- stat结构体中的各个成员;
- UNIX文件和目录的各个属性;
- 操作文件和目录的常用函数;
- UNIX文件系统的组织结构。
下一章我们将会学习标准IO库。
参考资料:
《Advanced Programming in the UNIX Envinronment 3rd》