UNIX环境高级编程——第四章-文件和目录

4.2 函数stat、fstat、lstat

1、函数原型:

#include 
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);

所有4个函数的返回值:若成功:返回0;若出错,返回-1

2、一旦给出pathname,stat函数将返回此命名文件有关的信息结构。

3、fstat函数获得已在描述符fd上打开文件的有关信息。

4、lstat函数类似于stat,但是当命名空间是一个符号链接时,lstat返回该符号链接的有关信息,而不是该符号链接引用的文件的信息。

5、fstatat函数为一个相对于当前打开目录,由(fd参数指向)的路径名返回文件统计信息。flag参数控制着是否跟随一个符号链接。
(1)当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而是返回符号链接本身的信息。
(2)当fd参数的值是AT_FDCWD,并且pathname参数是一个相对路径名,fstatat会计算相对于当前目录的pathname参数。如果pathname是一个绝对路径,fd参数就会被忽略。

6、第2个参数buf是一个指针,它指向一个我们必须提供的结构。函数来填充由buf指向的结构。结构的实际定义可能随具体实现有所不同,基本形式如下:

struct stat {
mode_t      st_mode;        /* file type & mode (permission) */
ino_t       st_ino;         /* i-node number (serial number) */
dev_t       st_dev;         /* device number for special files */
nlink_t     st_nlink;       /* device number (file system) */
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_atime;   /* time of last access */
struct timespec st_mtime;   /* time of last modification */
struct timespec st_ctime;   /* time of last file status change */
blksize_t   st_blksize;     /* best I/O block size */
blkcnt_t    st_blocks;      /* number of disk blocks allocated */

}

(2)timespec结构按照秒和纳秒定义了时间,至少包括下面两个字段:

time_t  tv_sec;
long tv_nsec;

4.3 文件类型

1、文件类型包括如下几种:

