UNIX环境高级编程(四)文件和目录

4.2 stat,fstat和lstat函数

本文讨论的中心是三个stat函数以及它们所返回的信息。

  1: #include<sys/stat.h>
  2: int stat(const char *pathname,struct stat *buf);
  3: int fstat(int filedes,struct stat *buf);
  4: int lstat(const chat *pathname,struct stat *buf);//三个函数返回值:若成功返回0,若出错返回-1

一旦给出pathname,stat函数就返回与此命名文件有关的信息结构。fstat函数获取已在文件描述符filedes上打开的文件的有关信息。lstat函数类似于stat,但是当命名文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是该符号链接引用文件的信息。

第二个参数是buf指针,它指向一个我们必须提供的结构

  1: struct stat {
  2: 	mode_t st_mode; /* file type & mode (permissions) */
  3: 	ino_t st_ino; /* i-node number (serial number) */
  4: 	dev_t st_dev; /* device number (file system) */
  5: 	dev_t st_rdev; /* device number for special files */
  6: 	nlink_t st_nlink; /* number of links */
  7: 	uid_t st_uid; /* user ID of owner */
  8: 	gid_t st_gid; /* group ID of owner */
  9: 	off_t st_size; /* size in bytes, for regular files */
 10: 	time_t st_atime; /* time of last access */
 11: 	time_t st_mtime; /* time of last modification */
 12: 	time_t st_ctime; /* time of last file status change */
 13: 	blksize_t st_blksize; /* best I/O block size */
 14: 	blkcnt_t st_blocks; /* number of disk blocks allocated */
 15: };
 16: 

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

4.3文件类型

UNIX系统的大多数文件是普通文件或者目录,但是也包括另外一些文件类型。文件类型包括以下几种:
(1)普通文件。这种文件包含某种形式的数据,至于这种数据是文本还是二进制数据对于UNIX内核而言并无区别。

(2)目录文件。这种文件包含其他文件的名字以及指向这些文件有关信息的指针。对于一个目录具有读权限的任意进程可以读该目录的内容,但只有内核可以直接写目录文件。

(3)块特殊文件

(4)字符特殊文件

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

(6)套接字

(7)符号链接

文件类型都包含在stat结构的st_mode成员中。可以用以下的宏确定文件类型

下图的宏可以用来确定IPC对象的类型,它们的参数并非st_mode,而是指向stat结构的指针

4.4设置用户ID和设置组ID

与一个进程相关的ID有6个或更多,它们表示于下图

实际用户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(st_gid)。在文件模式字中的这两位称作设置用户ID(set_user_ID)位和设置组ID位(set_group_ID).

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

4.5 文件访问权限

st_mode值也包含了针对文件的访问权限位。所有文件类型都有访问权限。

每个文件有9个访问权限位,可将它们分成三类,见下表

S_IRUSR      用户-读

S_IWUSR     用户-写

S_IXUSR      用户-执行

 

S_IRGRP      组-读

S_IWGRP     组-写

S_IXGRP       组-执行

 

S_IROTH      其他-读

S_IWOTH     其他-写

S_IXOTH      其他-执行

 

当我们用名字打开任一类型文件时,对名字中包含的每一个目录,包括它可能隐含的当前工作目录都应该具有执行权限。这就是为什么对于目录其执行权限被称作搜索位的原因

例如为了打开文件/usr/include/stdio.h,需要对目录/、/usr和、/usr/include具有执行权限。

如果当前工作目录是/usr/include,那么为了打开文件stdio.h,需要对该工作目录具有执行权限。

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

对于一个文件的读权限决定了我们是否能够打开该文件进行读操作。这与open函数的O_RDONLY和O_RDWR标志有关

对于一个文件的写权限决定了我们是否能够打开文件进行写操作。这与open函数的O_WRONLY和O_RDWR标志有关。

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

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

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

如果用exec函数执行一个文件,必须对该文件具有执行权限。该文件还必须是一个普通文件。

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,而这种测试可能涉及文件的所有者(st_uid和st_gid),进程有效ID(有效用户ID或有效组ID)以及进程的附加组ID.两个所有者ID是文件的性质,而两个有效ID和附加组ID是进程的性质

内核进行的测试是:

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

(2)若进程的有效用户ID等于文件的所有者ID(也就是该进程拥有此文件),那么:若所有者适当的访问权限被设置,则允许访问,否则拒绝访问。

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

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

按顺序执行这四部,如若进程拥有此文件,则按用户访问权限批准或拒绝该进程对文件的访问——不查看组访问权限

 

4.6新文件和目录的所有权

新文件的用户ID设置为进程的有效用户ID。关于组ID

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

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

 

4.7 access函数

当用open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。有时,进程也希望按其实际用户ID和实际组ID来测试

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

  1: #include<stdio.h>
  2: int access(const char *pathname,int mode);
  3: //若成功返回0,失败返回-1

