APUE学习笔记:第四章 文件和目录

4.1 引言

本章将描述文件的特征和文件的性质

4.2 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);

                三个函数的返回值:若成功则返回0,若出错则返回-1

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

fstat函数获取已在描述符filedes上打开文件的有关信息。

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

第二个参数buf是指针,它指向一个我们必须提供的结构。这些函数填写由buf指向的结构。该结构的实际定义可能随实现有所不同,但其基本形式是:

struct stat{
            mode_t    st_mode;   //file type&mode (permissions)
            ino_t       st_ino;      //i-node number
            dev_t       st_dev;    //device number(file system)
            dev_t       st_rdev;    //device number for special files
            nlink_t     st_ulink;    //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
            time_t      st_atime    //time of last access
            time_t      st_mtime    //time of last modification
            time_t      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
};//该结构中每一个成员都是基本系统数据类型

使用stat函数最多的可能是ls -l命令,用其可以获得有关一个文件的所有信息

4.3 文件类型

UNIX系统的大多数文件是普通文件或目录,但是也有另外一些文件类型。文件类型包括如下几种:

(1)普通文件  S_ISREG()

(2)目录文件  S_ISDIR()

(3)块特殊文件。这种文件类型提供对设备(例如磁盘)带缓冲的访问,每次访问以固定长度为单位进行  S_ISCHR()

(4)字符特殊文件。这种文件类型提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件  S_ISBLK()

(5)FIFO。这种类型文件用于进程间通信,有时也称为命名管道。  S_ISFIFO()

(6)套接字。这种类型用于进程间的网络通信。套接字也可用于在一台宿主机上进程之间的非网络通信  S_ISSOCK()

(7)符号链接。这种文件类型指向另一个文件  S_ISLNK()

(文件类型信息包含在stat结构的st_mode成员中,如S_ISREG)

POSIX.1允许实现将进程间通信(IPC)对象(例如,消息队列和信号量等)表示为文件。

<sys/stat.h>中的IPC类型宏:S_TYPEISMQ()  消息队列

               S_TYPEISSEM()  消息量

               S_TYPEISSHM()  共享存储对象

(这些宏的参数并非st_mode,而是指向stat结构的指针)

