UNIX环境高级编程读书笔记(4)

来源:互联网 发布:spss数据 excel 编辑:程序博客网 时间:2024/05/19 17:05

UNIX环境高级编程第四章 读书笔记

一、函数stat、fstat、fstatat、lstat

#include < sys/stat.h >
int stat(const char *restrict pathname,struct stat *restrict buf)
int fstat(int fd,struct stat *buf)
int lstat(const char *restrict pathname,struct stat *restrict buf)
int fstatat(int fd,const char *restrict pathname,struct stat *restrict buf,int flag)
若成功,返回0。若失败,返回-1

一旦给出pathname,stat函数将返回与此命名文件有关的信息结构。fstat函数获得已在描述符fd上打开文件的有关信息。
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息。
fstatat函数为一个相对于当前打开目录(由fd参数指向)的路径名返回文件统计信息。flag参数控制着是否跟随着一个符号链接。当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而返回符号链接本身的信息。否则,在默认情况下,返回的是符号链接所指向的实际文件的信息。如果fd参数的值是AT_FDCWD,并且pathname参数是一个相对路径名。fstatat会计算相对于当前目录的pathname参数。如果pathname是一个绝对路径,fd参数就会被忽略。这两种情况下,根据flag的取值,fstatat的作用就和stat或fstat一样。
第2个参数buf是一个指针,它指向一个我们必须提供的结构。函数来填充由buf指向的结构。结构的实际定义可能随具体实现有所不同,但其基本形式是:
struct stat {
mode_t st_mode; /* file type & mode (permissions) */
ino_t st_ino; /* i-node number (serial number) */
dev_t st_dev; /* device number (file system) */
dev_t st_rdev; /* device number for special files */
nlink_t st_nlink; /* number of links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
off_t st_size; /* size in bytes, for regular files */
struct timespec st_atim; /* time of last access */
struct timespec st_mtim; /* time of last modification */
struct timespec st_ctim; /* time of last file status change */
blksize_t st_blksize; /* best I/O block size */
blkcnt_t st_blocks; /* number of disk blocks allocated */
};
timespec结构类型按照纳秒定义了时间,至少包括下面两个字段:
- time_t tv_sec
- long tv_nsec

使用ls -l 命令可以获得一个有关一个文件的所有信息。

二、文件类型

UNIX系统的文件系统包括以下几种:
(1)普通文件。这是最常见的文件类型,这种文件类型包含了某种形式的数据。至于这种数据是文本还是二进制数据,对于UNIX内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。
(2)目录文件。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。
(3)块特殊文件。这种类型的文件提供对设备带缓冲的访问,每次访问以固定长度为单位进行。
(4)字符特殊文件。这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
(5)FIFO。这种类型的文件用于进程间通信,有时也称为命名管道。
(6)嵌套字。这种类型的文件用于进程间的网络通信。嵌套字也可用于在一台宿主机上进程之间的非网络通信。
(7)符号链接。这种类型的文件指向另一个文件。

文件类型信息包含在stat结构的st_mode成员中。
用来判断文件类型的文件类型宏
这里写图片描述

POSIX.1允许实现将进程间的通讯(IPC)对象说明文件。
用来判断进行通讯类型的IPC类型宏
这里写图片描述

利用宏判断并打印文件类型的程序

#include "apue.h"intmain(int argc, char *argv[]){    int  i;    struct stat buf;    char  *ptr;    for (i = 1; i < argc; i++) {        printf("%s: ", argv[i]);        if (lstat(argv[i], &buf) < 0) {            err_ret("lstat error");            continue;        }        if (S_ISREG(buf.st_mode))            ptr = "regular";        else if (S_ISDIR(buf.st_mode))            ptr = "directory";        else if (S_ISCHR(buf.st_mode))            ptr = "character special";        else if (S_ISBLK(buf.st_mode))            ptr = "block special";        else if (S_ISFIFO(buf.st_mode))            ptr = "fifo";        else if (S_ISLNK(buf.st_mode))            ptr = "symbolic link";        else if (S_ISSOCK(buf.st_mode))            ptr = "socket";        else            ptr = "** unknown mode **";        printf("%s\n", ptr);        }    exit(0);}

这里写图片描述

三、设置用户ID和设置组ID

与一个进程相关联的ID有6个或更多。

实际用户ID 实际组ID 有效用户ID 有效组ID 附属组ID 保存的设置用户ID 保存的设置组ID

- 实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自口令文件中过的登录项。通常,在一个登录会话期间这些值并不会改变,但是超级用户进程可以更改这些值。
- 有效用户ID、有效组ID以及附属组ID决定了文件访问权限。
- 保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。