mode是下表所列常量的按位或

R_OK  测试读权限

W_OK 测试写权限

X_OK 测试执行权限

F_OK 测试文件是否存在

 

4.8 umask函数

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

  1: #include<stdio.h>
  2: mode_t umask(mode_t cmask);
  3: //返回值:以前的文件模式创建屏蔽字

其中的cmask参数是上文提到的9个常量中的若干个按位或构成的。

在进程创建文件或者目录时就一定会使用文件模式创建屏蔽字。对于任何在文件模式创建屏蔽字中为1的位,在文件mode中的相应位则一定被关闭

更改进程的文件模式创建屏蔽字并不影响其父进程的屏蔽字。

4.9 chmod和fchmod函数

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

  1: #include<sys/stat.h>
  2: int chmod(const char *pathname,mode_t mode);
  3: int fchmod(int filedes,mode_t mode);
  4: //两个函数返回值:若成功则返回0,若出错则返回-1

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

参数mode是下表所示常量的某种按位或运算构成的(除了在上文提到的9个文件访问权限位,加了另外的6项)

S_ISUID   执行时设置用户ID

S_ISGID   执行时设置组ID

S_ISVTX   保存正文(粘住位)

S_IRWXU   用户读、写、执行

S_IRWXG  组读、写、执行

S_IRWXO  其他读、写和执行

我们会了解到chmod更新的只是 i 结点最近一次被更改的时间。按照系统默认方式,ls  -l列出的是最后修改文件内容的时间。

 

4.10粘住位

如果一个可执行程序文件的粘住位被设置了,那么在该进程第一次执行并结束时,其程序正文部分的一个副本仍被保存在交换区。这使得下次执行程序时可以快速装入内存区。其原因是:交换区占用连续磁盘空间,可将它视作连续文件,而且一个程序的正文部分在交换区中也是连续存放的,而在一般的UNIX文件系统中,文件的各数据块很可能是随机存放的

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

拥有此文件

拥有此目录

是超级用户

 

4.11 chown、fchown和lchown函数

下面几个函数可用于更改文件的用户ID和组ID

  1: #include<unistd.h>
  2: int chown(const char *pathname,uid_t owner,gid_t group);
  3: int fchown(int filedes,uid_t owner,gid_t group);
  4: int lchown(const char *pathname,uid_t owner,gid_t group);
  5: //返回值:若成功返回0,若出错返回-1
  6: 
  7: 

在符号链接的情况下,lchown更改符号链接本身的所有者,而不是符号链接所指向的文件。

若果两个参数owner或group中的任意一个是-1,则对应的ID不变

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

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

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

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

 

4.12 文件长度

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

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

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

现今大多数UNIX系统提供阻断st_blksize和st_blocks。其中我们将st_blksize用于读操作时,读一个文件的所需时间量最少。

 

文件中的空洞

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

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

4.13 文件截短

有时我们需要在文件尾端截去一些数据以缩短文件。将文件清空为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点

  1: #include<unistd.h>
  2: int truncate(const char *pathname,off_t length);
  3: int ftruncate(int filedes,off_t length);
  4: //两个函数返回值:若成功返回0,出错返回-1

两个函数把现有的文件长度截短为length字节。如果文件以前的长度小于length,一些系统将增加该文件的长度。在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

 

4.14 文件系统

先介绍UNIX文件系统的基本结构。 我们可以把一个磁盘分成多个分区。每个分区包含一个文件系统见下图




i 节点是固定长度的记录项,它包含有关文件的大部分信息。

如果更仔细观察一个柱面组的 i 节点和数据块部分,则可以看到下图结构4-2


(1)在图中有两个目录项指向同一 i 节点。每一个 i 节点中有一个链接计数,其值是指向该 i 节点的目录项数。只有当链接计数减少至0时,才可删除该文件(也就是可以释放该文件占用的数据块)。这就是为什么”解除对一个文件的链接“操作并不总是意味着”释放该文件占用的磁盘块“的原因

(2)另外一种链接类型称为符号链接。对于这种链接,该文件的实际内容(在数据块中)包含了该符号链接所指向文件的名字。文件类型是S_IFLNK

(3) i 节点包含了大多数与文件有关的信息:文件类型、文件访问权限、文件长度和指向该文件所占用的数据块的指针等等。stat结构的大部分信息都取自 i 节点。只有两项数据存放在目录项中:文件名和 i 节点编号。

(4)每个文件系统各自对它们的 i 节点进行编号,因此目录项中的i节点标号数指向同一文件系统中的相应i节点,不能使一个目录项指向另一个文件系统的i节点。这就是为什么ln命令(构造一个指向现有文件的目录项)不能跨越文件系统的原因。

 

对于目录文件的链接计数问题,可以参考下图

我么构造了一个新目录:
mkdir  testdir