实例:4_1 对每个命令行参数打印文件类型

 1 #include"apue.h"
 2 int main(int argc,char *argv[])
 3 {
 4     int i;
 5     struct stat buf;
 6     char *ptr;
 7     
 8     for(i=1;i<argc;i++){
 9         printf("%s: ",argv[i]);
10         if(lstat(argv[i],&buf)<0){     //若改成stat(),则无法看到link类型
11             err_ret("lstat error");
12             continue;
13         }
14         if(S_ISREG(buf.st_mode))
15             ptr="regular";
16         else if (S_ISDIR(buf.st_mode))
17             ptr="directory";
18         else if (S_ISCHR(buf.st_mode))
19             ptr="character special";
20         else if (S_ISBLK(buf.st_mode))
21             ptr="block special";
22         else if (S_ISFIFO(buf.st_mode))
23             ptr="fifo";
24         else if (S_ISLNK(buf.st_mode))
25             ptr="symbolic link";
26         else if (S_ISSOCK(buf.st_mode))
27             ptr="socket";
28         else
29             ptr="** unknown mode **";
30         printf("%s
",ptr);
31     }
32     exit(0);
33 }

4.4 设置用户ID和设置组ID

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

-实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自口令文件中的登陆项。通常,在一个登陆会话间这些值并不改变,但是超级用户进程有方法改变它们

-有效用户ID,有效组ID以及附加组ID决定了我们的文件访问全乡

-保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本

通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是”当执行此文件时,将进程的有效用户ID设置为文件所有者ID(st_uid)“(st_gid情况也类似)

例如,若文件的所有者是超级用户,而且设置了该文件的设置用户ID位,然后当该程序由一个进程执行时,则该进程具有超级用户特权。(不管执行此文件的进程的实际用户ID是什么,都进行这种处理)

4.5 文件访问权限

st_mode值也包含了针对文件的访问权限位,每个文件有9个访问权限位,可将它们分成三类:owner  group  other

            S_IRUSR    用户-读

            S_IWUSR     用户-写

            S_IXUSR    用户-执行

            S_IRGRP    组-读

            S_IWGRP     组-写

            S_IXGRP    组-执行

            S_IROTH    其他-读

            S_IWOTH     其他-写

            S_IXOTH    其他-执行

注意:对于目录的读权限和执行权限的意义是不同的。读权限允许我们读目录,获得在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时,对该        目录的执行权限使我们可以通过该目录

访问位规则:

-我们用名字打开任意类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限

-对于一个文件的读权限决定了我们是否能够打开该文件进行读操作

-对于一个文件的写权限决定了我们是够能够打开该文件进行写操作

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

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

-为了删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限。对该文件本身则不需要有读写权限

-如果6个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限。该文件还必须是一个普通文件

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试:

(1)若进程的有效用户ID是0(超级用户),则允许访问。这给予了超级用户对整个文件系统进行处理的最充分的自由

(2)若进程的有效用户ID等于文件的所有者ID(也就是该进程拥有此文件),那么:若所有者适当的访问权限位被设置,则允许访问,否者拒绝访问。(适当的访问权限位指的是    若进程为读而打开该文件,则用户读位应为1;若进程为写而打开该文件,则用户写位应为1;若进程将执行该文件,则用户执行位应为1)

(3)若进程的有效组ID或进程的附加组ID之一等于文件的组ID,那么:若组适当的访问权限位被设置,则允许访问,否者拒绝访问。

(4)若其他用户适当的访问权限位被设置,则允许访问,否者拒绝访问

4.6 新文件和目录的所有权

新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX.1允许实现选择下列之一为新文件的组ID

(1)新文件的组ID可以是进程的有效组ID

(2)新文件的组ID可以是它所在目录的组ID

4.7 access函数

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

#include<unistd.h>

int access(const char *pathname ,int mode);

                        返回值:若成功则返回0,若出错则返回-1

access函数的mode常量:

          R_OK  测试读权限

          W_OK  测试写权限

          X_OK  测试执行权限

          F_OK  测试文件是否存在

实例:4_2 access函数实例

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 int main(int argc,char *argv[])
 4 {
 5     if(argc!=2)
 6     err_quit("usage: a.out <pathname>");
 7     if(access(argv[1],R_OK)<0)
 8     err_ret("access error for %s",argv[1]);
 9     else
10     printf("read access OK
");
11     if(open(argv[1],O_RDONLY)<0)
12     err_ret("open error for %s",argv[1]);
13     else 
14      printf("open for reading OK
");
15     exit(0);
16 }

4.8 umask函数

umask函数为进程设置文件模式创建屏蔽字,并返回以前的值

#include<sys/stat.h>

mode_t umask(mode_t cmask);

                            返回值:以前的文件模式创建屏蔽字

参数cmask是由st_mode中的9个常量(S_IRUSR,S_IWUSR等)中的若干个按位或构成的

在进程创建一个新文件或新目录时,就一定会使用文件模式创建屏蔽字

实例:4_3 umask函数实例

#include"apue.h"
#include<fcntl.h>

#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)

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

此程序创建了两个文件,创建第一个时,umask值为0,创建第二个时,umask值禁止所有组和其他用户的访问权限。

UNIX系统的大多数用户从不处理他们的umask值,但当我们的进程运行时,有效的umask值可能关闭该权限位,这时应该在进程运行时修改umask值

4.9 chmod 和fchmod 函数

这两个函数使我们可以更改现有文件的访问权限

#include<sys/stat.h>

int chmod(const char *pathname,mode_t mode);    //在指定的文件上进行操作

int fchmod(int filedes,mode_t mode);      //对已打开的文件进行操作

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

参数mode常量有9个与st_mode一样,不过多了6项,它们是两个设置ID常量(S_ISUID,S_ISGID),保存正文常量(S_ISVTX),以及三个组合常量(S_IRWXU,S_IRWXG,S_IRWXO)。

附:保存-正文位(S_ISVTX)不是POSIX.1的一部分。在SUS中,它被定义在XSI扩展中

实例:4_4 chmod 函数实例

 1 #include"apue.h"
 2 int main()
 3 {
 4     struct stat statbuf;
 5     
 6     /*turn on set-group-ID and turn off group-execute*/
 7     
 8     if(stat("foo",&statbuf)<0)
 9         err_sys("stat error for foo");
10     if(chmod("foo",(statbuf.st_mode&~S_IXGRP)|S_ISGID)<0)
11         err_sys("chmod error for foo");
12     
13     /*set absolute mode to "rw-r--r--"*/
14     if(chmod("bar",S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)<0)
15         err_sys("chmod error for bar");
16     exit(0);
17 }

(在运行程序4_4中的程序后,ls命令列出的时间和日期并没有改变。在4.18节中,我们会了解到chmod函数更新的只是i节点最近一次被更改的时间。按系统默认方式,ls -l 列出的是最后修改文件内容的时间)

4.10 粘住位(S_ISVTX)

如果对一个目录设置了粘住位,则只有对该目录具有写权限的用户在满足下列条件之一的情况下,才能删除或更名该目录下的文件:

-拥有此文件

-拥有此目录

-是超级用户

目录/tmp和/var/spool/uucppublic是设置粘住位的典型候选者------任何用户都可在这两个目录中创建文件。任意用户(用户、组和其他)对这两个目录的权限通常都是读、写和执行。但是用户不应能删除或更名属于其他人的文件,为此在这两个目录的文件模式中都设置了粘住位

4.11 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更改符号链接本身的所有者,而不是该符号链接所指向的文件

_POSIX_CHOWN_RESTRICTED指明使用chown是否是受限的

若_POSIX_CHOWN_RESTRICTED对指定的文件起作用,则

(1)只有超级用户进程能更改该文件的用户ID

(2)若满足下列条件,一个非超级用户进程就可以更改该文件的组ID:

  a.进程拥有此文件(其有效用户ID等于该文件的用户ID)

  b.参数owner等于-1或文件的用户ID,并且参数group等于进程的有效ID或进程的附加组ID之一

这意味着,当_POSIX_CHOWEN_RESTRICTED起作用时,不能更改其他用户的用户ID。你可以更改你所拥有的文件的组ID,但只能改到你所属的组

 

4.12 文件长度

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

-对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束(eof)指示

-对于目录,文件长度是一个数(例如16或512)的倍数

-对于符号链接,文件的长度是文件名中的实际字节数

文件中的空洞

当存在空洞时,磁盘总量小于文件长度,对于没有写过的字节位置,read函数读到的字节是0。

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

$cat xxx > xxx.copy

新文件的实际字节数与文件长度接近,但并不相同。其原因是文件系统使用了若干以存放指向实际数据块的各个指针。

4.13 文件截短

在文件尾端处截取一些数据以缩短文件(将一个文件清空为0是一个特例,在打开文件时使用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,则其效果与系统有关(遵循XSI的系统将增加该文件的长度。若UNIX系统实现扩展了该文件,则在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞))

4.14 文件系统

http://www.cnblogs.com/qianye/archive/2012/11/24/2786345.html

4.15 link,unlink,remove和rename函数

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

#include<unistd.h>

int link(const char *existingpath, const char *newpath);
    
                            返回值:若成功则返回0,若出错则返回-1

此函数创建一个新目录项newpath,它引用现有的文件existingpath。如若newpath存在,则返回出错。

为了删除一个现有的目录项,可以调用unlink函数

#include<unistd.h>

int unlink(const char *pathname);

                    返回值:若成功则返回0,若出错则返回-1

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

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

实例:4_5 打开一个文件,然后unlink它

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 
 4 int main()
 5 {
 6     if(open("tempfile",O_RDWR)<0)
 7         err_sys("open error");
 8     if(unlink("tempfile")<0)
 9         err_sys("unlink error");
10     printf("file unlinked
");
11     sleep(15);
12     printf("done
");
13     exit(0);
14 }

进程用open创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时,该文件的内容才会被删除。

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

remove函数可以解除对一个文件或目录的链接。(对于文件,remove的功能与unlink相同。对于目录,remove的功能与rmdir相同)

#include<stdio.h>

int remove(const char *pathname);

                        返回值:若成功则返回0,若出错则返回-1

文件或目录用rename函数更名

#include<stdio.h>

int rename(const char *oldname, const char *newname);

                        返回值:若成功则返回0,若出错则返回-1
根据oldname是文件还是目录,分为以下4种情况:(还没弄明白)
1,如果oldname指的是一个文件而不是目录,那么为该文件或符号链接更名。在这种情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录删除然后将oldname更名为newname。对于包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录;
2,若oldname指的是一个目录,那么为该目录更名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有.和..项)。如果newname已存在而且是一个空目录,则现将其删除,然后将oldname更名为newname。另外,当为一个目录更名时,newname不能包含oldname作为其路径前缀,因为旧名字是新名字的前缀而不能将其删除;
3,若oldname或newname引用符号链接,则处理的是符号链接本身,而不是它所引用的文件;
4,作为特例,若oldname和newname引用同一文件,则函数不做任何更改而成功返回。


4.16 符号链接

符号链接是指向一个文件的指针。引入符号链接的原因是为了避开硬链接的一些限制:

-硬链接通常要求链接和文件位于同一文件系统中

-只有超级用户才能创建指向目录的硬链接

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

当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是该函数是否跟随符号链接到达它所链接的文件。如若该函数具有处理符号链接的功能,则其路径名参数引用由符号链接指向的文件。否则,路径名参数将引用链接本身,而不是该链接指向的文件。(mkdir,mkinfo,mkmod和rmdir这些函数,当路径名是符号链接时,都返回出错)

附各个函数对符号链接的处理:http://blog.chinaunix.net/uid-15084954-id-190319.html

注:同时用O_CREAT和O_EXCL两者调用open函数。在此情况下,若路径名引用符号链接,open将返回出错,并将errno设置为EEXIST。这种处理方式的意图是堵塞一个安全性漏洞,使具有特权的进程不会被诱骗对不适当的文件进行写操作

实例:使用符号链接可能在文件系统中引入循环

mkdir foo
touch foo/a
ln -s ../foo foo/testdir
ls -l foo


用符号链接指定文件时,即使该文件不存在,也能建立链接,但若用open函数指定此链接,则返回出错,因为不存在该文件。

4.17 symlink 和 readlink函数

symlink函数创建一个符号链接

#include<unistd.h>

int symlink(const char *actualpath, const char *sympath);

                    返回值:若成功则返回0,若出错则返回-1

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

因为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字符终止

 

4.18 文件的时间

st_atime,st_mtime,st_ctime

修改时间是文件内容最后一次被修改的时间。更改状态时间是该文件的i节点最后一次被修改的时间。

影响i节点的操作:更改文件的访问权限,用户ID,链接数等,但它们并没有更改文件的实际内容(系统并不保存对一个i节点的最后一次访问时间,所以access和stat函数并不更改这         三个时间中的任何一个)

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

4.19 utime函数

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

#include<utime.h>

int utime(const char *pathname, const struct utimbuf *times);

                            返回值:若成功则返回0,若出错则返回-1

此函数所用的数据结构是

struct utimbuf{
        time_t actime;
        time_t modtime;
};

此结构中的两个时间值是日历时间。这是自1970年1月1日00:00:00以来国际标准时间所经过的秒数

此函数的操作以及执行它所要求的特权取决于times参数是否是null:

-如果times是一个空指针,则访问时间和修改时间两者都设置为当前时间。为了执行此操作必须满足下列条件之一:进程的有效用户ID必须等于该文件的所有者ID;或者进程对该文件必须具有写权限

-如果times是非空指针,则访问时间和修改时间被设置为times所指向结构中的值。此时,进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件只具有写权限是不够的

程序清单:4_6 utime函数实例

 1 #include"apue.h"
 2 #include<fcntl.h>
 3 #include<utime.h>
 4 
 5 int main(int argc,char *argv[])
 6 {
 7     int i,fd;
 8     struct stat statbuf;
 9     struct utimbuf    timebuf;
10     for(i=1;i<argc;i++){
11         if(stat(argv[i],&statbuf)<0){
12             err_ret("%s:stat error",argv[i]);
13             continue;
14         }
15         if((fd=open(argv[i],O_RDWR|O_TRUNC))<0){
16             err_ret("%s:open error",argv[i]);
17             continue;
18         }
19         close(fd);
20         timebuf.actime=statbuf.st_atime;
21         timebuf.modtime=statbuf.st_mtime;
22         if(utime(argv[i],&timebuf)<0){
23             err_ret("%s:utime error",argv[i]);
24             continue;
25         }
26     }
27     exit(0);
28 }

运行后,最后修改时间和最后访问时间未变。但是,更改状态时间则更改为程序运行时的时间

4.20  mkdir 和 rmdir函数

用mkdir函数创建目录,用rmdir函数删除目录

#include<sys/stat.h>

int mkdir(const char *pathname, mode_t mode);

                    返回值:若成功则返回0,若出错则返回-1

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

#include<unistd.h>

int rmdir(const char *pathname);

                    返回值:若成功则返回0,若出错则返回-1

如果调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或几个进程打开了此目录,则在此函数返回前删除最后一个链接及.和..项。另外,在此目录中不能在创建新文件。但是在最后一个进程关闭它前并不释放此目录。(即使另一个进程打开该目录,他们在此目录下也不能执行其他操作。这样处理的原因是,为了使rmdir函数成功执行,该目录必须是空的)

4.21 读目录

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

#include<dirent.h>

DIR *opendir(const char *pathname);
                    //返回值:若成功则返回指针,出错则返回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);
struct dirent{
        ino_t d_ino;    //i-node number
        char d_name[NAME_MAX+1];  //null-terminated filename
}

简单的文件遍历程序

实例:4_7 递归降序遍历目录层次结构,并按文件类型计数

  1 #include"apue.h"
  2 #include<dirent.h>
  3 #include<limits.h>
  4 
  5 /*function type that is called for each filename*/
  6 typedef int Myfunc(const char *,const struct stat *,int);
  7 static Myfunc myfunc;
  8 static int myftw(char *,Myfunc *);
  9 static int dopath(Myfunc *);
 10 
 11 static long nreg,ndir,nblk,nchr,nfifo,nslink,nsock,ntot;
 12 
 13 int main(int argc,char *argv[])
 14 {
 15     int ret;
 16     if(argc!=2)
 17         err_quit("usage: ftw <starting-pathname>");
 18     ret = myftw(argv[1],myfunc);    //does it all
 19     ntot=nreg+ndir+nblk+nchr+nfifo+nslink+nsock;
 20     
 21     if(ntot==0)
 22         ntot=1; //avoid divide by 0;print 0 for all counts
 23     printf("regular files =%7ld,%5.2f %%
",nreg,nreg*100.0/ntot);
 24     printf("directories = %7ld,%5.2f %%
",ndir,ndir*100.0/ntot);
 25     printf("block special =%7ld,%5.2f %%
",nblk,nblk*100.0/ntot);
 26     printf("char special =%7ld,%5.2f %%
",nchr,nchr*100.0/ntot);
 27     printf("FIFOs =%7ld,%5.2f %%
",nfifo,nfifo*100.0/ntot);
 28     printf("symbloic links=%7ld,%5.2f %%
",nslink,nslink*100.0/ntot);
 29     printf("sockets =%7ld,%5.2f %%
",nsock,nsock*100.0/ntot);
 30     exit(ret);
 31 }
 32 /*
 33  *Descend through the hierarchy,starting at "pathname".
 34  *The caller's func() is called for every file
 35  */
 36 #define FTW_F 1 //file other than directory
 37 #define FTW_D 2 //directory
 38 #define FTW_DNR 3 //directory that can't be read
 39 #define FTW_NS 4 //file that we can't stat
 40 
 41 static char *fullpath; //contains full pathname for every file
 42 
 43 static int myftw(char *pathname,Myfunc *func)
 44 {
 45     int len;
 46     fullpath=path_alloc(&len);    //malloc's for PATH_MAX+1 bytes
 47     strncpy(fullpath,pathname,len); //protect against
 48     fullpath[len-1]=0;    //buffer overrun
 49     return(dopath(func));
 50 }
 51 /*
 52  *Descend through the hierarchy,starting at "fullpath"
 53  *If "fullpath" is anything other than a directory,we lstat() it,
 54  *call func(),and return . For a directory,we call ourself
 55  *recursively for each name in the directory
 56  */
 57 static int dopath(Myfunc* func)   //we return whatever func() returns
 58 {
 59     struct stat statbuf;
 60     struct dirent *dirp;
 61     DIR *dp;
 62     int ret;
 63     char *ptr;
 64     if(lstat(fullpath,&statbuf)<0) //stat error
 65         return(func(fullpath,&statbuf,FTW_NS));
 66     if(S_ISDIR(statbuf.st_mode)==0) //not a directory
 67         return(func(fullpath,&statbuf,FTW_F));
 68     /*
 69      *It's a directory.First call func() for the directory
 70      *then process each filename in the directory.
 71      */
 72     if((ret=func(fullpath,&statbuf,FTW_D))!=0)
 73         return(ret);
 74     ptr=fullpath+strlen(fullpath);//point to end of fullpath
 75     *ptr++='/';
 76     *ptr=0;
 77     if((dp=opendir(fullpath))==NULL)//can't read directory
 78         return(func(fullpath,&statbuf,FTW_DNR));
 79     while((dirp=readdir(dp))!=NULL){
 80         if(strcmp(dirp->d_name,".")==0||strcmp(dirp->d_name,"..")==0)
 81             continue;//ignore dot and dot-dot
 82         strcpy(ptr,dirp->d_name);//append name after slash
 83         if((ret=dopath(func))!=0)//recursive
 84             break;
 85     }
 86     ptr[-1]=0;//erase everthing for alash onwards
 87     if(closedir(dp)<0)
 88         err_ret("can't close directory %s",fullpath);
 89     return(ret);
 90 }
 91 char* path_alloc(int*size)
 92 {
 93     char *p=NULL;
 94     if(!size)
 95     return NULL;
 96     p=malloc(256);
 97     if(p)
 98     *size=256;
 99     else
100     *size=0;
101     return p;
102 }
103 static int myfunc(const char *pathname,const struct stat *statptr,int type)
104 {
105     switch(type){
106     case FTW_F:
107         switch(statptr->st_mode &S_IFMT){
108         case S_IFREG:    nreg++; break;
109         case S_IFBLK:    nblk++; break;
110         case S_IFCHR:    nchr++; break;
111         case S_IFIFO:    nfifo++;break;
112         case S_IFLNK:    nslink++;break;
113         case S_IFSOCK:  nsock++;break;
114         case S_IFDIR:
115             err_dump("for S_IFDIR for %s",pathname);
116                 //directories should have type = FTW_D
117         }
118         break;
119     case FTW_D:
120         ndir++;
121         break;
122     case FTW_DNR:
123         err_ret("can't read directory %s",pathname);
124         break;
125     case FTW_NS:
126         err_ret("stat error for %s",pathname);
127         break;
128     default:
129         err_dump("unknown type %d for pathname %s",type,pathname);
130     }
131     return(0);
132 }
View Code


按书上给的函数编译后会告知path_alloc()未定义,解决办法:http://www.cnblogs.com/xiaoliyu/archive/2009/03/08/1406448.html

4.22 chdir,fchdir和getcwd函数

进程通过调用chdir或fchdir函数可以更改当前工作目录

#include<unistd.h>

int chdir(const char *pathname);      //$ cd

int fchdir(int fieldes);

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

在这两个函数中,分别用pathname或打开文件描述符

实例:4_8 chdir函数实例

1 #include"apue.h" 
2 int main()
3 {
4     if(chdir("/tmp")<0)
5         err_sys("chdir failed");
6     printf("chdir to /tmp succeeded
");
7     exit(0);
8 }

执行完这段程序后可以看出,当前工作目录没有改变,其原因是shell创建了一个子进程,由该子进程具体执行mycd程序。由此可见,为了改变shell进程自己的工作目录,shell应当直接调用chdir函数,为此cd命令的执行程序直接包含在shell程序中

getcwd获取当前目录路径名

#include<unistd.h>

char *getcwd(char *buf,size_t size);

                    返回值:若成功则返回buf,若出错则返回NULL

4.23 设备特殊文件

st_dev和st_rdev这两个字段经常引起混淆:

-每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为其通信的外设板;次设备号标识特定的子设备

-我们通常可以使用两个宏即major和minor来访问主、次设备号,大多数实现都定义了这两个宏。这就意味着我们无需关心这两个数是如何存放在dev_t对象中的。

-系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件以及与其对应的i节点

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

实例:4_10 打印st_dev和st_rdev值

 1 #define _BSD_SOURCE
 2 #include"apue.h"
 3 //#ifdev SOLARIS
 4 //#include<mkdev.h>
 5 //#endif
 6 
 7 int main(int argc,char *argv[])
 8 {
 9     int i;
10     struct stat buf;
11     for(i=1;i<argc;i++){
12     printf("%s: ",argv[i]);
13     if(stat(argv[i],&buf)<0){
14         err_ret("stat error");
15         continue;
16     }
17     printf("dev = %d/%d",major(buf.st_dev),minor(buf.st_dev));
18     if(S_ISCHR(buf.st_mode)||S_ISBLK(buf.st_mode)){
19     printf(" (%s) rdev=%d/%d",(S_ISCHR(buf.st_mode))?"character":"block",
20         major(buf.st_rdev),minor(buf.st_rdev));
21     }
22     printf("
");
23     }
24     exit(0);
25 }

此程序按书上编译时找不到<sys/mkdev.h>,后来直接在第一行加上一句就行

4.24 文件访问权限位小结

常量 说明 对普通文件的影响 对目录的影响
S_ISUID
S_ISGID
设置用户ID
设置组ID
执行时设置有效用户ID
若组执行位设置,则执行时设置有效组ID,否则使强制性记录锁起作用(若支持)
(不使用)
将在目录中创建的新文件的组ID设置为目录的组ID
S_ISVTX 粘住位 在交换区保存程序正文(若支持) 限制在目录中删除和更名文件
S_IRUSR
S_IWUSR
S_IXUSR
用户读
用户写
用户执行
许可用户读文件
许可用户写文件
许可用户执行文件
许可用户读目录项
许可用户在目录中删除和创建文件
许可用户在目录中搜索给定路径名
S_IRGRP
S_IWGRP
S_IXGRP
组读
组写
组执行
许可组读文件
许可组写文件
许可组执行文件
许可组读目录项
许可组在目录中删除和创建文件
许可组在目录中搜索给定路径名
S_IROTH
S_IWOTH
S_IXOTH
其他读
其他写
其他执行
许可其他读文件
许可其他写文件
许可其他执行文件
许可其他读目录项
许可其他在目录中删除和创建文件
许可其他在目录中搜索给定路径名

最后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

原文地址:https://www.cnblogs.com/wbingeek/p/3838688.html