通常,有效用户ID等于实际用户ID有效组ID等于实际组ID
每个文件一个所有者和组所有者所有者由stat结构中的st_uid指定,组所有者则由st_gid指定。

执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,进程的有效组ID通常就是实际组ID。但是可以在文件模式字(st_mode)设置一个特殊标志,可以将进程的有效用户ID设置为文件所有者ID,可以将进程的有效组ID设置为文件所有组ID。在文件模式字中的这两位被称为设置用户ID位设置组ID位

设置用户ID位及设置组ID位都包含在文件中st_mode值中。这两位可分别用常量S_ISUID和S_ISGID测试。

四、文件访问权限

st_mode值也包含了对文件的访问权限位。所有文件类型都有访问权限。
每个文件有9个访问权限位,可将他们分成3类
这里写图片描述
术语用户指的是文件所有者。chomd(1)命令用于修改这9个权限位。该命令允许我们用u表示用户,用g表示组,用o表示其他。

3类访问权限有着不同的访问规则:
- 第一个规则:我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限。对于目录的执行权限位通常被称为搜索位
注意,执行权限和读权限不同,目录的读权限能使对象获得目录中所有文件的文件名列表,而执行权限能使对象通过该目录。
- 第二个规则:对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作。这与open函数的O_RDONLY和O_RDWR标志有关。
- 第三个规则:对于一个文件的写权限决定论我们是否能够打开现有文件进行写操作。这与open函数的O_WRONLY和O_RDWR标志有关。
- 第四个规则:为了在open函数中创建一个新文件指定O_TRUNC标志,必须对该文件具有写权限。
- 第五个规则:为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限。
- 第六个规则:为了删除一个现有文件,必须对包含该文件的目录具有写权限和指向权限。对该文件本身则不需要有读、写权限。
- 第七个规则:如果用7个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限,而且该文件必须是一个普通文件。

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,而这种测试可能涉及文件的所有者(st_uid和st_gid)、进程的有效ID(有效用户ID和有效组ID)以及进程的附属组ID两个所有者ID文件的性质,而两个有效ID附属组ID是进程的性质。
内核进行的测试具体如下:
(1)若进程的有效用户ID0(代表时超级用户),则无需进行文件访问权限的测试允许访问
(2)若进程的有效用户ID等于文件的所有者ID,那么如果所有适当的访问权限位设置,则允许访问;否则拒绝访问。适当的访问权限位指的是,进程要以什么形式打开文件,则需将该形式所代表的权限位设置为1.
(3)若进程的有效组ID进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限位设置,则允许访问;否则拒绝访问
(4)若其他用户适当权限位设置,则允许访问,否则拒绝访问
测试按以上4步顺序执行

五、新文件和目录的所有权

新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX.1允许实现选择下列之一作为新文件的组ID。
(1)新文件的组ID可以是进程的有效组ID
(2)新文件的组ID可以是它所在目录的组ID
使用POSIX.1所允许的第二个选项使得在某个目录下创建的文件和目录都具有该目录的组ID。于是文件和目录的组所有权从该点向下传递。

六、函数access和faccessat

access和faccessat函数是按实际用户ID和实际组ID进行访问权限测试的。

#include < unistd.h >
int access(const char *pathname,int mode)
int faccessat(int fd,const char *pathname,int mode,int flag)
若成功,返回0,若出错,返回-1

mode参数的取值如下:

mode 说明 F_OK 测试文件是否已经存在 R_OK 测试文件的读权限 W_OK 测试文件的写权限 X_OK 测试文件的执行权限

faccessat函数与access函数在下面两种情况下是相同的:
1、pathname参数为绝对路径
2、fd的参数取值为AT_FDCWD而pathname参数为相对路径
除了以上的情况外,faccessat计算相对于打开目录(由fd参数指向)的pathname

flag参数可以用于改变faccessat的行为,如果flag设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID有效组ID,而不是实际用户ID和实际组ID。

显示access函数的使用方法的程序

#include "apue.h"#include <fcntl.h>int main(int argc,char *argv[]){    if(argc!=2)        err_quit("usage: a.out <pathname>");    if(access(argv[1],R_OK)<0)        err_ret("access error for %s",argv[1]);    else        printf("read access OK\n");    if(open(argv[1],O_RDONLY)<0)        err_ret("open error for %s",argv[1]);    else        printf("open for reading OK\n");    exit(0);}

这里写图片描述

七、函数umask

屏蔽字:创建文件的时候,会参考屏蔽字,如果屏蔽字为1,则将相应的文件设置位设置为0,否则设置为1。
umask函数为进程设置文件模式创建屏蔽字,并返回之前的值。