1、普通文件(regular file)。
2、目录文件(directory file3、块设备文件(block special file)。这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
4、字符特殊文件(character special file)。这种类型文件提供对设备不带缓冲的访问,每次访问长度可变。系统中所有设备要么是块特殊文件。要么是字符特殊文件
5、FIFO。这种类型的文件用于进程间的通信,有时也称为命名管道(named pipe)
6、套接字(socket)。这种类型的文件用于进程间的网络通信。套接字也可以用于在一台宿主机上进程之间的非网络通信。
7、符号链接(symbolic link)。这种类型的文件指向另一个文件

2、在以下宏确定文件类型。这些宏的参数都是stat结构中的st_mode成员:

宏           文件类型
S_ISREG()       普通文件
S_ISDIR()       目录文件
S_ISCHR()       字符特殊文件
S_ISBLK()       块特殊文件
S_ISFIFO()      管道或FIFO
S_ISSLNK()      符号链接
S_ISSOCK()      套接字

3、POSIX.1允许实现将进程间通信(IPC)对象(如消息队列和信号量等)说明为文件。以下的宏可用来从stat结构中确定IPC对象的类型。它们的参数并非st_mode,而是指向stat结构的指针。

宏           对象的类型
S_TYPEISMQ()        信息队列
S_TYPEISSEM()       信号量
S_TYPEISSHM()       共享存储对象

4、实例4-3:取其命令行参数,然后针对每一个命令行参数打印其文件类型

/*************************************************************************
    > File Name: instance4-3.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: 2017年05月10日 星期三 07时49分07秒
 ************************************************************************/

#include "apue.h"

int main(int argc, char *argv[])
{
    int     i;
    struct  stat buf;
    char    *ptr;

    for (i = 1; i "%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);

}

4.4 设置用户ID和设置组ID

1、与一个进程相关联的ID有6个或更多:

实际用户ID      我们实际上是谁
实际组ID       

有效用户ID      用于文件访问权限检查
有效组ID
附属组ID

保存的设置用户ID       由exec函数保存
保存的设置组ID 

2、当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”。有效组ID类似。在文件模式字中这两位被成为设置用户ID(set-user-ID)位和设置组ID(set-group-ID)位。

3、设置用户ID位及设置组ID位都包含在文件的st_mode值中。这两位可分别用常量S_ISUIDS_ISGID测试。

4.5 文件访问权限

1、st_mode值也包含了对文件的访问权限位。每个文件有9个访问权限位,可将它们分成3类:

取自<sys/stat.h>
st_mode屏蔽           含义
S_IRUSR             用户读
S_IWUSR             用户写
S_IXUSR             用户执行

S_IRGRP             组读
S_IWGRP             组写
S_IXGRP             组执行

S_IROTH             其他读
S_IWOTH             其他写
S_IXOTH             其他执行

2、访问权限的规则:
(1)第一个规则:我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包含它可能隐含的当前工作目录都应具有执行权限。这就是目录其执行权限位常被称为搜索位的原因。

(2)对于目录的读权限和执行权限的意义是不相同的。
一、读权限允许我们读目录,获得在该目录所有文件名的列表。
二、当一个目录是我们要访问文件的路径名的一个组成部分时,对该目录的执行权限使我们可通过该目录(也就是搜索该目录,寻找一个特定的文件名)

(3)对一个文件的读权限决定我们能够打开现有文件进行读操作。

(4)对一个文件的写权限决定我们能够打开现有文件进行写操作。

(5)为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限。

(6)为了在一个目录中创建一个文件,必须对该目录具有写和执行权限。

(7)为了删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限。

3、内核进行文件访问权限测试具体流程:
(1)若进程的有效ID 是0(超级用户),则允许访问。这给予了超级用户对整个文件系统进行处理的充分的自由。
(2)若进程的有效用户ID等于文件的所有者(也就是进程拥有此文件),那么如果所有者适当的访问权限位(读、写、执行权限)被设置,则允许访问,否则拒绝访问。
(3)若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果适当的访问权限位置被设置,则允许访问,否则拒绝访问。
(4)若其他用户适当的访问权限被设置,则允许访问,否则拒绝访问。

4.6 新文件的目录的所有权

1、新文件的所有权规则:
(1)新文件的用户ID设置为进程的有效用户ID
(2)新文件的组ID可以是进程的有效组ID
(3)新文件的组ID可以是它所在目录的组ID

4.7 函数access和faccessat

1、内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。access和faccessat函数是按实际用户ID和实际组ID进行访问权限测试的。

#include 
int access(const char *pathname, int mode);
int faccess(int fd, const char *pathname, int mode, int flag);

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

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

5、access函数的用法

#include "apue.h"
#include 
int main(int argc, char *argv[])
{
    if (argv != 2)
        err_quit("usage:a.out ");
    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);
}

4.8 函数umask

1、umask函数为设置文件模式创建屏蔽字,并返回之前的值。

#include 
mode_t  umask(mode_t cmask);
                        返回值,之前的文件模式创建屏蔽字。

2、实例:

/*************************************************************************
    > File Name: ins4_9.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Tue 08 Aug 2017 10:51:21 AM CST
 ************************************************************************/

/*  modifity the permission of file bar and foo  */
/*
-rw------- 1 king king 0 Aug  8 10:46 bar
-rw-rw-rw- 1 king king 0 Aug  8 10:46 foo
*/

#include "apue.h"

int
main(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);
}

3、更改进程的文件模式创建屏蔽字并不影响其父进程(常常是shell)的屏蔽字。

4.9 函数chmod、fchmod和fchmodat

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

#include 
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);
                            3个函数返回值,若成功,返回0,若出错,返回-1

2、chmod函数在指定的文件上操作,而fchmod函数则对已打开的文件进行操作。

3、fchmodat与chmod在下面两种情况是相同的:
(1)pathname参数为绝对路径
(2)fd取值为AT_FDCWD,而pathname参数为相对路径。
否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname。

