1.stat、fstat和lstat函数
获取一些文件相关的信息。
函数原型:
#include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict buf); //提供文件名字,获取文件对应属性。 int fstat(int filedes, struct stat *buf); //通过文件描述符获取文件对应的属性。 int lstat(const char *restrict pathname, struct stat *restrict buf); //连接文件描述命,获取文件属性。
参数说明:pathname:文件路径名,filedes:文件描述词,buf:是stat结构体的指针
区别:
1).fstat系统调用接受的是 一个“文件描述符”,而另外两个则直接接受“文件全路径”。文件描述符是需要我们用open系统调用后才能得到的,而文件全路经直接写就可以了。
2).stat 和lstat的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息。(lstat比stat多了一个l,因此它是有本事处理符号链接文件的,因此当遇到符号链接文件时,lstat当然不会放过。而stat系统调用不行,它只能直接去处理链接所指文件)
2.文件类型
普通文件、目录文件、符号链接(指向另一个文件)、字符设备、块设备、socket、FIFO
3.设置用户ID和设置组ID
每个进程都有相关的6个或更多的ID,如下表所示:
每个进程相关的用户ID和组ID | |
真实用户ID(real user ID) 真实组ID(real group ID) |
我们真实的身份 |
有效用户ID(effective user ID) 有效组ID(effective group ID) 补充组ID(supplementary group ID) |
用于文件权限核查 |
保存的设置用户ID(saved set-user-ID) 保存的设置组ID(saved set-group-ID) |
由exec函数保存 |
1).真实用户ID和真实组ID标识了我们真实的身份。
2).有效用户ID、有效组ID和补充组ID决定了我们文件的访问权限,
3).保存的设置用户ID和保存的设置组ID在程序执行时,包含了有效用户ID和有效组ID的复制。。
4.新文件和目录的所有权
1).新文件的用户id是进程的有效用户id。
2).而新文件的组id有两种说法 1.进程的有效组id;2. 文件所在的目录的组id。
5.access函数
检测真实用户id的访问文件的权限,而不管它是否是set-user-id程序
#include <unistd.h> int access(const char *pathname, int mode); //成功返回0,错误返回-1。
mode为下表常量中的位或值:
mode | 描述 |
R_OK | 测试读权限 |
W_OK | 测试写权限 |
X_OK | 测试执行权限 |
F_OK | 测试文件存在 |
6.umask函数
umask函数为进程设置文件模式创建屏蔽字,并返回以前的值
#include <sys/stat.h>
mode_t umask(mode_t cmask);
cmask取值详见APUE P46
7.chmod和fchmod函数
这两个函数允许我们改变一个已存在的文件的访问权限。
#include <sys/stat.h> int chmod(const char *pathname, mode_t mode); int fchmode(int filedes, mode_t mode); //两者成功都返回0,失败返回-1。
chmod操作一个指定的文件,而fchmod操作一个已打开的文件,为了改变一个文件的权限位,进程的有效用户ID必须与文件的属主ID相同,或者进程必须有超级用户权限。
mode有下表的常量的位或值指定:
<sys/stat.h>是chmod函数的模式常量 | |
模式 | 描述 |
S_ISUID | 执行时的set-user-ID |
S_ISGID | 执行时的set-group-ID |
S_ISVTX | saved-text(粘滞位) |
S_IRWXU | 用户读、写、执行 |
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRWXG | 组读、写、执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IRWXO | 其他人读、写、执行 |
S_IROTH | 其他人读 |
S_IWOTH | 其他人写 |
S_IXOTH | 其他人执行 |
注意上表中有9个访问权限位与4.5节的表一样。我们加入了两个设置ID常量(S_ISUID和S_ISGID)、saved-text常量(S_ISVTX)、以及三个联合常量(S_IRWXU、S_IRWXG和S_IRWXO)。
8.黏住位
作用是在可执行程序在第一次执行时,程序的代码段text将复制到swap空间中。在下次执行时,此程序load到内存中就很快。如今的unix系统,虚拟内存以及快速文件系统的出现,这项技术并不需要了。
9.chown、fchown和lchown函数
各chown函数允许我们改变一个文件的用户ID和组ID。
#include <unistd.h> int chown(const char *pathname, uid_t owner, gid_t group); int fchown(int filedes, uid_t owner, gid_t group); int lchown(const char *pathname, uid_t owner, gid_t group); //三者成功都返回0,失败返回-1。
这三个函数基本类似,除非引用的文件是一个符号链接。在这种情况下,lchown改变符号链接本身的属主,而不是符号链接指向的文件。
10.文件长度
1).stat结构中的st_size包含文件的字节大小。但是只对普通、目录、符号链接文件有意义。
2).对于普通文件,大小为0是允许的。第一次读就得到end-of-file。
3).对于目录文件,大小通常是16或512的倍数。
4).对于符号链接文件,大小是被链接的文件路径名长度。此外,它不包含C语言中字符串的终止空字符。
5).stat结构中的st_blksize为文件I/O时所使用的块大小。
6).stat结构中的st_blocks为文件所占的实际磁盘块数。
7).文件中的洞,当文件中有洞时,通过ls –l core显示时,会包含洞所占的字节数。而用du –s,core则显示文件实际所占的磁盘块数。当用read读文件中洞的内容时,得到的是0(不是‘0’而是‘/0’,字符串的结束符),打印时将什么都不会显示。wc –c core可以统计文件中的字节数。 经验证,在Linux中,虽然是空洞文件,但是空洞仍然占磁盘块空间。
11.文件截短
有时我们想要通过在文件末尾删除数据来裁切一个文件。我们可以在open时使用用O_TRUNC标志来清空一个文件,这是裁切的一个特殊例子。
#include <unistd.h> int truncate(const char *pathname, off_t length); int ftruncate(int filedes, off_t length); //两者成功都返回0,失败返回-1。
这两个函数把已存在的文件裁切成length字节。如果文件之前的尺寸比length大,超过length后的部分便不再能被访问了。如果之前尺寸比
length小,效果依系统而定,但遵守XSI的系统将会增加文件尺寸。如果实现没有扩展一个文件,文件旧末尾与新末尾之间会被读为0(也就是说,文件里很可能创建了一个空洞。)
12.文件系统
有以下要点
1).磁盘可以有多个分区,每个分区可以是不同的文件系统。文件系统主要包含部分:boot block,super block,cylinder group0~n。
2).每个cylinder group中包含:super block copy,i-node map,block bitmap, i-nodes(一个文件/目录一个), data/directory blocks(存放文件/目录的数据)。
3).对于普通文件,i-node指向了所属文件的数据块。而目录文件,i-node则指向了所属目录的directory blocks。
4).每个directory block中的主要内容是目录项所包含的文件的i-node号以及文件名。当目录项包含这样的信息时,被包含的文件的i-node中stat的成员st_nlink即链接数就增1。这种链接就是硬链接,通过ln命令实现,即创建一个新目录项指向已有的文件。而只有当链接数为0的时候,文件才被删除
5).修改文件名如mv命令,是添加一个新目录项指向已有文件,再unlink掉原目录项,而不需要移动文件的实际内容。
6).叶目录(目录中不包含任何其他目录)的链接数是2,分别是目录中dot,以及父目录对它的指向。在叶目录增加一个子目录,则叶目录的链接数就增1,因为新增子目录的dotdot指向它。
13.link、unlink和rename函数
1).任何文件可以有多个目录项指向它的i节点,创建一个已存在的文件的链接的方法是使用link函数。
#include <unistd.h> int link(const char *existingpath, const char *newpath); //成功返回0,失败返回-1
此函数创建一个新的目录项newpath,指向已存在文件exsitingpath。如果newpath已存在,则返回一人错误。只有newpath的最后一个部分被创建。路径的其余部分必须已经存在。
2).为了删除一个已存在的目录项,我们调用unlink函数。
#include <unistd.h> int unlink(const char *pathname); //成功返回0,错误返回-1
函数删除目录项并减少pathname引用的文件的链接数。如果文件有其它链接,文件的数据仍可以通过别的链接访问。如果错误发生,文件不会改变
3).我们还可以用remove函数解链接一个文件或目录。对于一个文件,remove与unlink相同。对于一个目录,remove和rmidir相同。
#include <stdio.h> int remove(const char *pathname); //成功返回0,失败返回-1。
4).一个文件或目录可以用rename函数来重命名.
#include <stdio.h> int rename(const char *oldname, const char *newname); //成功返回0,失败返回-1。
14.符号链接
符号链接是一个文件的间接指针,不像上面的硬链接--直接指向文件的i-node。符号链接用来解决硬链接的限制。
1).硬链接通常需要链接和文件处在同一个文件系统上
2).只有超级用户才能创建目录的硬链接。
符号链接和它指向的文件没有文件系统的限制,任何人都可以创建一个目录的符号链接。符号链接被典型用来把一个文件或整个目录结构移到系统的另一个位置。
当使用通过名字表示文件的函数时,我们问题需要知道函数是否解析一个符号链接。如果函数解析一个符号链接,函数的路径名参数就表示符号链接指向的文件。否则,路径名参数表示符号链接本身,而不是链接指向的文件。下表总结了本章函数是否解析一个符号链接:
各种函数对待符号链接的方式 | ||
函数 | 不解析符号链接 | 解析符号链接 |
access | * | |
chdir | * | |
chmod | * | |
chown | * | * |
creat | * | |
exec | * | |
lchown | * | |
link | * |
函数mkdir、mkfifo、mknod和rmdir不在这张表里,因为当路径名是一个符号链接时它们会返回一个错误。还有,接受文件描述符参数的函数,比如fstat和fchmod,也没有列出来,因为符号链接的处理已经被返回文件描述符的函数(通常是open)完成了。chown是否解析一个符号链接取决于实现
其他详见APUE
15.symlink和readlink函数
1).symlink函数可以用来创建符号链接。
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); //成功返回0,错误返回-1。
该函数创建了一个指向actualpath的新目录项sympath,在创建此符号链接时,并不需要actualpath存在。并且,actualpath和sympath不需要在同一个文件系统上。
2).因为open函数解析一个符号链接,所以需要一个打开该链接本身,并读取链接里的名字,readlink函数提供这个功能。
#include <unistd.h> ssize_t readlink(const char* restrict pathname, char *restrict buf, size_t bufsize); //成功返回字节数,失败返回-1。
此函数合并了open、read和close的操作。如果函数成功执行,则它返回读入buf的字节数。在buf中返回的符号链接的容不以null终止。
16.文件时间
每个文件维护了三个时间域。下表总结了它们的目的:
与每个文件相关的三个时间域 | |||
域 | 描述 | 例子 | ls 选项 |
st_atime | 文件数据的最后访问时间 | read | -u |
st_mtime | 文件数据的最后修改时间 | write | 默认 |
st_ctime | i-node状态的最后改变时间 | chmod, chown | -c |
注意修改时间(st_mtime)和改变状态时间(st_ctime)的区别。修改时间是文件内容最后被修改的时间。状态改变时间是文件的i-node最后被修改的时间。在本章,我们已经描述过许多影响i-node而不改变文件真实内容的操作:改变文件的访问权限、改变用户ID、改变链接数、等等。因为 i-node的信息与文件真实内容分开存放,所以我们在修改时间之外,需要状态改变时间。
注意系统没有为一个i-node维护最后访问时间。这是为什么函数access以及stat没有改变这三个时间的任一个的原因。
17.utime函数
一个文件的访问时间和修改时间可以用utime函数改变。
#include <utime.h> int utime(const char *pathname, const struct utimebuf *times); //成功返回0,失败返回-1。
utimebuf结构体:
struct utimbuf { time_t actime; /* 访问时间 */ time_t modtime; /* 修改时间 */ };
18.mkdir和rmdir函数
用mkdir函数来创建目录,用rmdir函数删除目录。
#include <sys/stat.h> int mkdir(const char *pathname, mode_t mode); //成功返回0,失败返回-1。
此函数创建一个新的空的目录,其中.和..的目录项被自动创建,指定的文件访问权限mode由进程的文件模式创建屏蔽字所修改。
#include <unistd.h> int rmdir(const char *pathname); //成功返回0,失败返回-1。
如果调用这个函数后目录的链接数变为0,并且没有其它进程打开这个目录,则目录开辟的空间会被释放掉。如果当链接数达到0时,有一个或多个进程打开了这个目录,则函数返回前最后的链接会被删除,而且.和..项也会被删除。另外,这个目录里不能再创建新的文件,但是最后一个进程关闭它之前并没有被释放此目录。(即使有些其它进程打开了这个目录,它在此目录下也不能执行其他操作,这样处理的原因,为了使rmdir函数执行成功,该目录必须是空的)。
19.chdir、fchdir和getcwd函数
1).进程通过可以用chdir和fchdir函数可以更改当前工作目录。
#include <unistd.h> int chdir(const char *pathname); int fchdir(int filedes); //成功返回0,失败返回-1。
fchdir函数提供我们完成这个任务的一个简单方法。不需要使用getcwd(见下面),我们可以在进入文件系统别的位置前用open当前目录并保存文件描述符。当我们想要返回我们开始的位置时,我们可以简单地把描述符会给fchdir。
2).我们需要的是一个从当前工作目录.开始,并使用..目录项找到其上一层目录时,然后读取它的目录项,直到该目录项中的i节点编号与工作目录i节点编号相同。这样就找到了其对应的文件名,按照这种方法,逐层上移,直到根目录,其这样得到当前工作目录的整个绝对路径。getcwd函数来为我们完成这个任务了。
#include <unistd.h> char *getcw(char *buf, size_t size); //成功返回buf,错误返回NULL。
注:设备特殊文件和文件访问权限位详见APUE