#include < sys/stat.h >
mode_t umask(mode_t cmask)
返回之前的文件模式创建屏蔽字

其中,参数cmask是由9个访问权限位常量中的若干位按”或”构成的
在进程创建一个新文件新目录时,就一定会使用文件模式创建屏蔽字。在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭

通常在登录时,umask由shell的启动文件设置一次,然后,在不改变。但是当编写创建新文件的程序时,如果我们想确保指定的访问权限位已经激活,那么必须在进程运行时修改umask值。

更改进程的文件模式创建屏蔽字并不影响其父进程的屏蔽字。所有shell都有内置umask命令,我们可以用该命令设置或打印当前文件模式创建屏蔽字。

用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制,一位代表一种要屏蔽的权限,设置相应位后,它所对应的权限就会被拒绝。

umask文件访问权限位如下:
这里写图片描述

umask设置程序

#include "apue.h"#include <fcntl.h>#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)intmain(void){    umask(0);    if (creat("foo", RWRWRW) < 0)        err_sys("creat error for foo");    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);    if (creat("bar", RWRWRW) < 0)        err_sys("creat error for bar");    exit(0);}

这里写图片描述

利用shell设置umask
这里写图片描述

八、函数chmod、fchmod、fchmodat

chmod、fchmod、fchmodat这三个函数使我们可以更改现有文件的访问权限

#include < sys/stat.h >
int chmod(const char *pathname,mode_t mode)
int fchmod(int fd,mode_t mode)
int fchmodat(int fd,const char *pathname,mode_t mode,int flag)
若成功,返回0,若出错,返回-1

chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。
fchmodat函数chmod函数在下面两种情况下相同的:
1.pathname是绝对路径
2.fd参数取值为AT_FDCWD而pathname参数为相对路径
除了以上的情况以外,fchmodat计算相对于打开目录(由fd参数指向)pathname
flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat不会跟随符号链接
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限

mode变量的取值:
这里写图片描述
chmod函数自动清除两个权限位
1.如果试图设置普通文件的粘着位,又没有超级用户权限,则mode中的粘着位被关闭。只有超级用户才能设置普通文件的粘着位。
2.如果新文件的组ID不等于进程的有效组ID或进程附属组ID中的一个,而且进程没有超级用户权限,那么设置组ID位会自动被关闭。

利用chmod修改文件的模式的简单程序

#include "apue.h"intmain(void){    struct stat statbuf;    /* turn on set-group-ID and turn off group-execute */    if (stat("foo", &statbuf) < 0)        err_sys("stat error for foo");    if (chmod("foo",(statbuf.st_mode & ~S_IXGRP) | S_ISGID)<0)        err_sys("chmod error for foo");    /* set absolute mode to "rw-r--r--" */    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)        err_sys("chmod error for bar");    exit(0);}

这里写图片描述

九、粘着位

S_ISVTX位被称为粘着位。如果一个可执行程序文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本保存在交换区。这样保证了下次执行该程序时能较快地将其装载入内存

如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除重命名该目录下的文件
- 拥有此文件
- 拥有此目录
- 是超级用户

十、函数chown、fchown、fchownat##和lchown

下面几个函数可用于更改文件的用户ID和组ID,如果两个参数owner或group中的任意一个是-1,则对应的ID不变。

#include < unistd.h>
int chown(const char *pathname,uid_t owner, gid_t group)
int fchown(int fd,uid_t owner,gid_t group)
int fchownat(int fd,const char *pathname,uid_t owner,gid_t group,int flag)
int lchown(const char *pathname,uid_t owner,gid_t group)
若成功,返回0,若出错,返回-1