对于编号为2549的i节点,其类型字段表示它是一个目录,而链接计数为2。任何一个叶目录(不包含任何其他目录的目录)的链接计数总是2,

父目录中的每一个子目录都会使该目录的链接计数增1.

4.15 link、unlink、remove和rename函数

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

  1: #include<unistd.h>
  2: int link(const char *existpathname,const char *newpathname);
  3: //返回值:若成功则返回0,若出错则返回-1

此函数创建一个新目录项newpathname,它引用现有的文件existpathname。

如果newpathname已经存在,则返回出错。只创建newpathname的最后一个分量,路径中的其他部分应当已经存在。

创建新目录项和增加链接计数应当是一个原子操作。

如果实现支持创建指向一个目录的硬链接,那么也仅限于超级用户才可以这么做。

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

  1: #include<unistd.h>
  2: int unlink(const char *pathname);
  3: //返回值:若成功返回0,出错返回-1

此函数删除目录项,并将由pathname参数引用的文件链接计数减一。如果还有指向该文件的其他链接,则仍然可以通过其他链接访问该文件数据。

为了解除对文件的链接,必须对包含该目录项的目录具有写权限和执行权限。如果对该目录设置了粘住位,则对该目录必须有写权限,并且满足下面三个条件之一:

拥有该文件

拥有该目录

具有超级用户权限

关闭一个文件时,内核首相检查打开该文件的进程数,如果数达到0,然后检查文件的链接计数,如果这个也是0,那么就删除该文件的内容。

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

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

 

我们也可以用remove函数解除对一个文件或目录的链接。

  1: #include<stdio.h>
  2: int remove(const char *pathname);
  3: //返回值:若成功返回0,若出错返回-1

 

4.16 符号链接

符号链接是指向一个文件的间接指针。

硬链接有一些限制:
硬链接通常要求链接和文件位于同一个文件系统中

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

对符号链接以及它指向何种对象无任何文件系统限制,任何用户都可以创建指向目录的符号链接

符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置。

当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是函数是否跟随符号链接到达它链接的文件。

unlink不跟随符号链接。

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

4.17 symlink和readlink函数

symlink函数创建一个符号链接

  1: #include<unistd.h>
  2: int symlink(const char *actualpath,const char *sympath); 
  3: //返回值:若成功返回0,出错返回-1

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

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

  1: #include<unistd.h>
  2: int readlink(const char *pathname,char *buf,size_t bufsize);
  3: //返回值:若成功则返回读到的字节数,若出错返回-1

4.18 文件时间

每个文件保持有三个时间字段,如下:
st_atime       文件数据最后访问时间            例如read

st_mtime       文件数据最后修改时间            write

st_ctime         i节点状态最后更改时间            chmod,chown

注意修改时间(st_mtime)和更改状态时间(st_ctime)之间的区别

修改时间是文件内容最后一次被修改的时间。更改状态的时间是该文件的i节点最后一次被修改的时间。(例如:更改文件的访问权限、用户ID、链接数等但是它们并没有更改文件的实际内容。)

access和stat函数并不更改上述三个时间

4.19 utime函数

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

  1: #include<utime.h>
  2: int utime(const char *pathname,const struct utimbuf *times);
  3: //返回值:若成功返回0,若出错返回-1

此函数使用的数据结构是:

  1: struct utimbuf{
  2: time_t actime;//access time;
  3: time_t modtime;//modification time;
  4: }

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

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

当调用utime函数时,st_ctime字段被被自动更新

4.20 mkdir和rmdir函数

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

  1: #include<sys/stat.h>
  2: int mkdir(const char *pathname,mode_t mode);
  3: //返回值:若成功则返回0,若出错则返回-1

此函数创建一个新的空目录。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

对于目录,至少要设置一个执行权限位,以允许访问该目录中的文件名

rmdir可以删除一个空目录。空目录只包含.和..这两项

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

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

4.21 读目录

对某个目录具有访问权限的任一用户都可以读该目录,但是只有内核才可以写目录。一个目录的写权限和执行权限决定了在目录中能否创建文件或者删除文件,它们并不表示能否写目录本身

 

4.22 chdir、fchdir和getcwd函数

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

当前工作目录是进程的一个属性

进程通过调用chdir和fchdir可以更改当前工作目录。chdir跟随符号链接

  1: #include<unistd.h>
  2: int chdir(const char *pathname);
  3: int fchdir(int filedes);
  4: //两个函数返回值:若成功返回0,若出错返回-1

因为当前工作目录是进程的一个属性,所以chdir只影响调用函数的进程本身,而不影响其他进程。

cd命令的执行程序直接包含在shell进程中。

我们需要一个函数,逐层上移,直到遇到根,这样就得到了当前工作目录完整的绝对路径名。函数getcwd提供了这种功能

  1: #include<unistd.h>
  2: char *getcwd(char *buf,size_t size);
  3: //返回值:若成功返回buf,若出错返回NULL
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值