4、flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。

5、为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者进程必须具有超级用户权限。

6、参数mode是下图所示常量的按位或:

mode                            说明
S_ISUID                     执行时设置用户ID
S_ISGID                     执行时设置组ID
S_ISVTX                     保存正文(粘着位)

S_IRWXU                     用户(所有者)读、写和执行
    S_IRUSR                     用户(所有)读
    S_IWUSR                     用户(所有)写
    S_IXUSR                     用户(所有)执行

S_IRWXG                     组读、写和执行
    S_IRGRP                 组读
    S_IWGRP                 组写
    S_IXGRP                 组执行

S_IRWXO                     其他读、写和执行
    S_IROTH                 其他读
    S_IWOTH                 其他写
    S_IWOTH                 其他执行

7、chmod实例:

/*************************************************************************
    > File Name: ins4_9.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Tue 08 Aug 2017 10:51:21 AM CST
 ************************************************************************/

/*  modifity the permission of file bar and foo  */
/*
-rw------- 1 king king 0 Aug  8 10:46 bar
-rw-rw-rw- 1 king king 0 Aug  8 10:46 foo
*/

#include 

int
main(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);
}

4.10 粘着位

1、S_ISVTX位被称为粘着位(sticky bit)。如果一个可执行程序文件的这一位被设置,那么该程序第一次被执行,在其终止时,程序的正文部分的一个副本仍被保存在交换区(程序的正文部分是机器指令)。这使得下一次执行该程序时还能较快地将其装载入内存。

2、Single UNIX Specification允许针对目录设置粘着位。如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:

1、拥有此文件
2、拥有此目录
3、是超级用户

4.11 函数chown、fchown、fchownat和lchown

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

#include 
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);

2、在符号链接情况下,lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者。

3、fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已经打开的文件上操作,就不能用于改变符号链接的所有者。

4、fchownat函数与chown函数或者lchown函数在下面两种情况下是相同的:
(1)pathname参数为绝对路径
(2)fd参数取值为AT_FDCWD而pathname参数为相对路径。
在以上两种情况下,如果flag参数设置了AT_SYMLINK_NOFOLLOW标志,fchownat与lchown行为相同,如果flag参数清除了AT_SYMLINK_NOFOLLOW标志,fchownat和chown行为相同。

5、_POSIX_CHOWN_RESTRICTED常量可选地定义在头文件

4.12 文件长度

1、stat结构成员st_size表示以字节位单位的文件的长度,此字段对普通文件、目录文件和符号链接有意义。

2、对于普通文件,其长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示。

3、对于目录,文件长度通常是一个数(如16或512)的整倍数。