符号链接情况下,lchownfchownat(设置了AT_SYMLINK_NONFOLLOW更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者
fchown函数改变fd变量指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能改变符号链接的所有者

fchownat函数chown函数或者lchown函数下面两种情况下是相同的:
1.pathname参数是绝对路径
2.fd参数取值为AT_FDCWD,而pathname参数为相对路径
两种情况下,如果flag参数中设置了AT_SYMLINK_NOFOLLOW标志,则fchownatlchown行为相同。如果flag参数中清楚了AT_SYMLINK_NOFOLLOW标志 ,则fchownatchown行为相同。如果fd参数设置为打开目录的文件描述符,并且pathname参数一个相对路径名fchownat函数计算相对于打开目录的pathname

_POSIX_CHOWN_RESTRICTED对指定文件生效,则
(1)只有超级用户进程更改该文件的用户ID
(2)如果进程拥有此文件(其有效用户ID等于该文件的用户ID),参数owner等于-1文件的用户ID,并且参数group等于进程的有效组ID或进程的附属组ID之一,那么一个非超级用户进程可以更改该文件的组ID

如果这些函数由非超级用户进程调用,则在成功返回时,该文件设置用户ID位设置组ID位被清除。

十一、文件长度

stat结构成员st_size表示以字节为单位的文件的长度,此字段只对普通文件目录文件符号链接有意义。对于普通文件来说,代表的是文件大小,如果长度0,读文件就会得到文件结束指示;对于目录文件来说,长度是一个数的整数倍;对于符号链接来说,代表文件名中的实际字节数

大多数现代的UNIX系统提供字段st_blksize(指的是对文件I/O较合适的块长度)和st_blocks(指的是所分配的实际512字节块块数),其中,利用st_blksize用于读操作时,读一个文件所需的时间量最少。为了提高效率标准I/O库也试图一次读、写st_blksize个字节

空洞是由所设置的偏移量超过文件尾端,并写入某些数据后造成的

对于没有写过的字节位置read函数读到的字节是0

Tip:带-c选项的wc(1)命令计算文件中的字符数(字节)。

如果使用实用程序复制这个文件,那么所有这些空洞都会被填满。其中所有实际数据字节皆填写为0.

空洞文件查询和复制后的内存变化
这里写图片描述
注意:文件的长度等于文件大小/512。

十二、文件截断

截断文件可以使用truncate和ftruncate

#include < unistd.h >
int truncate(const char *pathname,off_t length)
int ftruncate(int fd,off_t length)
若成功,返回0,若失败,返回-1

以上两个函数用于将一个现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的数据不再能访问。如果以前的长度小于length文件长度增加,在以前的文件尾端新的文件尾端之间的数据将读作0创建了一个空洞)。

十三、文件系统

UFS(传统基于BSD的UNIX文件系统)把一个磁盘分成一个多个分区每个分区可以包含一个文件系统i节点固定长度记录项,它包含有关文件大部分信息

UFS的文件组成
这里写图片描述

柱面组的i节点和数据块关系图
这里写图片描述

由上图,可以获得以下几点:
1.在图中有两个目录项指向同一个i节点每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少到0时,才可删除该文件。在stat结构中,链接计数包含在st_nlink成员中,其基本系统数据类型nlink_t。这种链接叫做硬链接
2.另外一种链接类型称为符号链接符号链接文件实际内容包含了该符号链接所指向的文件的名字
3.i节点包含了文件有关的所有信息文件类型文件访问权限位文件长度指向文件数据块指针等。stat结构中的大多数信息取自i节点。只有两项重要数据存放在目录项中:文件名i节点编号i节点的编号数据类型位ino_t
4.因为目录中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点
5.当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容未移动,只构造一个指向现有i节点的新目录项,并删除老的目录项链接计数*不会改变

当创建了一个目录文件时,目录会自动创建两个目录文件 ... 。而且父目录中的每一个子目录都使该父目录的链接计数增加1

十四、函数link、linkat、unlink、unlinkat和remove

任何一个文件可以有多个目录项指向其i节点。创建一个指向现有文件的链接最好的方法就是使用link或linkat函数。

#include < unistd.h >
int link(const char *existingpath,const char *newpath)
int linkat(int efd,const char *existingpath,int nfd,const char *newpath,int flag)
若成功,返回0,若出错,返回-1

这两个函数创建一个新的目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在
对于linkat函数现有文件通过efd和existingpath两个参数指定的新的文件通过nfd和newpath参数指定的。默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算。如果两个文件描述符中的任一设置位AT_FDCWD,那么相对路径名通过当前目录进行计算。如果任一路径名绝对路径相应的文件描述符参数就会被忽略
现有文件符号链接时,由flag参数控制linkat函数创建指向现有符号链接的链接还是创建指向现有符号链接所指向的文件的链接。如果在flag参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。如果这个标志被清除了,则创建一个指向符号链接本身的链接
创建新目录项增加链接计数应当是一个原子操作
虽然POSIX.1允许实现跨文件系统的链接,但是大多数实现要求现有新建两个路径名同一个文件系统中。如果实现支持创建指向一个目录的硬链接,也仅限于超级用户才可以这样做。否则可能导致在文件系统中产生循环

为了删除一个现有的目录项,可以调用以下方法:

#include < unistd.h >
int unlink(const char *pathname)
int unlink(int fd,const char *pathname,int flag)
若成功,返回0,若出错,返回-1

这两个函数删除目录项,并将由pathname所引用的文件链接减1。如果对该文件还有其他链接,则仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改

只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开文件,其内容也不能删除关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个计数达到0,内核再去检查其链接计数,如果计数也是0,那么删除该文件的内容

