第四章 文件和目录
〖BFQ〗第四章〓文件和目录?
4?1〓引言?
在上一章我们说明了执行I/O操作的基本函数。其讨论是围绕普通文件的I/O进行的
-打开-
文件,读或写一个文件。本章将观察文件系统的其它特征和文件的性质。我们从s
tat函数开
始,并逐个说明stat结构的每一个成员以了解文件的所有属性。在此过程中,我们
的说明修
改这些属性的各个函数(更改属主,更改许可数等)。我们也将更详细地察看Unix文
件系统的
结构以及符号连接。本章结束部分介绍对目录进行操作的各个函数,并且开发了一
个以降序
遍历目录层次结构的函数。?
4?2〓stat,fstat以及lstat函数?
本章的讨论的中心是三个stat函数以及它们所返回的信息。?
#include?
#include?
int stat(const char *?pathname?,struct stat *?buf?);?
int fstat(int ?filedes,?struct stat *?buf?);?
int lstat(const char *?pathname,?struct stat *?buf?);?
三个函数的返回:若成功为0,出错为-1?
给予一个pathname,stat函数返回一个与此命名文件有关的信息结构,fstat函数获
得已在插
述符filedes上打开的文件的有关信息。lstat函数类似于stat,但是当命名的文件
是一个符
号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的
信息。(在
4?2)节中当降序周游目录层次结构时,我们需要lstat。在4?16节中的较详细地
说明符号
连接。)?
lstat函数不属于POSIX 1003?1-1990标准,但很可能加到1003?1a中。SVR4和4?
3+BSD支
持lstat。?
第二个参数是个指针,它指向一个我们应提供的结构。这些函数填写由buf指向的
结构。该
结构的实际定义可能所实施而有所不同,但其基本形式是:?
struct stat{?
mode 迹茫模*常病絫 st 迹茫模*常病絤ode; /*文件类型和方式(许可数)*/?
ino 迹茫模*常病絫 st 迹茫模*常病絠no;/* i-节点号(序列号)*/?
dev 迹茫模*常病絫 st 迹茫模*常病絛ev;/*设备号(文件系统)*/?
dev 迹茫模*常病絫 st 迹茫模*常病絩dev;/*特殊文件的设备号*/?
nlink 迹茫模*常病絫 st 迹茫模*常病絥link;/*连接数*/?
uid 迹茫模*常病絫 st 迹茫模*常病絬id;/*属主的用户ID*/?
gid 迹茫模*常病絫 st 迹茫模*常病絞id;/*属主的组ID*/?
off 迹茫模*常病絫 st 迹茫模*常病絪ize;/*普通文件的字节长度*/?
time 迹茫模*常病絫 st 迹茫模*常病絘time;/*最后存取时间*/?
time 迹茫模*常病絫 st 迹茫模*常病絤time;/*最后修改存取时间*/?
time 迹茫模*常病絫 st 迹茫模*常病絚time;/*最后文件状态更改时间*/?
long st 迹茫模*常病絙lksize;/*最佳I/O块长*/?
long st 迹茫模*常病絙locks;/*分配的512字节块块数?
};?
POSIX?1未定义st 迹茫模*常病絩dev?st 迹茫模*常病絙lksige和st 迹茫模?nbsp;
?2〗blo
cks字段。SVR4和4?3+BSD则定义了这些字段。?
注意,除最后两个以外,其它各成员都说明为基本系统数据类型(见2?7节)。我们
将说明此
结构的每个成员以了解文件属性。?
stat函数的最大用户很可能是ls-l命令,用其可以获得有关一个文件的所有信息。
?
4?3〓文件类型?
至今我们已介绍了两种不同的文件类型-普通文件和目录。Unix系统的大多数文件
是普通文
件或目录,但是也有另外一些文件类型:?
1?普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式
的数据。
至于这种数据是文本还是二进制数据对于系统核而言并无区别。对普通文件内容的
解释由处
理该文件的应用程序进行。?
2?目录文件(Directory file)。这种文件包含了其它文件的名字以及指向与这些
文件有关
信息的指针。对一个目录文件具有读许可数的任一进程都可以读该目录的内容,但
只有系统
核可以写目录文件。?
3?字符特殊文件(Charocter special file)。这种文件用于系统中的某些类型的
设备。?
4?块特殊文件(Block special file)。这种文件典型地用于磁盘设备。系统中的
所有设备
或者是字符特殊文件,或者是块特殊文件。?
5? FIFO。这种文件用于进程间的通信,有时也将其称为命名管道。在14?5对其
进行说明
。?
6?套接口(socket)。这种文件用于进程间的网络通信。套接口也可用于在一台宿
主机上的
进程之间的非网络通信。在第十五章,我们将用套接口进行进程间的通信。?
只有4?3+BSD才返回套接口文件类型,虽然SVR4支持用套接口进行进程间通信,但
现在是经
由套接口函数库实现的,而不是通过系统核内的套接口文件类型,将来的SVR4版本
可能会支
持套接口文件类型。?
7?符号连接(Symbolic link)。这种文件指向另一个文件。我们在4?16中将更多
地述及符
号连接。?
文件类型信息,包含在stat结构的st 迹茫模*常病絤ode成员中。我们可以用图4
?1中的宏
确室文件类型。这些宏的参数都是stat结构中的st 迹茫模*常病絤ode成员。??
?
图4?1〓在中的文件类型宏?
实例?
程序4?1取其命令行参数,然后针对每一个命令行参数打印其文件类型。???
程序4?1〓对每个命令行参数打印文件类型?
程序4?1的样本输出是:?
$ a?out/vmunix/etc/dev/ttya/dev/sd0a/var/spool/cron/FIFO\?
>/bin/dev/printer?
/vmunix:普通?
/etc:目录?
/dev/ttya:字符特殊?
/dev/sd0a:块?
/var/spool/cron/FIFO:fifo?
/bin:symbolic符号连接?
/dev/printer:套接口?
(其中,在第一命令行末端我们键入了一个反斜线,通知shell我们要在下一行继续
键入命令
,然后shell在下一行上用其第二提示符,>,提示我们特地使用了lstat函数而不是
stat函数
以便检测符号连接。如若使用了stat函数,则决不会观察到符号连接。?
较早的Unix版本并不提供S 迹茫模*常病絀SXXX宏,于是就需要将st 迹茫模*常?nbsp;
〗mode与
屏蔽字S 迹茫模*常病絀FMT逻辑与,然后与
名为s 迹茫模*常病絀FXXX的常数相比较。SVR4和4?3+BSD在文件
中定义了
此屏蔽字和相关的常数。如若我们查看此文件,则可找到S 迹茫模*常病絀SDIR宏
定义为:
?
我们说过,普通文件是最主要的文件类型,但是观察一下在一个给定的系统中各种
文件的比
例是很有兴趣的。图4?2中显示了在一个中等规模的系统中的统计值。这一数据是
由4?21
节中的程序得到的。???
图4?2〓不同类型文件的计数值和比例?
4?4〓设置一用户 迹茫模*常病絀D和设置一组 迹茫模*常病絀D?
与一个进程相关联的ID有六个或更多。它们示于图4?3中。???
图4?3〓与每个进程相关联的用户ID和组ID?
·实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自我们在口令
文件中的
记录项。通常,在一个登录会话期这些值并不改变,但是超级用户进程有方法,改
变它们,
在8?10节中将说明这些方法。?
·有效用户ID,有效组ID以及添加组ID决定了我们的文件存取数,下一节将对此进
行说明。
(我们已在1?8节中说明了添加组ID)。?
·保存的设置一用户 迹茫模*常病絀D和保存的设置一组 迹茫模*常病絀D在执行
一个程序
时包含了有效用户I
D和有效组ID的副本,在8?10节中说明setuid函数时,我们将说明这两个保存值的
作用。?
在POSIX?1中,这些保存ID是可选择的。一个应用程序在编译时可测试常数 CD
*常病?nbsp;
POSIX 迹茫模*常病絊AVED 迹茫模*常病絀
DS,或在运行时以参数 迹茫模*常病絊C 迹茫模*常病絊AVED 迹茫模*常病絀D
S调用函数
sysconf,以判断此实现是否支持这种特征。SVR4支持此特征。?
FIPS 。 5|-1要求POSIX?1的这种可选择特征。?
通常,有效用户ID等于实际用户ID,以及有效组ID等于实际用户ID。?
每个文件有一个属主和组属主,属主是由stat结构中的st 迹茫模*常病絬id表示
的,组属
主则由st 迹茫模*常病絞id成员表示。?
当我们执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通
常是实际
组ID。但是可以在文件方式字(st 迹茫模*常病絤ode)中设置一个特殊标志,其定
义是"当
执行此文件时
,将进程的有效用户ID设置为文件的属主(st 迹茫模*常病絬id)"。与此相类似,
在文件
方式字中可以设
置另一位,它使得执行此文件的进程的有效组ID设置为文件的组属主(st 迹茫模?nbsp;
?2〗gid
)。在文件方式字中的这两位被称之为设置一用户 迹茫模*常病絀D位和设置一组
迹茫模?nbsp;
?2〗ID位。?
例如,若文件属主是超级用户,而且设置了该文件的设置一用户 迹茫模*常病絀
D位,然后
当该程序由一
个进程运行时,则该进程具有超级用户优先数。不管执行此文件的进程的实际用户
ID是什么
,都作这种处理。作为一个例子,Unix程序passwd(1)允许任一用户改变其口令字
,该程序
是一个设置一用户 迹茫模*常病絀D程序。因为该程序应能将用户的新口令字写入
口令字文
件中(典型地
这是/etc/passwd或/etc/shadow),而只有超级用户才具有该文件的写许可数,所以
需要使用
设置一用户 迹茫模*常病絀D特征。因为运行设置一用户 迹茫模*常病絀D程序的
进程通常
得到额外的许
可数,所以要特别谨慎地编写这种程序。我们将在第八章更详细地讨论这种类型的
程序。?
再返回到stat函数,设置一用户 迹茫模*常病絀D位及设置一组 迹茫模*常病絀
D位都包含
在st 迹茫模*常病絤ode值中。这两位可用常数S 迹茫模*常病絀SUID和S CD
*常病絀
SGID测试。?
4?5〓文件存取许可数?
st 迹茫模*常病絤ode值也包含了对文件的存取数位。当我们说及文件时,我们指
的是前面
所提到的任何
类型的文件。所有文件类型(目录,字符特别文件等)都有许可数。很多人认为只有
普通文件
有存取许可数,这是一种误解。?
每个文件有9个存取数位,可将它们分成三类。这些都示于图4?4中。???
图4?4〓9个存取数位(在中定义)?
在图4?4开头三行中,术语用户指的是文件属主。chmod(1)命令用于修改这9个许
可数位。
该命令允许我们用u表示用户(属主),用g表示组,用o表示其他。有些书把这三种
用户夫妇
别称之为属主,组和世界。这会造成混乱,因为chmod命令用o表示其他,而不是属
主(owner
)。我们将使用术语用户,组和其他,以便与chmod命令一致。?
图中的三类存取数-读、写及执行-以各种方式由不同的函数使用。我们将这些不同
的使用
方法摘要列在下面,当说明这些函数时,再进一步作讨论。?
第一个规则是,我们用名字要打开任一类型的文件时,对该名字中包含的每一个目
录,包括
它可能隐含的当前工作目录都应具有执行许可数。这就是为什么对于目录其执行许
可数位常
被称为搜索位的原因。?
例如,为了打开文件/usr/dict/words,我们需要具有对目录/,/usr,/usr/dict的
执行许可
数。然后,我们需要对该文件本身的适当许可数,这取决于我们要以何种方式打开
它(只读,读-写等)。
如果当前目录是/usr/dic,那么为了打开文件words,我们需要有对该目录的执行许
可数。这
就是隐含了当前目录的例子,我们在指定打开文件words时,没有显式地提及/usr
/dic?wor
ds与/words两种表示方法是一致的。?
注意,对于目录的读许可数和执行许可数的意义是不相同的。读许可数允许我们读
目录,获
得在该目录中所有文件名的列表。当一个目录是我们要存取文件的路径名的一个分
量时,对
该目录的执行许可数使我们可通过该目录。(也就是搜索该目录,寻找一个特定的
文件名。
)?
引用隐含目录的另一个例子是,如果PATH环境变量(在8?4节中说明)指定了一个我
们不具有
存取数的目录,那么shell决不会在该目录下打到可执行文件。?
·对于一个文件的读许可数决定了我们是否能够打开该文件进行读操作。这对应于
open函数
的O 迹茫模*常病絉DONLY和O 迹茫模*常病絉DWR标志。?
·对于一个文件的写许可数决定了我们是否可能够打开该文件进行写操作这对应于
open函数
的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。?
·对于一个文件的写许可数决定了我们是否能够打开该文件进行写操作。这对应于
open函数
的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。?
·为了在open函数中对一个文件指定O 迹茫模*常病絋RUNC标志,我们必须对该文
件具有写
操作许可数。?
·为了在一个目录中创建一个新文件,我们对该目录需要具有写许可数和执行许可
数。?
·为了删除一个文件,我们需要对包含该文件的目录具有写许可数和执行许可数。
对该文件
本身则不需要有读、写许可数。?
·如果我们用6个exec函数(8?9节)中的任何一个执行某个文件,则我们需要对该
文件具有
执行许可数。?
进程每次打开、创建或删除一个文件时,系统核就进行文件存取数测试,而这种测
试可能涉
及文件的属主(st 迹茫模*常病絬id和st 迹茫模*常病絞id)。进程的有效ID(有
效用户ID
和有效组ID)、以及进程的添加
组ID(若支持的话)。两个属主ID是文件的性质,而有效ID和添加组ID则是进程的性
质。系统
核进行的测试是:?
1?若进程的有效用户ID是O(超级用户),则允许存取。这给于了超级用户对文件系
统进行处
理的最充分的自由。?
2?若进程的有效用户ID等于文件的属主ID(也就是该进程拥有此文件):?
a?若适当的属主用户存取数位是设置的,则允许存许,?
b?否则拒绝存取。?
关于确当的存取数位,我们指的是,如若进程为读而打开该文件,是属主用户-读
位应为1
。若进程为写而打开该文件,则属主用户-写位必须为1。若进程将执行该文件,则
属主用
户-执行位必须为1。?
3?若进程的有效组ID或进程的添加组ID之一等于文件的组ID:?
a?若适当的组存取数位是设置的,则允许存取,?
b?否则拒绝存取。?
4?若适当的其他用户存取数位是设置的,则允许存取,否则拒绝存取。?
按序试执行这四步。注意,如若进程拥有此文件(第2步),则按用户存取数批准或
拒绝该进
程对文件的存取-不查看组存取数。相类似,若进程并不拥有该文件。但进程属于
某个适当
的组,则按组存取数批准拒绝该进程对文件的存取-不查看其它用户存取数。?
4?6〓新文件和目录的属主关系?
在第三章中,当说明用open或creat创建新文件时,我们没有说明赋与新文件的用
户ID和组I
D的值是什么。在4?20中,我们将说明如何创建一个新目录以及mkdir函数。关于
新目录的
属主关系的规则与本节将说明的新文件的属主关系的规则相同。?
新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX?1允许选择下列之一
作为新文
件的组ID。?
1?新文件的组ID可以是进程的有效组ID。?
2?新文件的组ID可以是它所在目录的组ID。?
在SVR4中,新文件的组ID决取于它所在的目录的设置一组 迹茫模*常病絀D位是否
设置。如
果该目录的这
一位已经设置,则新文件的组ID设置为目录的组ID;否则新文件 组ID设置为进程
的有效组I
D。?
4?3+BSD总是使用目录的组ID作为新文件的组ID。?
其它系统允许以一个文件系统作为单位在POSIX?1所允许的两种方法中选择一种,
为此在mo
unt(1)命令中使用了一个特殊标志。?
FIPS |5|-1要求一个新文件的组ID是它所在目录的组ID。?
使用POSIX?1所允许的第二种方法(继承目录的组ID)使得在某个目录下创建的文件
和目录都
有该目录的组ID。于是文件和目录的组属主关系从该点就向下传递。例如,在/va
r/spcol目
录中就使用这种方法。?
正如前面提到的,这种设置组属主关系的方法对4?3+BSD是系统默认的,对SVR4则
是可选择
的。在SVR4之下,我们必须设置设置一组 迹茫模*常病絀D位。更进一步,为供这
种方法能
够正常工作,
SVR4的mkdir函数要自动地传递一个目录的设置一组 迹茫模*常病絀D位。(在4?
20节中我
们将说明,mkdir就是这样做的)。?
4?7〓access函数?
正如前面所说明的,当用open函数打开一个文件时,系统核以进程的有效用户ID和
有效组ID
为基础执行其存取数限测试。有时,进程也希望按其实际用户ID和实际组ID来测试
其存取能
力。例如当一个进程使用设置一用户 迹茫模*常病絀D,或设置一组 迹茫模*常?nbsp;
〗ID特征
作为另一个用户(或组)运行
时,这可能就是需要的。即使一个进程可能已经设置一用户 迹茫模*常病絀D为根
,它仍可
能想验证实际
用户能否存取一个给定的文件。access函数是按实际用户ID和实际组ID进行存取数
测试的。
(经过4?5节结束部分中所述的4个步骤,但将有效改为实际。)?
#include?
int access(const char *?pathname,?int ?mode?);?
返回:若成功为0,出错为-1?
其中,mode是图4?5中所列常数的按位或。???
图4?5?
实例?
程序4?2显示了access函数的使用。下面是该程序的一些运动结果:?
$ ls -1 a?out?
-rwxrwxr-x 1 stevens 105216 Jan 18 08:48 a?out?
$ a?out a?out?
read access OK?
open for reading OK?
$ ls -1/etc/uucp/Systems?
-rw-r----- 1 uucp 1441 Jul 18 15:05/etc/uucp/Systems?
$ a?out/etc/uucp/Systems?
access error for/etc/uucp/Systems:Permission denied?
open error for /etc/uucp/Systems:Permission denied?
$ su〓成为超级用户?
Password:输入超级用户口令?
# chown uucp a?out?
# chkmod u+s a?out将文件用户ID改为uucp,打开设置用户ID位???
程序4?2〓access函数的实例。?
在本例中,设置一用户 迹茫模*常病絀D程序可以确定实际用户不能读某个文件,
而open函
数却能打开该文件。?
在上面例子以及在第八章中,我们有时要成为超级用户,以便例示某些功能是如何
工作的。
如果你使用多用户系统,但无超级用户许可数,那么你就不能完整地重复这些实例
。?
4?8〓umask函数?
至此我们已说明了与每个文件相关联的9个存取数位,在此基础上我们可以说明与
每个进程
相关联的文件方式创建屏蔽字。?
umask函数为进程设置文件方式创建屏蔽字,并返回以前的值。9这是少数几个没有
出错返回
的函数中的一个。)?
#include?
#include?
mode 迹茫模*常病絫 umask(mode 迹茫模*常病絫 ?cmask)?;?
返回:以前的文件方式创建 帘为?nbsp;
其中,参数cmask是由图4?4中的9个常数(S 迹茫模*常病絀RUSR,S 迹茫模*常?nbsp;
〗IWUSR等
)按位或构成的。?
在进程创建一个新文件或一个新目录时,就一定会使用文件方式创建屏蔽字。(回
忆3?3和3
?4节,在那里我们说明了open和creat函数。这两个函数都有一个参数mode,它指
定了新文
件的存取许可数位。)我们将在4?20节说明如何创建一个新目录,在文件方式创建
屏蔽字中
为1的位,在文件mode中的相应位则一定被转成0。?
实例?
程序4?3创建了两个文件,创建第一个时,umask值为0,创建第二个时,umask值
禁止所有
组和其它存取数。若运行此程序可得如下结果,从中可见存取数是如何设置的。?
$ umask〓第一次打印当前文件方式创建 帘为?nbsp;
02?
$ a?out-
4 ls -1 foo bar?
-rw------- 1 stevens 0 Nov 16 16:23 bar?
-rw -rw-rw- 1 stevens 0 Nov 16 16:23 foo?
$ umask〓观察文件方式创建屏蔽是否更改?
02???
程序4?3〓umask函数的实例?
4?9〓chmod和fchmod函数?
这两个函数使我们可以更改现存文件的存取许可数。?
#include?
#include?
int chmod(const char *?pathname,?mode 迹茫模*常病絫 ?mode);??
int fchmod(int ?filedes,?mode 迹茫模*常病絫 ?mode);??
二个函数返回:若成功为0,出错为-1?
chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。
?
fchmod函数并不是POSIX?1的组成部分。这是SVR4和4?3+BSD的扩充部分。?
为了改变一个文件的许可数位,进程的有效用户ID必须等于文件的属主,或者该进
程必须具
有超级用户许可数。?
参数mode是图4?6中所示常?哪持职次换颉*おお?nbsp;
图4?6〓chmod函数的mode常数(取自)?
注意,在图4?6中,有9项是取自图4?4中的9个文件存取许可数位。我们另外加上
了二位设
置 迹茫模*常病絀D常数(S 迹茫模*常病絀S〔UG〕ID),保存 迹茫模*常病 正文
常数(S〖
茫模*常病絀SVTX),以及三个组合常数(S 迹茫模*常病絀RWX〔UGO〕)
。(在这里,我们使用了标准Unix字符类算符 病? ,表示方括号算符中的任何一
个字符。
例如,最后一个,S 迹茫模*常病絀RWX〔UGO〕表示了三个常数:S 迹茫模*常?nbsp;
〗IRWXU、
S 迹茫模*常病絀RWXG和S 迹茫模*常病絀RWXO。这一字符
类算符是大多数Unix shell和很多标准Unix应用程序都提供的正规表达式的一种形
式。)?
保存 迹茫模*常病 正文位(S 迹茫模*常病絀SVTX)不是POSIX?1的一部分。我们
在下一节
说明其目的。?
实例?
先回忆一下为例示umask函数我们运行程序4?3时,文件foo和bar的最后状态:?
$ ls-1 foo bar?
-rw------- 1 stevens 0 Nov 16 16:23 bar?
-rw-rw-rw- 1 stenens 0 Nov 16 16:23 foo?
程序4?4修改了这两个文件的方式。在运行程序4?4后,我们见到的这两个文件的
最后状态
是:?
$ ls -1 foo bar?
-rw-r--r-- 1 stevens 0 Nov 16 16:23 bar?
-rw-rwlrw- 1 stenens 0 Nov 16 16:23 foo?
在此例子中,我们相对于foo的当前状态设置其许可数。为此,先调用stat获得其
当前许可
数,然后修改它。我们已显式地打开了设置一组 迹茫模*常病絀D位、关闭了组〖
茫模*?nbsp;
2〗执行位。对普通文件这样
做的结果是对该文件可以加强制性记录锁,我们将在12?3节中讨论强制性锁。注
意,ls命
令将组 迹茫模*常病 执行许可数表示为l,它表示对该文件可以加强制性记录锁
。对文件b
ar,不管其当前许可数位如何,我们将其许可数设置为一绝对值。???
程序4?4〓chmod函数的实例?
最后也要注意到。在我们运行程序4?4后ls命令列出的时间和日期并不改变。在4
?18节中
,我们会了解到chmod函数更新的只是i-node最近一次被更改的时间。按系统默认
方式ls-l
列出的是最后修改文件内容的时间。?
chmod函数在下列条件下自动清除2个许可数位。?
·如果我们试图设置普通文件的粘住位(S 迹茫模*常病絀SVTX),而且又没有超级
用户优先
数,那么mode
中的粘住位自动被关闭。(我们将在下一节说明粘住位)。这意味着只有超级用户才
能设置普
通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以
此方式填
满交换区(如果系统支持保存 迹茫模*常病 正文特征的话)。?
·新创建文件的组ID可能不是调用进程所属的组。回忆一下4?6节,新文件的组I
D可能是父
目录的组ID。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组I
D中的一个
,以及进程没有超级用户优先数,那么设置一组 迹茫模*常病絀D位自动被关闭。
这就防止
了用户创建一个设置一组 迹茫模*常病絀D文件,而该文件是由并非该用户所属的
组拥有的
。?
4?3+BSD和其它贝克莱导出的系统增加了另外的安全性特征以试图防止保获位的错
误使用。
如果一个没有超级用户优先数的进程写一个文件,则设置一用户 迹茫模*常病絀
D位和设置
一组 迹茫模*常病絀D位自动
被清除。如果一个不怀好意的用户找到一个他可以写的设置一组 迹茫模*常病絀
D和设置一
用户 迹茫模*常病絀D文件,即使他可以修改此文件,但失去了对该文件的特别优
先数。?
4?10〓粘住位?
S 迹茫模*常病絀SVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为
粘住位。
如果一个可执
行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的
一个文本
被保存在交换区。(程序的正文部分是机器指令部分。)这使得下次执行该程序时能
较快地将
其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的Unix文
件系统中
,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序
和编辑程
序的各部分常设置它们所在文件的粘住位。自然,对交换区中可以同时存放的设置
了粘住位
的文件数有一定限制,以免过多占用交换区空间,但无论如何这是一个有用的技术
。因为在
系统再次自草前,文件的正文部分总是在交换区中,所以使用了名字"粘住"。后来
的Unix
版本称之为保存 迹茫模*常病 正文位,因此也就有了常数S 迹茫模*常病絀SVT
X。现今较
新的Unix系统大多数都具有虚存系统,以及快速文件系统,所以可再需要使用这种
技术。?
目前粘住位的主要作用是针对目录文件的。如果对一个目录设置了粘住位,则只有
对该目录
文件具有写许可数的用户并且满足下列条件之一,才能删除或换名该目录下的文件
:?
·拥有此文件?
·拥有此目录,或者?
·是超级用户?
目录/tmp和/var/spool/uucppublic是设置粘住位的后选者-这两个目录是任何用户
都可在
其中创建文件的目录。这两个目录对任一用户(用户、组和其他)的许可数通常都是
读、写和
执行。但是用户不应能删除或换名属于其他人的文件,为此在这两个目录的文件方
式中都设
置了粘住位。?
POSIX?1没有定义粘住位。但SVR4和4?3+BSD则支持这种特征。?
4?11〓chown,fchown和lchown函数?
chown函数可用于更改文件的用户ID和组ID。?
#include?
#include?
int chown(const char *?pathname,?uid 迹茫模*常病絫 ?owner?,gid〖C
模*常?nbsp;
〗t? group);??
int fchown(int ?filedes,?uid 迹茫模*常病絫 ?owner,?gid 迹茫模*常?nbsp;
〗t ?gro
up);??
int lchown(const char *?pathname,?uid 迹茫模*常病絫 ?owner,?gid〖C
模*常?nbsp;
〗t ?group);??
三个函数的返回:若成功为0,出错为-1?
除了所引用的文件是个符号连接以外,这三个函数的操作相类似。在符号连接情况
下,lcho
wn更改符号连接本身的属主,而不是该符号连接所指向文件的属主。?
fchown函数并不在POSIX 1003?1-1990标准中,但很可能被加到1003?1a,SVR4和
4?3+BS
D则支持fchown。?
只有SVR4支持lchown函数。在非SVR4系统中(POSIX?1和4?3+BSD),若chown的参
数pathnam
e是符号连接,则改变该符号连接的属主关系,而不改变它所指向的文件的属主关
系。为了
更改该符号连接所指向的文件的属主关系,我们应指定该实际文件本身的pathnam
e,而不是
指向该文件的连接文件的pathname。?
SVR4,4?3+BSD和XPG3允许我们将参数owner或group指定为-1,以表示不改变相应
的ID。这
不是POSIX?1的一部分。?
基于贝克莱的系统一直规定只有超级用户才能更改一个文件的属主。这样做的原因
是防止用
户改变其文件的属主从而摆脱盘空间限额对他们的限制。但是,系统V则允许任一
用户更改
他们所拥有的文件的属主。?
按照 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED的值,
POSIX?1
在这两种形式的操作中选用一种。FIPS | 担?迹茫模*常病?要求 迹茫模*常?nbsp;
〗POSIX
迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED。?
对于SVR4,此功能是个配置可选择项,而4?3+BSD则总对chown施加了限制。?
回忆图2?5,该常数可选地定义在头文件中,而且总是可以用pathco
nf或fpath
conf函数查询。此可选项还与所引用的文件有关-可在每个文件系统基础上,使该
任选项起
作用或不起作用。在下文中,我们如提及"若 迹茫模*常病絇OSIX 迹茫模*常病?nbsp;
CHOWN〖
茫模*常病絉ESTRICTED起作用",则表示
这适用于我们正在谈及的文件,而不管该实际常数是否在头文件中定义。(例如,
4?3+BSD
总有这种限制,而并不在头文件中定义此常数。)?
若 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED对指定的
文件起作
用,则?
1?只有超级用户进程能更改该文件的用户ID。?
2?若满足下列条件,一个非超级用户进程可以更改该文件的组ID:?
a?进程拥有此文件(其有效用户ID等于该文件的用户ID),以及?
b?参数owner等于文件的用户 迹茫模*常病絀D,参数group等于进程的有效组ID
或进程的
添加组ID之一。?
这意味着,当 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRIC
TED有效时
,你不能更改其
他用户的文件的用户ID。你可以更入你所拥用的文件的组ID,但只能改到你所属于
的组。?
如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置一用户〖C
模*常?nbsp;
〗ID位和设置一组 迹茫模*常病絀D位都被清除。?
4?12〓文件长度?
stat结构的成员st 迹茫模*常病絪ige包含了以字节为单位的该文件的长度。此字
段只对普
通文件、目录文件和符号连接才是有意义的。?
SVR4对管道也定义了文件长度,它表示可从该管道中读到的字节数,我们将在14?
2中讨论
管道。?
对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。?
对于目录,文件长度通常是一个数,例如16或512的整倍数,我们将在4?21节中说
明读目录
操作。?
对于符号连接,文件长度是在文件名中的实际字节数。例如,?
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib->usr/lib?
其中,文件长度7就是路径名usr/lib的长度。(注意,因为符号连接文件长度总是
由st〖C
模*常病絪ige指示,所以符号连接并不包含通常C语言用作名字结尾的null字符
。)?
SVR4和4?3+BSD也提供字段st 迹茫模*常病絙lksige和st 迹茫模*常病絙locks
。第一个
是对文件I/O较好的块长度,第
二个是所分配的实际512字节块块数。回忆一下3?9节,其中提到了当我们将st〖
茫模*?nbsp;
2〗blksige用
于读操作时,读一个文件所需的最少时间量。为了效率的缘故,标准I/O库(我们将
在第五章
中说明)也试图一次读、写st 迹茫模*常病絙lksige字节。?
要知道,不同的Unix版本其st-blocks所用的单位可能不是512-字节块。使用此值
并不是可
移植的。?
文件中的空洞?
在3?6节中,我们提及普通文件可以包含"空洞"。在程序3?2中例示了这一点。空
洞是由
超过文件结尾端的位移量设置,并写了某些数据后造成的。作为一个例子,考虑下
列情况:
?
$ ls -1 core?
-rw-r--r-- 1 stevens 8483248 Nov 18 12:18 core?
$ du -s core?
272 core?
文件core的长度超过8兆字节,而du命令则报告该文件所使用的盘空间总量是272个
512字节
块(139,264字节)。(在很多贝克莱类的系统上,du命令报告1024字节块块数;SV
R4则报告5
12-字节块块数。)很明显,此文件有很多空洞。?
正如我们在3?6节中提及的,read函数对于没有写过的字节位置读到的数据字节是
0。如果
我们执行:?
$ wc -c core?
8483248 core?
从此可见,正常的I/O操作读至整个文件长度。带-c选择项的(wc(1)命令计算文件
中的字符(
字节)数。)?
如果我们使用公用程序,例如cat,复制这种文件,那么所有这些空洞都被写成实际
数据字节
0。?
$ cat core>core?copy?
$ ls -1 core*?
-rw-r--r-- 1 stevens 8483248 Nov 18 12:18 core?
-rw-rw-r-- 1 stevens 8483248 Nov 18 12:27 core?copy?
$ du -s core*?
272 core?
16592 core?copy?
从中可见,新文件所用的字节数是8,495,104(512×16,592)。此长度与
ls命令报
告的长度之间的差别是由于文件系统使用了若干块以保持指向实际数据块的各指针
。?
有兴趣的读者应当参阅Bach〔19 福丁 的4?2节和Leffler〔1989〕的7?2节,
以更详细
地了解文件的物理安排。?
4?13〓文件截短?
有时我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截短为0
是一个特
例,在open一个文件时指定O 迹茫模*常病絋RUNC标志就可以做到这一点。为了截
短文件可
以使用系统调用函数truncate和ftruncate。?
#include?
#include?
int truncate(const char *?pathname,?off 迹茫模*常病絫 ?length);??
int ftruncate(int ?filedes,?off 迹茫模*常病絫 ?length);??
两个函数的返回;若成功为0,出错为-1?
这两个函数将由路径名pathname或打开文件描述符filedes指定的一个现存文件的
长度截短
为length。如果该文件以前的长度大于Length,则超过Length以外的数据就不再能
存取。如
果以前的长度短于Length,则其后果与系统有关。如果某个实现的处理是扩展该文
件,则在
以前的文件尾端和新的文件尾端之间的数据将读作为0(也就是在文件中创建了一个
空洞)。
?
SVR4和4?3+BSD提供了这两个函数。它们不是POSIX?1或XPG3的组成部分。?
SVR4截短或扩展一个文件。4?3+BSD只用这三个函数截短一个文件-不能用它们扩
展一个文
件。?
Unix从来就没有截短文件的一种标准方法。完全兼容的应用程序必须对文件制作一
个副本,
在制作它时只复制所希望的数据字节。?
SVR4的fcntl中有一个POSIX?1没有规定的命令F 迹茫模*常病紽REESP,它允许释
放一个文
件中的任何一部分,而不只是文件尾端处的一部分。?
在程序12?5中,我们使用了ftruncate函数,以便在获得对该文件的锁后,使一个
文件变完
。?
4?14〓文件系统?
为了说明文件连接的概念,先要对文件系统的结构有基本了解。同时,了解i〖C
模*常?nbsp;
〗node和指向一个i 迹茫模*常病絥ode的目录项之间的区别也是很有益的。?
现在,有很多Unix文件系统的实现。例如,SVR4支持两种不同类型的盘文件系统:
传统的Un
ix系统V文件系统(称为55),以及统一文件系统(称为UFS)。在图2?6中,我们已看
到了这两
种文件系统的一个区别。UFS是以贝克莱快速文件系统为基础的。SVR4也支持另外
一些非磁
盘文件系统,两个分布式文件系统,以及一个自举文件系统,这些文件系统都不影
响下面的
讨论。本节讨论传统的Unix系统V文件系统。这种类型的文件系统可以回溯到Vers
ion7。?
我们可以把一个盘驱分成一个或多个分区。如图4?7中所示,每个分区可以包含一
个文件系
统。???
图4?7〓盘驱,分区和文件系统?
i 迹茫模*常病絥ode是固定长度的记录项,它包含有关文件的信息。?
在Version 7中,一个i 迹茫模*常病絥ode占用64字节,在4?3+BSD中,一个i〖
茫模*?nbsp;
2〗node占用128字节。在SVR4
中,在磁盘上一个i 迹茫模*常病絥ode的长度与文件系统的类型有关:一个S5 i
迹茫模?nbsp;
?2〗node占用64字节,而UFS i 迹茫模*常病絥ode占用128字节。?
如果在忽略自举块和超级块情况下,更细仔地观察文件系统,则可以得到图4?8中
所示的情
况。???
图4?8〓较详细的文件系统?
注意图4?8中的下列各点:?
·在图中有两个目录项指向同一i 迹茫模*常病絥ode。每个i 迹茫模*常病絥od
e中都有一
个连接计数,其值是指向该i 迹茫模*常病?nbsp;
node的目录项数。只有当连接计数减少为0时,才可删除该文件(也就是可以释放该
文件占用
的数据块)。这就是为什么"解除对一个文件的连接"操作并不总是意味着"释放该文
件占
用的盘块"的原因。这也就是为什么删除一个目录项的函数被称之为unlink而不是
删除的原
因。在stat结构中,连接计数包含在st 迹茫模*常病絥link成员中,其基本系统
数据类型
是nlonk 迹茫模*常病絫。这
种连接类型称之为硬连接。回忆图2?7,其中,POSIX?1常数LINK 迹茫模*常病?nbsp;
MAX指定
了一个文件连接数的最大值。?
·另外一种连接类型称之为符号连接。对于这种连接,该文件的实际内容(在数据
块中)包含
了该符号连接所指向的文件的名字。在下列例子中:?
lrwxrwxrwx 1 root 7 sep 25 07:14 lib->urs/lib?
在该目录项中的文件名是lib,而在该文件中包含了7个数据字节usr/lib。在该i〖
茫模*?nbsp;
2〗node中的文件类型是S 迹茫模*常病絀FLNK,于是系统知道这是一个符号连接
。?
·i 迹茫模*常病絥ode包含了所有与文件有关的信息:文件类型,文件存取许可
数位,文
件长度,指向该
文件所占用的数据块的指针等等。stat结构中的大多数信息都取自i 迹茫模*常?nbsp;
〗node。
只有
二项数据存放在目录项中:文件名和i 迹茫模*常病絥ode编号数。i 迹茫模*常?nbsp;
〗node编
号数的数据类型是ino 迹茫模*常病絫。?
·因为在目录项中的i 迹茫模*常病絥ode编号数指向同一文件系统中的一个i〖C
模*常?nbsp;
〗node,所以我们不能使一个
目录项指向另一个文件系统的i 迹茫模*常病絥ode。这就是为什么ln(1)命令(构造一
个指向一个现存文件的新目录项),不能跨越文件系统的原因。我们将在下一节说明
link函数。?
·当不更改文件系统情况下为一个文件改换名字时,该文件的实际内容并未移动
,需要做的
是构造一个指向现存i 迹茫模*常病絥ode的新目录项,并除去老的目录项。例如
,为将文
件/usr/lib/fo
o换名为/usr/foo,如果目录/usr/lib和/usr在同一文件系统上,则文件foo的内容
无需移动
。这就是mv(1)命令的通常操作方式。?
我们说明了普通文件的连接计数的概念,但是对于目录文件的连接计数字段又如何
呢?假定
我们在新目录中构造了一个新目录:?
$ mkdir testdir?
图4?9显示了其结果。注意,在该图中,我们显式地显示了?和??目录项。??
?
图4?9〓在创建了目录testdir后的样本文件系统?
编号2549的i 迹茫模*常病絥ode其类型字段表示它是一个目录,而连接计数2。任
何一个叶
目录(不包含任
何其它目录,也就是子目录的目录)其连接计数总是2,数值2来自于命名该目录的
目录项(te
stdir)以及在该目录中的?项。编号为1267的i 迹茫模*常病絥ode,其类型字段
表示它是
一个目录,而其
连接计数则大于或等于3。它大于或等于3的原因是,至少有由三个目录项指向它:
一个是命
名它的目录项(在图4?9中段有表示出来),第二个是在该目录中的?项,第三个是
在其子目
录testdir中的??项。注意,在工作目录中的每个子目录都使该工作目录的连接
计数增1。
?
正如前面所述,这是Unix文件系统的经典格式,在Bach〔1986〕一市的第四章中对
此作了说
明。关于贝克莱快速文件系统对此所作的更改请参阅Leffler etal〔1989〕中
的第七章
。?
4?15〓link,unlink,remove和rename函数?
如上节所所述,任何一个文件可以有多个目录项指向其i 迹茫模*常病絥ode。创
建一个向
现存文件的连接的方法是使用link函数。?
#include?
int link(const char *?existingpath,?const char *?newpath);??
返回:若成功为0,出错为-1?
此函数创建一个新目录项,newpath,它引用现存文件existingpath。如若newpath
已经存在
,则出错返回。?
创建新目录项以及增加连接计数应当是个原子操作。(请回忆在3?11节中对原子操
作的讨论
。)?
大多数实现,例如SVR4和4?3+BSD要求这两个路径名在同一个文件系统中。?
POSIX?1允许支持跨越文件系统的连接的实现。?
只有超级用户进程可以创建指向一个目录的新连接。其理由是这样做可能在文件系
统中形成
循环,大多数处理文件系统的公用程序都不能处理这种情况。(我们在4?16节中将
说明一个
由符号连接引入的循环的例子。)?
为了移去一个现存的目录项,我们调用unlink函数。?
#include ?
int unlink(const char *?pathname);??
返回:若成功为0,出错为-1?
此函数移去目录项,并将由pathname所引用的文件的连接计数减1。如果该文件还
有其它连
接,则仍可通过其它连接存取该文件的数据。如果出错,则该文件作任何更改。?
我们在前面已经提及,为了解除一个对文件的连接,我们必须对包含该目录项的目
录具有写
和执行许可数。正如我们在4?10节中所述,如果对该目录设置了粘住位,则我们
对该目录
必须具有写许可数,并且?
·拥有该文件,或者?
·拥有该目录,或者?
·具有超级用户优先数?
只有当连接计数达到0时,该文件的内容再可被删除。另一个条件也阻止删除文件
的内容-
只要有一个进程使该文件打开,其内容也不能删除。当关闭一个文件时,系统核首
先检查使
该文件打开的进程计数。如果该计数达到0,然后系统核检查其连接计数,如果这
也是0,那
么就删除该文件的内容。?
实例?
程序4?5打开一个文件,然后unlink它。执行该程序的进程然后睡眠15秒钟,接着
就终止。
???
程序4?5〓打开一个文件,然后unlink它运行该程序,其结果是:?
$ ls -1 tempfile〓查看文件大小?
-rw-r--r-- 1 stevens 9240990 Jul 31 13:42 tempfile?
$ df/home〓检查空间区?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 181979 72638 71% /home?
$ a?out &〓在后台运行程序4?5?
1364〓shell打印其进程ID?
$ file unlinked〓该文件是未连接的?
ls -1 tempfile〓观察文件是否仍旧存在?
tempfile not found〓目录项已删除?
$ df/home〓检查空间区有无变化?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 181979 72638 71% /home?
$ done〓程序执行结束,所有打开文件皆关闭,相应盘空间成为空间?
df/home?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 172939 81678 68% /home?
现在,盘空间区增加了9?2Mbytes?
unlink的这种特性常由程序用来确保即便程序崩溃它所创建的临时文件也不会遗留
下来。进
程用open或creat创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的
,所以不
会将其内容删除。只有当进程关闭该文件或终止时(在这种情况下,系统核关闭该
进程所打
开的全部文件),该文件的内容再被删除。?
如若pathname是一符号连接,那么unlink涉及的是符号连接而不是由该连接所引用
的文件。
?
超级用户可以调用其参数pathname指定一个目录的unlink,但是通常不使用这种方
式,而应
当使用函数rmdir我们将在4?20节中说明rmdir函数。?
我们也可以用remove函数解除对一个文件或目录的连接。对于文件,remove的功能
与unlink
相同。对于目录,remove的功能与rmdir相同。?
#include?
int remove(const char *?pathname);??
返回:若成功为0,出错为-1?
ANSI C指定remove函数删除一个文件。这更改了Unix历来使用的名字unlink,其原
因是实现C
标准的大多数非Unix系统并不支持文件连接。?
文件或目录用rename函数换名。?
#include?
int rename(const char *?oldname,?const char *?newname);??
返回:若成功为0,出错为-1?
ANSI C 对文件定义了此函数。(C标准不处理目录。)POSIX?1扩展此定义包含了目
录。?
依赖于oldname是指文件还是目录,有两种情况要加以说明。我们也应说明如果ne
wname已经存在将会发生什么。?
1?如若oldname说明一个文件而不是目录,那么为该文件换名。在这种情况下,
如果newnam
e已存在,则它不能引用一个目录。如若newname已存在,而且不是一个目录,则先
将该目录
项删除然后将oldname换名为newname。对包含oldname的目录以及包含newname的目
录,调用
进程必须具有写许可数,因为将更改这两个目录。?
2?如若oldname说明一个目录,那么为该目录换名。如果newname已存在,则它必
须引用一
个目录,而且该目录应当是空目录。(我们提及空目录时,指的是该目录中只有?
和??项
。)如果newname存在(而且是一个空目录),则先将其删除,然后将oldname换名为
newname。
另外,当我们为一个目录换名时,newname不能包含oldname作为其路径前缀。例如
,我们不
能将/usr/foo换名为/usr/foo/testdir,因为老名字(/usr/foo)是新名字的路径前
缀,因而
不能将其移去。?
3?作为一个特例,如果oldname和newname引用同一文件,则函数并不作任何更改
而成功返
回。?
如若newname已经存在,则调用进程需要对其有写许可数(如同删除情况一样)。另
外,选用
进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含old
name的目
录及包含newname的目录具有写和执行许可数。?
4?16〓符号连接?
一个符号连接是对一个文件的间接指针,它与上一节所述的硬连接有所不同,硬连
接直接指
向文件的i 迹茫模*常病絥ode。引进符号连接的原因是为了避免硬连接的一些限
制:(a)硬
连接通常要求
连接和文件位于同一文件系统中,(b)只有超级用户才能创建一个到一个目录的硬
连接。对
符号连接以及它指向什么没有文件系统限制,任何用户都可创建指向目录的符号连
接。符号
连接典型地用于将一个文件或整个目录结构移到系统中的某个其它位置。?
符号连接由4?2BSD引进,后来又得到SVR4的支持。在SVR4中,传统的系统V文件系
统(S5)和
统一文件系统(UFS)都支持符号连接。?
POSIX 1003?1-1990标准并不包括符号连接。但很可能会加到1003?1a中。?
当使用以名字引用一个文件的函数时,我们应当了解该函数是否处理符号连接功能
。也就是
是否跟随符号连接到达它所连接的文件。如若该函数处理符号连接功能,则该函数
的路径名
参数引用由符号连接指向的文件。否则,一个路径名参数引用连接本身,而不是由
该连接指
向的文件。图4?10摘要列出了本章中所说明的各个函数是否处理符号连接功能。
因为rmdir
并不是针对符号连接进行定义的(宏path是符号连接则出错返回),所以在图4?10
中没有列
出这一函数。因为对符号连接的处理是由返回文件描述符的函数进行的(通常是op
en),所以
以文件描述符作为参数的函数(fstat,fchmod等)也未列出。chown是否跟随符号连
接取决于
实现-各种有关细节请参阅4?11节。???
图4?10〓各个函数对符号连接的处理?
实例?
使用符号连接可能在文件系统中引进循环。大多数查找路径名的函数在这种情况发
生时都返
回值为ELOOP的errno考虑下列命令序列:?
$ mkdir foo〓构造-新目录?
$ touch foo/a〓创建O长文件?
$ln -s ??/foo foo/testdir 创建-符号连接?
$ ls -1 foo?
total 1?
-rw-rw-r-- 1 stevnens 0 Dec 6 06:06 a?
lrwxrwxrwx 1 stenens 6 Dec 6 06:06 testdir -> ??/foo?
这创建了一个目录foo,它包含了一个名为a的文件以及一个符号连接,它指向foo
。在图4?
11中显示了这种结果,图中以园表示目录,以正方形表示一个文件。如若我们写一
段简单的
程序,它使用标准函数ftw(3)以降序周游文件结构,打印每个遇到的路径名,则其
输出是:
?
foo?
foo/a?
foo/testdir?
foo/testdir/a?
foo/testdir/testdir?
foo/testdir/testdir/a?
foo/testdir/testdir/testdir?
foo/testdir/testdir/testdir/a?
(many more lines)?
ftw returned -1:Too many levels of symbolic links?
在4?21节中,提供了我们自己的ftw函数版本,它用lstat代替stat以阻止它跟随
符号连接
。???
图4?11〓创建一个循环的符号连接testdir?
这样一个连接很容易被删除-因为unlink并不跟随符号连接,所以我们可以unlink
文件foo/
testdir。但是如果我们创建了一个构成这种循环的硬连接,那么就很难去除它?
?。这就
是为什么link函数不允许构造指向目录的硬连接的原因。(除非进程具有超级用户
优先数。)
?
在open一个文件时,如若传递给open函数的路径名指定了一个符号连接,那么ope
n跟随此连
接到指定的文件。若此符号连接所指向的文件并不存在,则open出错返回,表示它
不能打开
该文件。这可能会使不熟悉符号连接的用户感到迷惑,例如:?
$ ln -s /no/such/file myfile〓创建-符号连接?
$ ls myfile?
myfile〓ls查到该文件?
$ cat myfile〓试图观看该文件?
cat:myfile:No such file or directory?
$ ls -1 myfile〓试-l选项?
lrwxrwxrwx 1 stevens 13 Dec 6 07:27 myfile->/no/such/file?
文件myfile是存在的,但cat都称没有这一文件。其原因是myfile是个符号连接,
由该符号
连接所指向的文件并不存在。ls命令的-l选择项给与我们两个提示:第一个字符是
l,它表
示这是一个符号连接;而->也表示这是一个符号连接。ls命令还有另一个选择项(
-F),它在
是符号连接的文件名后加一个@符号,在未使用-l选择项时,这可以帮助识别出符
号连接。
?
4?17〓symlink和readlink函数?
symlink函数创建一个符号连接。?
#include?
int symlink(const char *?actualpath,?const char *?sympath);??
返回:若成功为0,出错为-1?
该函数创建了一个指向actualpath的新目录项sympath,在创建此符号连接时,并
不要求act
ualpath已经存在。(在上一节结束部分的例子中我们已经看到了这一点。)同时,
?
?场迹耍仟常病 在编写本节时,作者在自己的系统?nbsp;作为一个实验做了这一点。
文件系统
变得错误百出,正常的fsck(1)公共程序不能解决问题。为了修复此文件系统不得
不使用了
并不推荐使用的工具clri(8)和dcheck(8)。actualpath和sympath并不必须位于同
一文件系
统中。?
因为open函数跟随符号连接,所以我们需要有一种方法打开该连接本身,并读在该
连接中的
名字。readlink函数提供这种功能。?
#include ?
int readlink(const char *?pathname,?char *?buf?,int ?bufsize);??
返回:若成功为读的字节数,出错为-1?
此函数组合了open,read和close的所有操作。?
如果此函数成功,则它返回读入buf的字节数。在buf中返回的符号连接的内容不以
null字符
终止。?
4?18〓文件的时间?
对每个文件保持有三个时间字段。它们的意义示于图4?12中中。???
图4?12〓与每个文件相关的三个时间值?
注意修改时间(st 迹茫模*常病絤time)和更改状态时间(st 迹茫模*常病絚time
)之间的区
别。修改时间是文件内容最后
一次被修改的时间。更改状态时间是该文件的i 迹茫模*常病絥ode最后一次被修
改的时间
。在本章中我们
已说明了很多操作,它们影响到i 迹茫模*常病絥ode,但并没有更改文件的实际内
容:更改
文件的存取数
、更改用户ID、更改连接数等等。因为在i 迹茫模*常病絥ode中的所有信息都是
与文件的
实际内容分布存放的,所以,除了文件数据修改时间以外,需要更改状态时间。?
注意,系统并不保持有对一个i 迹茫模*常病絥ode的最后一次存取时间。所以ac
cess和sta
t函数并不更改这三个时间中的任一个。?
系统管理员常常使用存取时间来删除在一定的时间范围内没有存取过的文件。典型
的例子是
删除在过去一周内没有存取过的名为a?out或core的文件。find(1)命令常被用来
进行这种
操作。?
修改时间和更改状态时间可被用来归档其内容已经被修改或其i 迹茫模*常病絥o
de已经被
更改的那些文件。?
ls命令按这三个时间值中的一个排序进行显示。按系统默认(用-l或-t选择项调用
时),它按
文件的修改时间的先后排序显示。-u选择项使其用存取时间排序,-c选择项则使其
用更改状
态时间排序。?
用4?13摘要列出了我们已说明过的各种函数对这三个时间的作用。回忆4?14节中
所述,目
录是包含目录项(文件名和相关的i 迹茫模*常病絥ode编号)的文件,增加、删除
或修改目
录项会影响到与
其所在目录相关的三个时间。这就是在图4?13中包含而列的原因,其中一列是与
该文件(或
目录)相关的三个时间,另一列是与所引用的文件(或目录)的父目录相关的三个时
间。例如
,创建一个新文件影响到包含此新文件的目录,也影响该新文件的i 迹茫模*常?nbsp;
〗node。
但是,读或写一
个文件只影响该文件的i 迹茫模*常病絥ode,而对父目录则无影响。(mkdir和rm
dir函数在
4?20节中说明
。utime函数在下一节中说明。6个exec函数在4?20节中讨论。在第十四章说明mk
fifo和pip
c函数。)???
图4?13〓各种函数对存取、修改和更改状态时间的作用?
4?19〓utime函数?
一个文件的存取和修改时间可以用utime函数更改。?
#include?
#include?
int utime(const char *?pathname,?const struct utimbuf *?times);??
返回:若成功为0,出错为-1?
此函数所使用的结构是:?
struct untimbuf{?
time 迹茫模*常病絫 actime; /*存取时间*/?
time 迹茫模*常病絫 modtime;/*修改时间*/?
}?
在此结构中的两个时间值是日历时间。如同1?10节中所述,这是1970?1?1,00
:00:00
以来国际标准时所经过的秒数。?
此函数的操作以及执行它所要求的优先数取决于times参数是否是NULL。?
1?如若times是一个空指针,则存取时间和修改时间两者都设置为当前时间。为了
执行此操
作必须满足下列两条件之一:(a)进程的有效用户ID必须等于该文件的属主ID;(b
)进程对该
文件必须具有写许可数。?
2?如若times是非空指针,则存取时间和修改时间被设置为times所指向的结构中
的值。此
时,进程的有效用户ID必须等于该文件的属主ID,或者进程必须是一个超级用户进
程。对文
件只具有写许可数是不够的。?
注意,我们不能对更改状态时间st 迹茫模*常病絚time指定一个值,当调用utim
e函数时,
此字段被自动更新。?
在某些Unix版本中,touch(1)命令使用此函数。另外,标准归档程序tar(1)和cpi
o(1)可选
地调用utime,以便将一个文件的时间值设置为将它归档时的值。?
实例?
程序4?6使用带O 迹茫模*常病絋RUNC选择项的open函数将文件长度截短为0,但
并不更改
其存取时间及修
改时间。为了做到这一点,首先用stat函数得到这些时间,然后截短文件,最后再
用utime
函数复置这两个时间。???
程序4?6〓utime函数的实例?
以下列方式运行程序4?6:?
$ ls -1 changemod times〓观察长度和最后修改时间?
-rwxrwxr-x 1 stevens 24676 Dec 4 16:13 changemod?
-rwxrwxr-x 1 stevens 24676 Dec 6 09:24 times?
$ ls -lu changemod times〓观察最后存取时间?
-rwxrwxr-x 1 stevens 24676 Feb 1 12:44 changemod?
-rwxrwxr-x 1 stevens 24676 Feb 1 12:44 times?
$ date〓打印今天日期?
Sun Feb 3 18:22:33 MST 1991?
$ a?out changemod times〓运行程序4?6?
$ ls -1 changemod times〓检查结果?
-rwxrwxr-x 1 stevens 0 Dec 4 16:13 changemod?
-rwxrwxr-x 1 stevens 0 Dec 6 09:24 times?
$ ls -lu changemod times〓检查最后存取时间?
-rwxrwxr-x 1 stevens 0 Feb 1 12:44 changemod?
-rwxrwxr-x 1 stevens 0 Feb 1 12:44 times?
$ ls -lc changemod times〓更改状态时间?
-rwxrwxr-x 1 stevens 0 Feb 3 18:23 changemod?
-rwxrwxr-x 1 stevens 0 Feb 3 18:23 times?
正如我们所予料的一样,最后修改时间和最后存取时间未变。但是,更改状态时间
则更改为
程序运行时的时间。(这两个文件的最后存取时间相同的原因是,这是它们的目录
用tar命令
归档时的时间。)?
4?20〓mkdir和rmdir函数?
用mkdir函数创建目录,用rmdir函数删除目录。?
#include?
#include?
int mkdir(const char *?pathname,?mode 迹茫模*常病絫 ?mode?);?
返回:若成功为0,出错为-1?
此函数创建一个新的空目录。?和??目录项是自动创建的。所指定的文件存取许
可数mode
,由进程的文件方式创建屏蔽字修改。?
常见的错误是指定与文件相同的mode(只指定读、写许可权)。但是,对于目录我们
常至少要
设置1个执行许可权位,以允许存取该目录中的文件名。(见练习4?18。)?
按照4?6节中讨论的规则,设置新目录的用户ID和组ID。?
SVR4也使新目录继承父目录的设置一组 迹茫模*常病絀D位。这就使得在新目录中
创建的文
件将继承该目录的组ID。?
4?3+BSD并不要求继承此设置一组 迹茫模*常病絀D位,因为不论设置一组 CD
*常病?nbsp;
ID位如何,新创建的文件和目录总是继承父目录的组 迹茫模*常病絀D。?
早期的Unix版本并没有mkdir函数,它是由4?2BSD和SVR3引进的。在早期版本中,
进程要调
用mknod函数以创建一个新目录。但是只有超级用户进程才能使用mknod函数。为了
避免这一
点,创建目录的命令mkdir(1)必须由根(root)拥有,而且打开了其设置一用户〖C
模*常?nbsp;
〗ID位。进程为了创建一个目录,必须用system(3)函数调用mkdir命令(1)。?
用rmdir函数可以删除一个空目录。?
#include?
int rmdir(const char *?pathname);??
返回:若成功为0,出错为-1?
如果此调用使目录的连接计数成为0,并且也没有其它进程打开此目录,则释放由
此目录占
用的空间。如果在连接计数达到0时,有一个或几个进程打开了此目录,则在此函
数返回前
删除最后一个连接,删除?和??项。另外,在此目录中不能再创建新文件。但是
在最后一
个
进程关闭它之前并不释放此目录。(即使某些进程打开该目录,它们在此目录下,
因为为使r
mdir函数成功执行,该目录必须是空的。)?
4?21〓读目录?
对某个目录具有读取取数的任一用户都可读该目录。但是只有系统核才能写目录(
防止文件
系统发生混乱)。回忆4?5节,一个目录的写存取数限位和执行数限位决定了在该
目录中能
否创建新文件以及删除文件,它们并不表示能否写目录本身。?
目录的实际格式依赖于Unix的具体实现。早期的系统,例如Version 7,有一个比
较简单的
结构:每个目录项是16个字节,其中14个字节是文件名?个字节是i 迹茫模*常?nbsp;
〗node编
号数。而对于4
?2BSD而言,由于它允许相当长的文件名,所以每个目录项的长度是可变的。这就
意味着读
目录的程序与系统相关。为了简化这种情况UNIX现在包含了一套与读目录有关的例
程,它们
是POSIX?1的一部分。?
#include?
#include?
DIR *opendir(const char *?pathname);??
返回:若成功为指针,出错为NULL?
struct dirent *readdir(DIR *?dp);??
返回:若成功为指针,在目录尾或出错为NULL?
void rewinddir(DIR *?dp?);?
int closedir(DIR *?dp?);?
返回:若成功为0,出错为-1?
回忆一下,我们在程序1?1中(ls命令的基本实现部分)使用了这些函数。?
定义在头文件中的dirent结构是与实现有关的。SVR4和4?3+BSD定义
此结构至
少包含下列两个成员:?
struct dirent {?
ino 迹茫模*常病絫 d 迹茫模*常病絠no;/*i-节点号*/?
char d 迹茫模*常病絥ame〔NAME 迹茫模*常病組AX+1〕;/*以null符终止的文件
名*/?
}?
POSIX?1并没有定义d 迹茫模*常病絠no,因为这是一个实现特征。POSIX?1在此
结构中只
定义d 迹茫模*常病絥ame项。?
注意,SVR4没有的NAME 迹茫模*常病組AX定义为一个常数-其值依赖于该目录所在
的文件
系统,并且通常
可用fpathconf函数取得。在BSD类文件系统中,NAME 迹茫模*常病組AX的常用值
是255。(
见图2?7。)但
是,因为文件名是以null字符结束的,所以在头文件中如何定义数组d-name并无多
大关系。
?
DIR结构是一个内部结构,它由这四个函数用来保存正被读的目录的有关信息。其
作用类似
于FILE结构。FILE结构由标准I/O库维护(我们将在第五章中说明)。?
由opendir返回的指向DIR结构的指针由另外三个函数使用。opendir执行初始化操
作,使第
一个readdir读目录中的第一个目录项。在目录中各目录项的顺序是与实现有关的
。它们通
常并不是按字母顺序排列的。?
实例?
我们将使用这些目录例程编写一个周游文件层次结构的程序。其目的是得到如同我
们在图4
?2中所示的各种类型的文件数。程序4?7只有一个参数,它说明起点路径名,从
该点开始
递归降序周游文件层次结构。系统V提供了一个实际周游此层次结构的函数ftw(3)
,对于每
一个文件它都调用一个用户定义函数。此函数的问题是:对于每一个文件,它都调
用stat函
数,这就使程序跟随符号连接。例如,如果从root开始,并且有一个名为/lib的符
号连接,
它指向/usr/lib,则所有在目录/usr/lib中的文件都两次计数。为了纠正这一点,
SVR4提供
了另一个函数nftw(3),它具有一个停止跟随符号连接的可选择项。尽管可以使用
nftw,但
是为了说明目录例程的使用方法,我们还是编写了一个简单的文件周游程序。??
?
程序4?7〓递归降序周游目录层次结构,并按文件类型计数?
在程序中,我们提供了比所要求的更多的通用性。所以这样做是为了例示实际ftw
函数的应
用情况。例如,函数myfunc总是返回0,但是调用它的函数却准备处理非0返回。?
关于降序周游文件系统的更多信息,以及在很多标准Unix命令(find,ls,tar等)中
使用这种
技术的情况,请参阅Fowler,Korn及Vo〔1989〕。4?3+BSD提供了一新套的目录周
游函数-
请参阅fts(3)手册页。?
4?22〓Chdir,fchdir和getcwd函数?
每个进程有一个当前工作目录,此目录是搜索所有相对路径名的起点(小以斜线开
始的路径
名为相对路径名)。当用户登录到Unix系统时,其当前工作目录通常是口令字文件
(/etc/pas
swd)中该用户记录项的第6个字段-用户的起始目录。当前工作目录是进程的一个属
性;起
始目录则是登录名的一个属性。进程调用chdir或fchdir函数可以更改当前工作目
录。?
#include?
int chdir(const char *?pathname);??
int fchdir(int ?filedes);??
两个函数的返回:若成功为0,出错为-1?
在这两个函数中,我们可以分别用pathname或打开文件描述符来指定新的当前工作
目录。?
fchdir不是POSIX?1的所属部分,SVR4和4?3+BSD则支持此函数。?
实例?
因为当前工作目录是一个进程的属性,所以它只影响调用chdir的进程本身,而不
影响其它
进程。(我们将在第八章较详细地说明进程之间的关系。)这就意味着程序4?8并不
会产生我
们希望得到的后果。如果编译程序4?8,并且调用其可执行目标代码文件,则可以
得到下列
结果:?
$ pwd?
/usr/lib?
$ mycd?
chdir to/tmp succeeded?
$ pwd?
/usr/lib?
从中可以看出,执行myccl程序的shell的当前工作目录并没有改变。由此可见,s
hell应当
直接调用chdir函数,所以cd命令的执行程序直接包含在shell程序中。?
因为系统核保持有当前工作目录的知识,所以我们应能取其当前值。不幸的是,系
统核为每
个进程只保存其当前工作目录的i 迹茫模*常病絥ode编号以及设备标识,系统核
并不保存
该目录的完整路径名。???
程序4?8〓chdir函数的实例?
我们需要一个函数,它从当前工作目录开始,找到其上一级的目录,然后读其目录
项,直到
该目录项中的i 迹茫模*常病絥ode编号数与工作目录i 迹茫模*常病絥ode编号数
相同,这
样地就找到了其对应的文件。
按照这种方法,逐层上移,直到遇到根(root),这样就得到了当前工作目录的绝对
路径名。
很幸运,现存函数getcwd就是提供这种功能的。?
#include?
char *getcwd(char *?buf?,size 迹茫模*常病絫 ?size?);?
返回:若成功为buf,出错为NULL?
向此函数传递两个参数,一个是缓存地址buf,另一个是缓存的长度size。该缓存
必须有足
够的长度以容纳绝对路径名再加上一个null终止字符,否则出错返回。(请回忆2?
5?7节中
有关为最大长度路径名分配空间的讨论。)?
某些getcwd实现允许第一个参数buf为NULL。在这种情况下,此函数调用malloc动
态地分配s
ize字节数的空间。这不是POSIX?1或XPG3的所属部分,应予避免。?
实例?
程序4?9将工作目录更改至一个特定的目录,然后调用getcwd,最后打印该工作目
录。如果
运行该程序,则可得:?
$ a?out?
cwd=/var/spool/uucppublic?
$ ls -1/usr/spool?
lrwxrwxrwx 1 root 12 Jan 31 07:57/usr/spool->??/var/spool???
程序4?9〓getcwd函数的实例?
注意,chdir跟随符号连接(正如在图4?10中所示那样),但是当getcwd沿目录树上
溯遇到/v
ar/spool目录时,它并不了解该目录由符号连接/usr/spool所指向。这是符号连接
的一种特
性。?
4?23〓特殊设备文件?
st 迹茫模*常病絛ev和st 迹茫模*常病絩dev这两个字段经常引起混淆,当在11
?9节编写
ttyname函数时,我们需要使用这两个字段。有关规则是很简单的:?
·每个文件系统都由其主、次设备号而为人所知。设备号所用的数据类型是基本系
统数据类
型dev 迹茫模*常病絫。回忆图4?7,一个盘驱经常包含若干个文件系统。?
·我们通常可以使用两个大多数实现所定义的宏:major和minor来取得主、次设备
号。这就
意味着我们无需关心这两个数是如何存放在一个dev?t对象中的。?
早期的系统用16位整型存放设备号,8位用于主设备与,8号用于次设备号。SVR4使
用32位;
14位用于主设备号,18位用于次设备号。4?3+BSD则使用16位:8位用于主设备号
,8位用于
次设备号。?
POSIX?1说明dev 迹茫模*常病絫类型是存在的,但没有定义它包含什么,或如何
取得其内
容。大多数实现定义了宏major和minor,但在那一个头文件中定义它们则与实现有
关。?
·系统中每个文件名的st 迹茫模*常病絛ev值是文件系统的设备号,该文件系统
包含了该
文件名和其对应的i 迹茫模*常病絥ode。?
·只有字符特殊文件和块特殊文件才有st 迹茫模*常病絩dev值。此值包含该实际
设备的设
备号。?
实例?
程序4?10为每个命令行参数打印设备号,另外,若此参数引指的是字符特殊文件
或块特殊
文件,则也打印该特殊文件的st 迹茫模*常病絩dev值。???
程序4?10〓打印st 迹茫模*常病絛ev和st 迹茫模*常病絩dev值?
在SVR4中,为了定义宏major和minor,一定要包括头文件。运
行此程序
得到下面的结果:?
$ a?out//home/stevens/dev/tty[ab]?
/:dev=7/0?
/home/stevens:dev=7/7?
/dev/ttya:dev=7/0(character)rdev=12/0?
/dev/ttyb:dev=7/0(character)rdev=12/1?
$ mount?
/dev/sd0a on /?
/dev/sd0h on/home?
$ ls -1/dev/sd0[ah]/dev/tty[ab]?
brw-r----- 1 root 7,0 Jan 31 08:23/dev/sd0a?
brw-r----- 1 root 7,7 Jan 31 08:23/dev/sd0h?
crw-rw-rw- 1 root 12,0 Jan 31 08:22/dev/ttya?
crw-rw-rw- 1 root 12,1 Jul 9 10:11/dev/ttyb?
传递给该程序的头两个参数是目录(根和/home/stevens),后两个是设备名/dev/tt
g[ab],
这两个设备是字符特殊设备。从程序的输出可见,根目录和/home/steven目录的设
备号不同
,这表示它们位于不同的文件系统中。运行mount命令证明了这一点。然后我们用
ls命令查
看由mount命令报告的两个盘设备,和两个终端设备。这两个盘设备是块特殊设备
,面向个
终端设备则是字符特殊设备。(通常,只有块特殊设备才能包含随机存取文件系统
,它们是
:硬、软盘驱动器和CD-ROM等。Unix的较老版本支持磁带存放文件系统,但这从未
广泛使用
过。)注意,两个终端设备的文件名和i-node在设备I/O上(st 迹茫模*常病絛ev,
这是根文
件系统,它包含了/dev文件系统),但是它们的实际设备号是:12/0和12/1。?
4?24〓sync和fsync函数?
传统的Unix实现在系统核中没有缓冲存储器,大多数磁盘I/O都通过缓存进行。当
交数据写
到文件上时,通常该数据先由系统核复制到其一个缓存中,如果该缓存尚未写满,
则并不将
其排入输出队列,而是等待其写满或者当系统核需要重用该缓存以便存放其它盘块
数据时,
再将该缓存排入输出队列,然后待其到达队前时,才进行实际的I/O操作。这种输
出方式被
称之为延迟写。延迟写减少了硝基读写次数,但是却降低了文件内容的更新速度,
使得欲写
到文件中的数据在一段时间内并没有写到硝基上。当系统发生故障时,这种延迟可
能造成文
件更新内容的丢失。为了保证磁盘上实际文件系统与缓存中内容的一致性,UNIX系
统提供了
sgnc和fsync二个系统的用函数。?
#include?
void sync(void);?
int fsync(int ?filedes);??
返回:若成功为0,出错为-1?
sync只是将所有修改过的块的缓存排入写队列,然后就返回,它并不等待实际I/O
操作结束
。?
系统精灵进程(通常称为update)一般每隔30秒调用一次sync函数。这就保证了定期
刷新系统
核的块缓存。命令sync(1)也调用sync函数。?
函数fsync只引用单个文件(由文件描述符filedes指定),它等待I/O结束,然后返
回。fsync
可用于数据库这样的应用程序,在确保修改过的块立即写到磁盘上。比较一下fsync和O〖C
模*常病絊Y
NC标志(见3?13节)。当调用fsync时,它更新文件的内容,而对于O 迹茫模*常?nbsp;
〗SYNC,
则是每次对文件调用write函数时就更新文件的内容。?
SVR4和4?3+BSD两者都支持sync和fsync,它们都不是POSIX?1的组成部分,但XPG
3要求fsync。?
4?25〓文件存取许可数位摘要?
我们已注说明了所有文件存取数位,其中某些位有多种用途。图4?14摘要列出了
所有这些许可数位,以及它们对普通文件和目录文件的作用。???
图4?14〓文件存取数位摘要?
最后9个常数分成3组。?
S 迹茫模*常病絀RWXU=S+IRUSR|S 迹茫模*常病絀WUSR|S 迹茫模*常病絀XUS
R?
S 迹茫模*常病絀RWXG=S+IRGRP|S 迹茫模*常病絀WGRP|S 迹茫模*常病絀XGR
P?
S 迹茫模*常病絀RWXO=S+IROTH|S 迹茫模*常病絀WOTH|S 迹茫模*常病絀XOT
H?
4?26〓摘要?
本章内容围绕stat函数,详细介绍了stat结构中的每一个成员。这使我们对Unix文
件的各个属性都有所了解。对文件的所有属性以及对文件进行操作的所有函数有完
整的了解对各种Unix程序设计是非常重要的。[LM]
| | |