4、对于符号链接,文件长度是在文件名中的实际字节数
(注意,因为符号链接的长度总是由st_size指示,所以它并不包含C语言用作名字结尾的NULL字节

5、st_blksize是对文件I/O比较合适的块长度。

6、st_blocks是所分配的实际512字节块块数。

文件中的空洞

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

4.13 文件截断

1、将一个文件长度截断为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点。为了截断文件可以调用函数truncate和ftruncate

#include 
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
                                两个函数的返回值:若成功,返回0,若出错,返回-1

2、如果该文件以前的长度大于length,则超过length以外的数据就不能再访问。
如果以前的长度小于length,文件长度将增加,形成空洞。

4.14 文件系统

1、UFS快速文件系统图示:
UNIX环境高级编程——第四章-文件和目录_第1张图片

————————————————————–

UNIX环境高级编程——第四章-文件和目录_第2张图片

2、每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可删除文件(也就是释放该文件占用的数据块)。

2、在stat结构中,链接计数包含在st_nlink成员中,其基本数据类型是nlink_t。这种链接类型称为硬链接

3、另一种链接类型称为符号链接(symbolic link)。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向文件的名字。

4、i节点包含了文件有关的所有信息:
(1)文件类型
(2)文件访问权限位
(3)文件长度
(4)指向文件数据块的指针等。

5、stat结构中的大多数信息都取自i节点。只有两个重要数据存放在目录项中:
(1)i节点编号
(2)文件名

6、因为目录项中的i节点编号指向同一个文件系统的相应i节点,一个目录项不能指向另一个文件系统的i节点。所以ln命令不能跨越文件系统。

7、当不更改文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数不会改变。

UNIX环境高级编程——第四章-文件和目录_第3张图片
8、编号为2549的i节点,起类型字段表示它是一个目录,链接计数为2.任何一个叶目录(不包含任何其他目录的目录)的链接计数总是2,数值2来自于命名该目录(testsdir)的目录项以及在目录中的.项
编号为1267的i节点,其类型表示它是一个目录,链接计数大于或等于3。它大于或等于3的原因是,至少有3个目录项指向它:
(1)一个是命名它的目录项(图中没有表示出来)
(2)第二个是该目录项中的.项目
(3)第三个是在其子目录testdir中的..项。

4.15 函数link、linkat、unlink、unlinkat和remove

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

#include 
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
                                    两个函数的返回值:若成功,返回0;若出错,返回-1

2、这两个函数创建一个目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。

3、对于linkat函数,现有文件是通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指定的。
(1)默认情况下,两个路径中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算
(2)如果两个文件描述符中的任一个设置为AT_FDCWD,那么相对路径名(如果它是相对路径)就通过相对于当前目录进行计算。

4、当现有文件是符号链接时,由flag参数来控制linkat函数是创建指向现有符号链接的链接还是创建指向现有符号链接所指向的文件的链接
(1)如果flag参数设置了AT_SYMLINK_FOLLOW标志,则创建指向符号链接目标的链接
(2)如果flag**没有设置AT_SYMLINK_FOLLOW标志,则创建一个指向符号链接本身的链接**。

5、删除一个现有的目录项,可以调用unlink函数。

#include 
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname);
                                两个函数的返回值:若成功,返回0,若出错,返回-1

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

7、为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限

8、如果对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:
(1)拥有此文件
(2)拥有此目录
(3)具有超级用户权限

9、只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会组织删除文件的内容——只要有进程打开了该文件,其内容也不能删除。

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

11、打开一个文件,然后解除它的链接。执行该程序的进程然后睡眠15秒

/*************************************************************************                                              
    > File Name: ins4_15.c    
    > Author: King            
    > Mail: [email protected]                      
    > Created Time: Tue 08 Aug 2017 02:56:22 PM CST         
 ************************************************************************/                                              

#include              
#include             

int                           
main(void)                    
{                             
        if (open("tempfile", O_RDWR) < 0)                   
                err_sys("open error");                      
        if (unlink("tempfile") < 0)                         
                err_sys("unlink error");                    
        printf("file unlink\n");                            
        sleep(15);            
        printf("done\n");     
        exit (0);             
}   

12、unlink的这种特性经常被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来

13、我们也可以用remove函数解除对一个文件或目录的链接。对于文件,remove的功能与unlink相同,对于目录,remoe的功能与rmdir相同。

#include 
int remove(const char *pathname);
                                        返回值:若成功,返回0;若出错,返回-1

4.16 函数rename和renameat

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

#include 
int rename(const char *oldname, const char *newname);
int rename(int oldfd, const char *oldname, int newfd, const char *newname);
                                        两个函数的返回值:若成功,返回0,若出错,返回-1

2、如果oldname制定的是一个文件而不是目录,那么为该文件或符号链接重命名。
(1)该情况下,如果newname存在,则它不能引用一个目录。
(2)如果newname已经存在,而且不是一个目录,则先将该目录项删除然后将oldname重命名为newname
(3)对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。