如果pathname参数是相对路径名,那么unlinkat函数计算相对于由fd文件描述符参数代表的目录的路径名。如果fd参数设置为AT_FDCWD,那么通过相对于调用进程的当前工作目录来计算路径名。如果pathname参数为绝对路径名,那么fd参数被忽略

flag参数给出了一种方法,是调用进程可以改变unlinkat函数的默认行为。当AT_REMOVEDIR标志设置时,unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除unlinkatunlink执行同样的操作

unlink函数通常用来确保在程序崩溃时,它所创建的临时文件不会遗留下来

如果pathname是符号链接,那么unlink删除该符号链接,而不是删除由该符号链接引用的文件。给出符号链接名的情况下没有一个函数能删除由该链接所引用的文件

使用remove也可以解除对一个文件或目录的链接

#include < stdio.h >
int remove(const char *pathname)
若成功,返回0,若失败,返回-1

对于文件而言,remove的功能与unlink相同,对于目录而言,remove与rmdir相同。

十五、函数rename和renameat函数

文件或目录可以用rename函数或者renameat函数进行重命名。

#include < stdio.h >
int rename(const char *oldname,const char *newname)
int renameat(int oldfd,const char *oldname,int newfd,const char *newname)
若成功,返回0,若出错,返回-1

根据oldname是指文件目录还是符号链接,有以下情况需要说明:
1.如果oldname指的是一个文件而不是目录,那么为该文件或符号链接重命名。在这种情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除然将oldname重命名为newname。对包含oldname的目录以及包含newname的目录调用进程必须具有写权限,因为将更改这两个目录。
2.如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录。如果newname存在,而且该目录是一个空目录,则先删除该目录,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为路径前缀
3.如若oldnamenewname引用符号链接,则处理的是符号链接本身,而不是它所引用的文件
4.不能对 ... 重命名。更准确的说,...不能出现在oldname和newname的最后部分
5.作为一个特例,如果oldnamenewname引用同一个文件,则函数不做任何更改而成功返回

如若newname已存在,则调用进程对它需要具有写权限。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname包含newname的目录具有执行权限

如果oldnamenewname指向相对路径,有以下几种情况:
1.oldname参数指定了相对路径,就相对于oldfd参数引用的目录计算oldname
2.newname参数指定了相对路径,就相对于newfd参数引用的目录计算newname
3.oldfdnewfd参数设置为AT_FDCWDoldnamenewname就相对于当前目录来计算相应的路径名

除了oldnamenewname指向相对路径名的情况外,renamerenameat函数功能相同

十六、符号链接

符号链接对一个文件的间接指针,而硬链接直接指向文件的i节点符号链接能避开硬链接的一些限制
1.硬链接通常要求链接和文件位于同一文件系统中
2**.只有超级用户才能创建指向目标的硬链接**。

符号链接以及它所指向何种对象并无任何文件限制任何用户都可以创建指向目录的符号链接符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置

使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是该函数是否跟随符号链接到达它所链接的文件。如若该函数具有处理符号链接的功能,则其路径名参数引用符号链接指向的文件。否则,一个路径名参数引用链接本身,而不是由该链接指向的文件

各函数对符号链接的处理
这里写图片描述
上图存在例外:同时用O_CREAT和O_EXCL两者调用open函数。在此情况下,若路径名引用符号链接open将出错返回errno设置为EEXIST

利用shell创建符号链接
这里写图片描述

创建testdir符号链接链接到foo文件夹的结构图
这里写图片描述

open打开文件时,如果传递给open函数的路径名指定了一个符号链接,那么open随着链接到达指定的文件。若此符号链接所指向的文件并不存在,则open返回出错,表示不能打开该文件

十七、创建和读取符号链接

symlinksymlinkat函数创建一个符号链接

#include < unistd.h >
int symlink(const char *actualpath,const char *sympath)
int symlinkat(const char *actualpath,int fd,const char *sympath)
若成功,返回0,若出错,返回-1

函数创建一个指向actualpath的新目录项sympath。在创建此符号链接时,并不要求actualpath已经存在。并且,actualpath和sympath并不需要位于同一文件系统中

symlinkat函数symlink函数类似,但sympath参数根据相对于打开文件描述符引用的目录(由fd参数指定)进行计算。如果sympath参数指定的是绝对路径或者fd参数设置了AT_FDCWD值,那么symlinkat函数就等同于symlink函数

用于打开符号链接的函数readlink和readlinkat函数

#include < unistd.h >
size_t readlink(const char *restrict pathname,char *restrict buf,size_t bufsize)
size_t readlinkat(int fd,const char *restrict pathname,char *restrict buf,size_t bufsize)
若成功,返回读取的字节数,若出错,返回-1