3、若oldname指的是一个目录,那么为该目录重命名。
(1)如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(只有.和..项)。
(2)如果newname存在(而且是一个空目录),则现将其删除,然后将oldname重命名为newname。
(3)另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。如不能讲/usr/foo重命名为/usr/foo/testdir
(4)不能对.和..重命名。确切的说,..和.都不能出现在oldname和newname的最后部分

4、特例:如果oldname和newname引用同一个文件,则函数不做任何更改而且正确返回

5、如果newname已经存在,则调用进程需要有写权限(如同删除情况一样)。另外,调用进程讲删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行权限

6、除了当oldname或newname指向相对路径名时,其他情况下renameat函数与rename函数功能相同。
(1)如果oldname指定了相对路径,就相对于oldfd参数引用目录来计算oldname。
(2)如果newname指定了相对路径,就相对于newfd引用的目录来计算newname。
(3)oldfd或newfd参数(或两者)都能设置成AT_FDCWD,此时相对于当前目录来计算相应的路径名。

4.17 符号链接

1、引入符号链接的原因是为了避开硬链接的一些限制:
(1)硬链接通常要求链接和文件位于同一文件系统中
(2)只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下)

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

3、当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。
UNIX环境高级编程——第四章-文件和目录_第4张图片
(1)此图的例外:同时O_CREAT和O_EXCL两者调用open函数。此情况下,若路径名引用符号链接,open将出错返回,errno设置为EEXIST。这种处理方式的意图是堵塞一个安全性漏洞,以防止具有特权的进程被诱骗写错误的文件。

4.18 创建和读取符号链接

1、可以用symlink或symlinkat函数创建一个符号链接:

#include 
int symlink(const char *actualpath, const sympath);
int symlinkat(const char *actualpath,int fd, const char *sympath);
                                        两个函数返回值:若成功,返回0;若出错,返回-1

2、在创建符号链接时候,并不要求actualpath已经存在,并且actualpath和sympath并不需要位于同一个文件系统中。

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

4、因为open函数跟随符号链接,所以需要一种方法打开符号链接本身,并读该链接中的名字。readlink和readlinkat函数提供了这种功能。

#include 
ssize_t readlink(const char *restrict pathname, char *restrict buf, 
                size_t bufsize)
ssize_t readlinkat(int fd, const char *restrict pathname, 
                    char *restrict buf, size_t bufsize);
                                        两个函数返回值:若成功,返回读取的字节数;若出错,返回-1

4.19 文件的时间

1、对每个文件维护3个时间
UNIX环境高级编程——第四章-文件和目录_第5张图片

2、注意,修改时间(st_ntim)和状态更改时间(st_ctim)之间的区别。
(1)修改时间是文件内容最后一次被修改的时间
(2)状态更改时间是该文件的i节点最后一次被修改的时间

3、i节点被影响的操作:
(1)更改文件的访问权限
(2)更改用户ID
(3)更改链接数等
它们并没有更改文件的实际内容。因为i节点中的所有信息都是与文件的实际内容分开存放的。所以,出来要记录文件数据修改时间(st_mtim)外,还需要记录状态更改时间(st_ctim),也就是更改i节点中信息的时间。

4、注意,系统并不维护对一个i节点的最后一次访问时间(st_atim),所以access和stat函数并不更改这3个时间中的任一个。

5、ls命令按这3个时间值中的一个排序进行显示。系统默认(用-l 或 -t 选项调用时)是按文件的修改时间(st_mtim)的先后顺序显示。-u选项使ls命令是按访问时间(st_atim)排序。-c选项则使其按状态更改时间(st_ctim)排序。

6、目录是包含目录项(文件名和相关i节点编号)的文件,增加、删除或修改目录项会影响到它所在目录相关的3个时间

7、各种函数对访问、修改和状态更改时间的作用:
UNIX环境高级编程——第四章-文件和目录_第6张图片

4.20 futimens、utimensat和utimes

1、一个文件的访问时间和修改时间可以用以下几个函数更改。futimens和utimensat函数可以指定纳秒级精度的时间戳。可以用到的数据结构是与stat函数族相同的timespec结构。