两个函数组合了open、read和close的所有操作。如果函数成功执行,则返回读入buf的字节数。在buf中返回的符号链接的内容不以null字节终止

pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWDreadlinkat函数的行为与readlink函数相同。但是,如果fd参数一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于由fd代表的打开目录的路径名

十八、文件的时间

对每个文件维护3个时间字段,意义如下:

字段 说明 例子 ls(1)选项 st_atim 文件数据的最后访问时间 read -u st_mtim 文件数据的最后修改时间 write -l、-t(默认) st_ctim i节点状态的最后更改时间 chmod、chown -c

修改时间和状态更新时间之间的区别
修改时间是文件内容最后一次被修改时间
状态更新时间是该文件的i节点的最后一次被修改的时间

系统管理员常常使用访问时间来删除在一定时间范围内没有被访问过的文件。find(1)命令常被用来查找在一段时间范围内没有被访问的文件。

修改时间和状态更改时间可被用来归档那些内容已经被修改或i节点已经被更改的文件。

各种函数对访问、修改和状态更新时间的作用
这里写图片描述

十九、函数futimens、utimensat和utimes

一个文件的访问和修改时间可以用以下几个函数更改。

#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)
若成功,返回0,若出错,返回-1

这两个函数的times数组参数的第一个元素包含访问时间第二元素包含修改时间。这两个时间值是日历时间
时间戳可以按下列4种方式之一进行指定。
(1)如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间
(2)如果times参数指向两个timespec结构的数组任一数组元素的tv_nsec字段的值为UTIME_NOW相应的时间戳就设置为当前时间忽略相应的tv_sec字段
(3)如果times参数指向两个timespec结构的数组任一数组元素的tv_nsec字段的值为UTIME_OMIT相应的时间戳保持不变忽略相应的tv_sec字段
(4)如果times参数指向两个timespec结构的数组,且tv_nsec字段的值既不是UTIME_NOW也不是UTIME_OMIT,在这种情况下,相应的时间戳设置为相应的tv_sec和tv_nsec的字段值

执行这些函数所要求的优先权取决于times参数的值
- 如果times是一个空指针,或者任一tv_nsec字段设为UTIME_NOW,则进程的有效用户ID必须等于该文件的所有者ID进程该文件必须具有写权限,或者进程是一个超级用户进程
- 如果times非空指针,并且任一tv_nsec字段的值既不是UTIME_NOW也不是UTIME_OMIT,则进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程对文件只具有写权限是不够的
- 如果times非空指针,并且两个tv_nsec字段的值都为UTIME_OMIT,就不执行任何的权限检查

futimens函数需要打开文件来更改它的时间utimensat函数提供一种是用文件名更改文件时间的方法pathname参数相对于fd参数进行计算的fd要么是打开目录的文件描述符,要么设置为特殊值AT_FDCWD(强制通过相对于调用进程的当前目录计算pathname)。如果pathname指定了绝对路径,那么fd参数被忽略

utimensatflag参数可用于进一步修改默认行为。如果设置了AT_SYMLINK_NOFOLLOW标志,则符号链接本身的时间就会被修改(如果路径名指向符号链接)。默认的行为是跟随符号链接,并把文件的时间改成符号链接的时间

utimes函数也可以修改访问时间和修改时间。

#include < sys/time.h >
int utimes(const char *pathname,const struct timeval times[2])
若成功,返回0,若失败,返回-1

utimes函数对路径名进行操作。times参数指向包含两个时间戳(访问时间和修改时间)元素的数组的指针,两个时间戳使用微秒表示的。

struct timeval {
time_t tv_sec ; /* seconds */
long tv_usec;/* microseconds */
}

touch(1)命令使用这些函数中的某一个,标准归档程序tar(1)和cpio(1)可选地调用这些函数,以便将一个文件的时间值设置为将它归档时保存的时间。

使用futimens函数的使用

#include <fcntl.h>#include "apue.h"intmain(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]);        close(fd);    }    exit(0);}

这里写图片描述

二十、函数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)
若成功,返回0,若出错,返回-1

这两个函数创建一个新的空目录。其中 ... 目录项是自动创建的。所指定的文件访问权限mode进程的文件模式创建屏蔽字修改
注意:对于目录通常最少要设置一个执行权限位,以允许访问该目录中的文件名

mkdir函数与mkdir函数类似。当fd参数具有特殊值AT_FDCWD或者pathname参数指定了绝对路径名时,mkdirat与mkdir完全一样。否则,fd参数是一个打开目录,相对路径根据此打开目录进行计算。

用rmdir函数可以删除一个空目录。空目录代表只存在 . 和 .. 这两项的文件夹。

#include < unistd.h >
int rmdir(const char *pathname)
若成功,返回0,若出错,返回-1

如果调用此函数使路径的链接计数成为0,而且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接 . 和 .. 项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录

二十一、读目录

对某个目录具有访问权限的任一用户都可以读给目录,但是,为了防止文件系统产生混乱,只有内核才能写目录。一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件,但是并不代表能写目录本身。

对目录进行操作的函数

#include < dirent.h >
DIR *opendir(const char *pathname)
DIR *fdopendir(int fd)

若成功,返回指针,若出错,返回NULL
struct dirent *readdir(DIR *dp)
若成功,返回指针,若出错,返回NULL
void rewinddir(DIR *dp)
int closedir(DIR *dp)
若成功,返回0,若出错,返回-1
long telldir(DIR *dp)
返回与dp关联的目录中的当前位置
void seekdir(DIR *dp,long loc)

fdopendir函数提供了一种方法,可以把打开文件描述符转换成目录处理函数需要的DIR结构
opendirfdopendir返回的指向DIR结构的指针另外5个函数使用opendir执行初始化操作,使第一个readdir返回目录中第一个目录项DIR结构fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。注意,目录中各目录项的顺序与实现有关。通常不按字母顺序排列

遍历目录层次结构,并按文件计数的程序

#include "apue.h"#include <dirent.h>#include <limits.h>/* function type that is called for each filename */typedef int Myfunc(const char *, const struct stat *, int);static Myfunc myfunc;static int myftw(char *, Myfunc *);static int dopath(Myfunc *);static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;intmain(int argc, char *argv[]){    int  ret;    if (argc != 2)        err_quit("usage: ftw <starting-pathname>");    ret = myftw(argv[1], myfunc); /* does it all */    ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;    if (ntot == 0)        ntot = 1; /* avoid divide by 0; print 0 for all counts */    printf("regular files = %7ld, %5.2f %%\n", nreg,        nreg*100.0/ntot);    printf("directories  = %7ld, %5.2f %%\n", ndir,        ndir*100.0/ntot);    printf("block special = %7ld, %5.2f %%\n", nblk,        nblk*100.0/ntot);    printf("char special = %7ld, %5.2f %%\n", nchr,        nchr*100.0/ntot);    printf("FIFOs  = %7ld, %5.2f %%\n", nfifo,        nfifo*100.0/ntot);    printf("symbolic links = %7ld, %5.2f %%\n", nslink,        nslink*100.0/ntot);    printf("sockets  = %7ld, %5.2f %%\n", nsock,        nsock*100.0/ntot);    exit(ret);}/** Descend through the hierarchy, starting at "pathname".* The caller’s func() is called for every file.*/#define FTW_F 1 /* file other than directory */#define FTW_D 2 /* directory */#define FTW_DNR 3 /* directory that can’t be read */#define FTW_NS 4 /* file that we can’t stat */static char *fullpath; /* contains full pathname for every file */static size_t pathlen;static int /* we return whatever func() returns */myftw(char *pathname, Myfunc *func){    fullpath = path_alloc(&pathlen); /* malloc PATH_MAX+1 bytes */                    /* (Figure 2.16) */    if (pathlen <= strlen(pathname)) {        pathlen = strlen(pathname) * 2;        if ((fullpath = realloc(fullpath, pathlen)) == NULL)            err_sys("realloc failed");    }    strcpy(fullpath, pathname);    return(dopath(func));}/** Descend through the hierarchy, starting at "fullpath".* If "fullpath" is anything other than a directory, we lstat() it,* call func(), and return. For a directory, we call ourself* recursively for each name in the directory.*/static int /* we return whatever func() returns */dopath(Myfunc* func){    struct stat statbuf;    struct dirent *dirp;    DIR  *dp;    int  ret, n;    if (lstat(fullpath, &statbuf) < 0) /* stat error */        return(func(fullpath, &statbuf, FTW_NS));    if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */        return(func(fullpath, &statbuf, FTW_F));    /*    * It’s a directory. First call func() for the directory,    * then process each filename in the directory.    */    if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)        return(ret);    n = strlen(fullpath);    if (n + NAME_MAX + 2 > pathlen) { /* expand path buffer */        pathlen *= 2;        if ((fullpath = realloc(fullpath, pathlen)) == NULL)            err_sys("realloc failed");    }    fullpath[n++] = '/';    fullpath[n] = 0;    if ((dp = opendir(fullpath)) == NULL) /* can’t read directory */        return(func(fullpath, &statbuf, FTW_DNR));    while ((dirp = readdir(dp)) != NULL) {        if (strcmp(dirp->d_name, ".") == 0 ||strcmp(dirp->d_name, "..") == 0)            continue;  /* ignore dot and dot-dot */        strcpy(&fullpath[n], dirp->d_name); /* append name after "/" */        if ((ret = dopath(func)) != 0) /* recursive */            break; /* time to leave */    }    fullpath[n-1] = 0; /* erase everything from slash onward */    if (closedir(dp) < 0)        err_ret("can’t close directory %s", fullpath);    return(ret);}static intmyfunc(const char *pathname, const struct stat *statptr, int type){    switch (type) {    case FTW_F:    switch (statptr->st_mode & S_IFMT) {        case S_IFREG: nreg++;  break;        case S_IFBLK: nblk++;  break;        case S_IFCHR: nchr++;  break;        case S_IFIFO: nfifo++;  break;        case S_IFLNK: nslink++;  break;        case S_IFSOCK: nsock++;  break;        case S_IFDIR: /* directories should have type = FTW_D */            err_dump("for S_IFDIR for %s", pathname);    }    break;    case FTW_D:        ndir++;        break;    case FTW_DNR:        err_ret("can’t read directory %s", pathname);        break;    case FTW_NS:        err_ret("stat error for %s", pathname);        break;    default:        err_dump("unknown type %d for pathname %s", type, pathname);    }    return(0);}