#include 
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
                struct timespec {
                         time_t          tv_sec;
                         long            tv_nsec;
                 };

                                        两个函数返回值:若成功,返回0;若出错,返回-1

2、这两个函数的times数组参数的第一个元素是包含访问时间,第二个元素包含更改时间。

3、时间戳可以按下列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。

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

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

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

7、第三个函数utimes包含在 UNIX Specification的XSI扩展选项中。

#include 
int utimes(const char *pathname, const struct timeval times[2]);

8、足以,我们不能对状态更改时间st_ctim(i节点最近被修改时间)指定一个值,因为调用utimes函数时,此字段会被自动更新。

9、实例:使用带O_TRUNC选项的open函数讲文件长度截断为0,但并不更改其访问时间和修改时间。

/*************************************************************************                                              
    > File Name: ins4_21.c    
    > Author: King            
    > Mail: [email protected]                      
    > Created Time: Sun 13 Aug 2017 10:09:06 AM CST         
 ************************************************************************/                                              

/*  turncate the file length to zero, and keep the modifity time and change time  */                                    
#include "apue.h"             
#include             

int                           
main(int argc, char *argv[])  
{                             
        int             i,fd; 
        struct  stat    statbuf;                            
        struct  timespec        times[2];                   
        if (argc < 2)         
                err_quit("Usage: ./ins4_21 ");    
        for (i = 1; i < argc; i++)                          
        {                     
                if (stat(argv[i],&statbuf) < 0)         /*  fetch current times  */                                     
                {             
                        err_ret("%s: stat error", argv[i]); 
                }             

                if ((fd = open(argv[i],O_RDWR | O_TRUNC)) < 0)                                                          
                {             
                        err_ret("%s: open error", argv[i]); 
                        continue;                           
                }             
                times[0] = statbuf.st_atim;                 
                times[1] = statbuf.st_mtim;                 

                if (futimens(fd, times) < 0)                
                        err_ret("%s: futimens error", argv[i]);                                                         
                close(fd);    
        }                     
        exit (0);             
} 

4.21 函数mkdir、mkdirat和rmdir

1、用mkdir和mkdirat函数创建目录

#include 
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
                                    两个函数返回值:若成功,返回0,若出错,返回-1

2、这两个函数创建一个新的空目录。其中,.和..是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

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

4、rmdir函数可以删除一个空目录。空目录只包含.和..这两项的目录

#include 
int rmdir(const char *pathname);
                                    返回值:若成功,返回0;若出错,返回-1

5、如果调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数的返回值前删除最后一个链接及.和..项。

4.22 读目录

1、对某个目录具有访问权限的任一用户都可以读该目录,但是,为了防止文件系统产生混乱,只有内核才能写目录

#include 
DIR *opendir(const char *pathname);
DIR *fdopendirat(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);

2、fdopendir函数可以吧文件描述符转换成目录处理函数需要的DIR结构。

3、telldir和seekdir函数 是Single UNIX Specification中的XSI扩展。

4、定义在头文件中的dirent结构与实现有关。实现对此结构所做的定义至少包含下列两个成员:

ino_t   d_ino;                          /*i-node number*/
char    d_name[]                        /*null-terminated filename*/

(1)d_name大小没有指定,但必须保证它能包含至少NAME_MAX个字节(不包含终止null字节)。

5、由opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。
(1)opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项(i节点编号和文件名)
(2)DIR结构由fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关的稳健偏移量。
(3)注意,目录中各目录项的顺序与实现有关,他们通常并不按字母顺序排列

6、实例:便利文件层次结构程序:

/*************************************************************************
    > File Name: ins4_22.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Sun 13 Aug 2017 03:53:05 PM CST
 ************************************************************************/