这里写图片描述

二十二、函数chdir、fchdir和getcwd

每个进程存在一个当前工作目录,这个目录是搜索所有相对路径名的起点。当用户登录到UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)该用户登陆项的第6个字段——用户的起始目录。当工作目录进程一个属性起始目录则是登录名的一个属性
进程可以调用chdirfchdir函数可以更改当前工作目录

#include < unistd.h >
int chdir(const char *pathname)
int fchdir(int fd)
若成功,返回0,若出错,返回-1

这两个函数中,分别用pathname或打开文件描述符来指定新的当前工作目录。

chdir函数实例

#include "apue.h"intmain(void){    if (chdir("/tmp") < 0)        err_sys("chdir failed");    printf("chdir to /tmp succeeded\n");    exit(0);}

这里写图片描述

每个程序运行在独立的进程中,shell的当前工作目录并不会随着程序调用chdir而改变。为了改变shell进程自己的工作目录,shell应当直接调用chdir函数,所以cd内建在shell中。

获得绝对路径的函数getcwd

#include < unistd.h >
char *getcwd(char *buf,size_t size)
若成功,返回buf,若出错,返回NULL

必须向此函数传递两个参数,一个缓冲区地址buf,另一个是缓冲区的长度size。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错。

切换目录后返回的方法有两种:
1.在更换工作目录之前,我们可以调用getcwd函数先将其保存起来。在完成处理后,就可将所保存的原工作目录路径名作为参数传递给chdir,从而返回文件系统中的出发点。
2.fchdir直接使用open打开当前的工作目录,然后保存其返回的文件描述符。当希望回到当前工作目录时,只要简单的将该文件描述符传给fchdir。

getcwd函数的实现

#include "apue.h"intmain(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);}

这里写图片描述

二十三、设备特殊文件

st_devst_rdev两个字段的规则
- 每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型基本系统数据类型dev_t主设备号标识设备驱动程序,有时编码为其通讯的外设板次设备号标识特定的子设备。同一个磁盘驱动器上的各文件系统通常具有相同的主设备号,但次设备号却不同
- 通常可以使用两个宏:majorminor访问主、次设备号,大多数实现都定义这两个宏。
- 系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及其对应的i节点
- 只有字符特殊文件块特殊文件才有st_rdev值。此值包含实际设备的设备号

打印st_dev和st_rdev值的程序

#include <sys/types.h>#include "apue.h"intmain(int argc, char *argv[]){    int  i;    struct stat buf;    for (i = 1; i < argc; i++) {        printf("%s: ", argv[i]);        if (stat(argv[i], &buf) < 0) {            err_ret("stat error");        continue;        }        printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev));        if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) {            printf(" (%s) rdev = %d/%d",(S_ISCHR(buf.st_mode)) ? "character" : "block",major(buf.st_rdev), minor(buf.st_rdev));        }        printf("\n");    }    exit(0);}

这里写图片描述

二十四、文件访问权限位小结

文件访问权限位小结
这里写图片描述
将最后9个常量还可以分成如下3组:
S_IRWXU=S_IRUSR|S_IWUSR|S_IXUSR
S_IRWXG=S_IRGRP|S_IWGRP|S_IXGRP
S_IRWXO=S_IROTH|S_IWOTH|S_IXOTH

阅读全文
0 0