/*  The program of document traversal  */
/*  Before you complie ins4_22.c, you must annotation error.c in apue.h 
 *  and copy error.c 、pathallocate.c to this directory(error.c in Linux /usr/include/error.c).
 *  Complie : gcc ins4_22.c error.c pathallocate.c -o ins4_22
 */

#include "apue.h"
#include 
#include 
#include 


/*  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;

int
main(int argc, char *argv[])
{
    double  whole;
    clock_t start,finish;
    int     ret;
    start = clock();
    if (argc != 2)
        err_quit("usage: ftw ");
    ret = myftw(argv[1], myfunc);
    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);
    finish = clock();
    whole = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("ins4_22 cost : %f second\n", whole);
    exit(ret);
}

/*
 * Descend through the hierachy, 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  */
                                            /*  ({Flgure 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 hirearchy, 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 whatevery 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("realoc 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);
        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 int
myfunc(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("unknow type %d for pathname %s", type, pathname);
    }
    return (0);
}

4.23 函数chdir、fchdir和getcwd

1、每个进程都有一个当前工作目录,此目录就是搜索所有相对路径的七点(不以斜线开始的路径名为相对路径)

2、当前工作目录是进程的一个属性,起始目录则是登录名的一个属性

3、进程调用chdir或fchdir函数可以更改当前工作目录:

#include 
int chdir(const char *pathname);
int fchdir(int fd);
                                            两个函数的返回值:若成功,返回0,;若出错,返回-1

4、实例:更改目录,当前目录是进程的一个属性,所以它只影响调用chdir的进程本身:

/*************************************************************************
    > File Name: ins4_23.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Mon 14 Aug 2017 07:38:23 AM CST
 ************************************************************************/

/*  an instance of change directory  */
#include "apue.h"

int
main(void)
{
        if (chdir("/tmp") < 0)
                err_sys("chdir failed");
        printf("chdir to /tmp successded\n");
        exit(0);
}

5、获取当前工作目录的绝对路径名函数getcwd

#include 
char *getcwd(char *buf, size_t size);
                                         返回值:若成功,返回buf,若出错,返回NULL。

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

6、实例:将工作目录更改至一个指定的目录,然后调用getcwd。

/*************************************************************************
    > File Name: ins4_23_2.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Mon 14 Aug 2017 07:48:09 AM CST
 ************************************************************************/

/*  将工作目录更改至一个指定的目录,然后调用getcwd,最后打印该工作目录。*/
/*  Complie : gcc ins4_23_2.c error.c pathallocate.c -o ins4_23_2               */

#include "apue.h"
int
main(void)
{
        char    *ptr;
        size_t  size;
        if (chdir("/home/king/LearningC/apue/Chapter4/testdir/spool/uucppublic") < 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);
}

(1)chdir跟随符号链接。

4.24 设备特殊文件

1、每个文件系统所在的存储设备都由主、次设备号表示。

2、设备号所用的数据类型是dev_t

3、主设备号标识设备驱动程序,有时编码为与其通信的外设板,次设备号用于标识特定的子设备。

4、同一个磁盘驱动器上的各文件系统通常具有相同的主设备号,但次设备号不相同。

5、我们可以通过major和minor两个宏来访问主、次设备号。

6、系统中每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一个文件名以及与其对应的i节点。
()1
在Linux3.2.0上,_t虽然是64位整形,但只有12位用于主设备号,20位用于次设备号。

7、只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号

8、实例:打印设备号,并打印特殊文件的st_rdev值。

/*************************************************************************
    > File Name: ins4_24.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Mon 14 Aug 2017 09:34:24 AM CST
 ************************************************************************/


/*      程序为每个命令行参数打印设备号,另外,若此参数引用的是字符特殊文件或块特殊文件,则还打印特殊文件的st_rdev值。*/
#include "apue.h"
#include                               //major 和 minor 宏定义在/usr/include/sys/sysmacors.h中

#ifdef          SOLARIS
#include 
#endif

int
main(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);
}

你可能感兴趣的:(APUE学习笔记)