UNIX环境高级编程(文件和目录)
第四章 文件和目录
41〓引言
在上一章我们说明了执行I/O操作的基本函数。其讨论是围绕普通文件的I/O进行的—打开—文件,读或写一个文件。本章将观察文件系统的其它特征和文件的性质。我们从stat函数开始,并逐个说明stat结构的每一个成员以了解文件的所有属性。在此过程中,我们的说明修改这些属性的各个函数(更改属主,更改许可数等)。我们也将更详细地察看Unix文件系统的
结构以及符号连接。本章结束部分介绍对目录进行操作的各个函数,并且开发了一个以降序遍历目录层次结构的函数。
42〓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返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。(在42)节中当降序周游目录层次结构时,我们需要lstat。在416节中的较详细地说明符号
连接。)
lstat函数不属于POSIX 10031—1990标准,但很可能加到10031a中。SVR4和43+BSD支持lstat。
第二个参数是个指针,它指向一个我们应提供的结构。这些函数填写由buf指向的结构。该结构的实际定义可能所实施而有所不同,但其基本形式是:
struct stat{
mode〖CD#2〗t st〖CD#2〗mode; /*文件类型和方式(许可数)*/
ino〖CD#2〗t st〖CD#2〗ino;/* i-节点号(序列号)*/
dev〖CD#2〗t st〖CD#2〗dev;/*设备号(文件系统)*/
dev〖CD#2〗t st〖CD#2〗rdev;/*特殊文件的设备号*/
nlink〖CD#2〗t st〖CD#2〗nlink;/*连接数*/
uid〖CD#2〗t st〖CD#2〗uid;/*属主的用户ID*/
gid〖CD#2〗t st〖CD#2〗gid;/*属主的组ID*/
off〖CD#2〗t st〖CD#2〗size;/*普通文件的字节长度*/
time〖CD#2〗t st〖CD#2〗atime;/*最后存取时间*/
time〖CD#2〗t st〖CD#2〗mtime;/*最后修改存取时间*/
time〖CD#2〗t st〖CD#2〗ctime;/*最后文件状态更改时间*/
long st〖CD#2〗blksize;/*最佳I/O块长*/
long st〖CD#2〗blocks;/*分配的512字节块块数
};
POSIX1未定义st〖CD#2〗rdevst〖CD#2〗blksige和st〖CD#2〗blo
cks字段。SVR4和43+BSD则定义了这些字段。
注意,除最后两个以外,其它各成员都说明为基本系统数据类型(见27节)。我们将说明此结构的每个成员以了解文件属性。
stat函数的最大用户很可能是ls-l命令,用其可以获得有关一个文件的所有信息。
43〓文件类型
至今我们已介绍了两种不同的文件类型—普通文件和目录。Unix系统的大多数文件是普通文件或目录,但是也有另外一些文件类型:
1普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式的数据。
至于这种数据是文本还是二进制数据对于系统核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。
2目录文件(Directory file)。这种文件包含了其它文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读许可数的任一进程都可以读该目录的内容,但只有系统核可以写目录文件。
3字符特殊文件(Charocter special file)。这种文件用于系统中的某些类型的设备。
4块特殊文件(Block special file)。这种文件典型地用于磁盘设备。系统中的所有设备或者是字符特殊文件,或者是块特殊文件。
5 FIFO。这种文件用于进程间的通信,有时也将其称为命名管道。在145对其进行说明。
6套接口(socket)。这种文件用于进程间的网络通信。套接口也可用于在一台宿主机上的进程之间的非网络通信。在第十五章,我们将用套接口进行进程间的通信。
只有43+BSD才返回套接口文件类型,虽然SVR4支持用套接口进行进程间通信,但现在是经由套接口函数库实现的,而不是通过系统核内的套接口文件类型,将来的SVR4版本可能会支持套接口文件类型。
7符号连接(Symbolic link)。这种文件指向另一个文件。我们在416中将更多地述及符号连接。
文件类型信息,包含在stat结构的st〖CD#2〗mode成员中。我们可以用图41中的宏
确室文件类型。这些宏的参数都是stat结构中的st〖CD#2〗mode成员。
图41〓在中的文件类型宏
实例
程序41取其命令行参数,然后针对每一个命令行参数打印其文件类型。
程序41〓对每个命令行参数打印文件类型
程序41的样本输出是:
$ aout/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〖CD#2〗ISXXX宏,于是就需要将st〖CD#2〗mode与屏蔽字S〖CD#2〗IFMT逻辑与,然后与名为s〖CD#2〗IFXXX的常数相比较。SVR4和43+BSD在文件中定义了此屏蔽字和相关的常数。如若我们查看此文件,则可找到S〖CD#2〗ISDIR宏定义为:
我们说过,普通文件是最主要的文件类型,但是观察一下在一个给定的系统中各种文件的比例是很有兴趣的。图42中显示了在一个中等规模的系统中的统计值。这一数据是由421节中的程序得到的。
图42〓不同类型文件的计数值和比例
44〓设置一用户〖CD#2〗ID和设置一组〖CD#2〗ID
与一个进程相关联的ID有六个或更多。它们示于图43中。
图43〓与每个进程相关联的用户ID和组ID
·实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自我们在口令文件中的记录项。通常,在一个登录会话期这些值并不改变,但是超级用户进程有方法,改变它们,在810节中将说明这些方法。
·有效用户ID,有效组ID以及添加组ID决定了我们的文件存取数,下一节将对此进行说明。
(我们已在18节中说明了添加组ID)。
·保存的设置一用户〖CD#2〗ID和保存的设置一组〖CD#2〗ID在执行一个程序时包含了有效用户ID和有效组ID的副本,在810节中说明setuid函数时,我们将说明这两个保存值的作用。
在POSIX1中,这些保存ID是可选择的。一个应用程序在编译时可测试常数〖CD#2〗
POSIX〖CD#2〗SAVED〖CD#2〗I
DS,或在运行时以参数〖CD#2〗SC〖CD#2〗SAVED〖CD#2〗IDS调用函数
sysconf,以判断此实现是否支持这种特征。SVR4支持此特征。
FIPS |5|-1要求POSIX1的这种可选择特征。
通常,有效用户ID等于实际用户ID,以及有效组ID等于实际用户ID。
每个文件有一个属主和组属主,属主是由stat结构中的st〖CD#2〗uid表示的,组属主则由st〖CD#2〗gid成员表示。
当我们执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。但是可以在文件方式字(st〖CD#2〗mode)中设置一个特殊标志,其定义是“当执行此文件时,将进程的有效用户ID设置为文件的属主(st〖CD#2〗uid)”。与此相类似,在文件方式字中可以设置另一位,它使得执行此文件的进程的有效组ID设置为文件的组属主(st〖CD#2〗gid)。在文件方式字中的这两位被称之为设置一用户〖CD#2〗ID位和设置一组〖CD#2〗ID位。
例如,若文件属主是超级用户,而且设置了该文件的设置一用户〖CD#2〗ID位,然后当该程序由一个进程运行时,则该进程具有超级用户优先数。不管执行此文件的进程的实际用户ID是什么,都作这种处理。作为一个例子,Unix程序passwd(1)允许任一用户改变其口令字,该程序是一个设置一用户〖CD#2〗ID程序。因为该程序应能将用户的新口令字写入口令字文件中(典型地这是/etc/passwd或/etc/shadow),而只有超级用户才具有该文件的写许可数,所以需要使用设置一用户〖CD#2〗ID特征。因为运行设置一用户〖CD#2〗ID程序的进程通常得到额外的许可数,所以要特别谨慎地编写这种程序。我们将在第八章更详细地讨论这种类型的程序。
再返回到stat函数,设置一用户〖CD#2〗ID位及设置一组〖CD#2〗ID位都包含在st〖CD#2〗mode值中。这两位可用常数S〖CD#2〗ISUID和S〖CD#2〗ISGID测试。
45〓文件存取许可数
st〖CD#2〗mode值也包含了对文件的存取数位。当我们说及文件时,我们指的是前面所提到的任何类型的文件。所有文件类型(目录,字符特别文件等)都有许可数。很多人认为只有普通文件有存取许可数,这是一种误解。
每个文件有9个存取数位,可将它们分成三类。这些都示于图44中。
图44〓9个存取数位(在中定义)
在图44开头三行中,术语用户指的是文件属主。chmod(1)命令用于修改这9个许可数位。
该命令允许我们用u表示用户(属主),用g表示组,用o表示其他。有些书把这三种用户夫妇别称之为属主,组和世界。这会造成混乱,因为chmod命令用o表示其他,而不是属主(owner)。我们将使用术语用户,组和其他,以便与chmod命令一致。
图中的三类存取数—读、写及执行—以各种方式由不同的函数使用。我们将这些不同的使用方法摘要列在下面,当说明这些函数时,再进一步作讨论。
第一个规则是,我们用名字要打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行许可数。这就是为什么对于目录其执行许可数位常被称为搜索位的原因。
例如,为了打开文件/usr/dict/words,我们需要具有对目录/,/usr,/usr/dict的执行许可数。然后,我们需要对该文件本身的适当许可数,这取决于我们要以何种方式打开它(只读,读—写等)。
如果当前目录是/usr/dic,那么为了打开文件words,我们需要有对该目录的执行许可数。这就是隐含了当前目录的例子,我们在指定打开文件words时,没有显式地提及/usr/dicwords与/words两种表示方法是一致的。
注意,对于目录的读许可数和执行许可数的意义是不相同的。读许可数允许我们读目录,获得在该目录中所有文件名的列表。当一个目录是我们要存取文件的路径名的一个分量时,对该目录的执行许可数使我们可通过该目录。(也就是搜索该目录,寻找一个特定的文件名。
)
引用隐含目录的另一个例子是,如果PATH环境变量(在84节中说明)指定了一个我们不具有
存取数的目录,那么shell决不会在该目录下打到可执行文件。
·对于一个文件的读许可数决定了我们是否能够打开该文件进行读操作。这对应于open函数
的O〖CD#2〗RDONLY和O〖CD#2〗RDWR标志。
·对于一个文件的写许可数决定了我们是否可能够打开该文件进行写操作这对应于open函数
的O〖CD#2〗WRONLY和O〖CD#2〗RDWR标志。
·对于一个文件的写许可数决定了我们是否能够打开该文件进行写操作。这对应于open函数
的O〖CD#2〗WRONLY和O〖CD#2〗RDWR标志。
·为了在open函数中对一个文件指定O〖CD#2〗TRUNC标志,我们必须对该文件具有写
操作许可数。
·为了在一个目录中创建一个新文件,我们对该目录需要具有写许可数和执行许可数。
·为了删除一个文件,我们需要对包含该文件的目录具有写许可数和执行许可数。对该文件
本身则不需要有读、写许可数。
·如果我们用6个exec函数(89节)中的任何一个执行某个文件,则我们需要对该文件具有
执行许可数。
进程每次打开、创建或删除一个文件时,系统核就进行文件存取数测试,而这种测试可能涉
及文件的属主(st〖CD#2〗uid和st〖CD#2〗gid)。进程的有效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步),则按用户存取数批准或拒绝该进
程对文件的存取—不查看组存取数。相类似,若进程并不拥有该文件。但进程属于某个适当
的组,则按组存取数批准拒绝该进程对文件的存取—不查看其它用户存取数。
46〓新文件和目录的属主关系
在第三章中,当说明用open或creat创建新文件时,我们没有说明赋与新文件的用户ID和组I
D的值是什么。在420中,我们将说明如何创建一个新目录以及mkdir函数。关于新目录的
属主关系的规则与本节将说明的新文件的属主关系的规则相同。
新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX1允许选择下列之一作为新文
件的组ID。
1新文件的组ID可以是进程的有效组ID。
2新文件的组ID可以是它所在目录的组ID。
在SVR4中,新文件的组ID决取于它所在的目录的设置一组〖CD#2〗ID位是否设置。如
果该目录的这
一位已经设置,则新文件的组ID设置为目录的组ID;否则新文件 组ID设置为进程的有效组I
D。
43+BSD总是使用目录的组ID作为新文件的组ID。
其它系统允许以一个文件系统作为单位在POSIX1所允许的两种方法中选择一种,为此在mo
unt(1)命令中使用了一个特殊标志。
FIPS |5|-1要求一个新文件的组ID是它所在目录的组ID。
使用POSIX1所允许的第二种方法(继承目录的组ID)使得在某个目录下创建的文件和目录都
有该目录的组ID。于是文件和目录的组属主关系从该点就向下传递。例如,在/var/spcol目
录中就使用这种方法。
正如前面提到的,这种设置组属主关系的方法对43+BSD是系统默认的,对SVR4则是可选择
的。在SVR4之下,我们必须设置设置一组〖CD#2〗ID位。更进一步,为供这种方法能
够正常工作,
SVR4的mkdir函数要自动地传递一个目录的设置一组〖CD#2〗ID位。(在420节中我
们将说明,mkdir就是这样做的)。
47〓access函数
正如前面所说明的,当用open函数打开一个文件时,系统核以进程的有效用户ID和有效组ID
为基础执行其存取数限测试。有时,进程也希望按其实际用户ID和实际组ID来测试其存取能
力。例如当一个进程使用设置一用户〖CD#2〗ID,或设置一组〖CD#2〗ID特征
作为另一个用户(或组)运行
时,这可能就是需要的。即使一个进程可能已经设置一用户〖CD#2〗ID为根,它仍可
能想验证实际
用户能否存取一个给定的文件。access函数是按实际用户ID和实际组ID进行存取数测试的。
(经过45节结束部分中所述的4个步骤,但将有效改为实际。)
#include
int access(const char *pathname,int mode);
返回:若成功为0,出错为-1
其中,mode是图45中所列常数的按位或。
图45
实例
程序42显示了access函数的使用。下面是该程序的一些运动结果:
$ ls -1 aout
-rwxrwxr-x 1 stevens 105216 Jan 18 08:48 aout
$ aout aout
read access OK
open for reading OK
$ ls -1/etc/uucp/Systems
-rw-r----- 1 uucp 1441 Jul 18 15:05/etc/uucp/Systems
$ aout/etc/uucp/Systems
access error for/etc/uucp/Systems:Permission denied
open error for /etc/uucp/Systems:Permission denied
$ su〓成为超级用户
Password:输入超级用户口令
# chown uucp aout
# chkmod u+s aout将文件用户ID改为uucp,打开设置用户ID位
程序42〓access函数的实例。
在本例中,设置一用户〖CD#2〗ID程序可以确定实际用户不能读某个文件,而open函
数却能打开该文件。
在上面例子以及在第八章中,我们有时要成为超级用户,以便例示某些功能是如何工作的。
如果你使用多用户系统,但无超级用户许可数,那么你就不能完整地重复这些实例。
48〓umask函数
至此我们已说明了与每个文件相关联的9个存取数位,在此基础上我们可以说明与每个进程
相关联的文件方式创建屏蔽字。
umask函数为进程设置文件方式创建屏蔽字,并返回以前的值。9这是少数几个没有出错返回
的函数中的一个。)
#include
#include
mode〖CD#2〗t umask(mode〖CD#2〗t cmask);
返回:以前的文件方式创建屏蔽
其中,参数cmask是由图44中的9个常数(S〖CD#2〗IRUSR,S〖CD#2〗IWUSR等
)按位或构成的。
在进程创建一个新文件或一个新目录时,就一定会使用文件方式创建屏蔽字。(回忆33和3
4节,在那里我们说明了open和creat函数。这两个函数都有一个参数mode,它指定了新文
件的存取许可数位。)我们将在420节说明如何创建一个新目录,在文件方式创建屏蔽字中
为1的位,在文件mode中的相应位则一定被转成0。
实例
程序43创建了两个文件,创建第一个时,umask值为0,创建第二个时,umask值禁止所有
组和其它存取数。若运行此程序可得如下结果,从中可见存取数是如何设置的。
$ umask〓第一次打印当前文件方式创建屏蔽
02
$ aout—
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
程序43〓umask函数的实例
49〓chmod和fchmod函数
这两个函数使我们可以更改现存文件的存取许可数。
#include
#include
int chmod(const char *pathname,mode〖CD#2〗t mode);
int fchmod(int filedes,mode〖CD#2〗t mode);
二个函数返回:若成功为0,出错为-1
chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。
fchmod函数并不是POSIX1的组成部分。这是SVR4和43+BSD的扩充部分。
为了改变一个文件的许可数位,进程的有效用户ID必须等于文件的属主,或者该进程必须具
有超级用户许可数。
参数mode是图46中所示常数的某种按位或。
图46〓chmod函数的mode常数(取自)
注意,在图46中,有9项是取自图44中的9个文件存取许可数位。我们另外加上了二位设
置〖CD#2〗ID常数(S〖CD#2〗IS〔UG〕ID),保存〖CD#2〗正文常数(S〖
CD#2〗ISVTX),以及三个组合常数(S〖CD#2〗IRWX〔UGO〕)
。(在这里,我们使用了标准Unix字符类算符〔〓〕,表示方括号算符中的任何一个字符。
例如,最后一个,S〖CD#2〗IRWX〔UGO〕表示了三个常数:S〖CD#2〗IRWXU、
S〖CD#2〗IRWXG和S〖CD#2〗IRWXO。这一字符
类算符是大多数Unix shell和很多标准Unix应用程序都提供的正规表达式的一种形式。)
保存〖CD#2〗正文位(S〖CD#2〗ISVTX)不是POSIX1的一部分。我们在下一节
说明其目的。
实例
先回忆一下为例示umask函数我们运行程序43时,文件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
程序44修改了这两个文件的方式。在运行程序44后,我们见到的这两个文件的最后状态
是:
$ 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获得其当前许可
数,然后修改它。我们已显式地打开了设置一组〖CD#2〗ID位、关闭了组〖CD#
2〗执行位。对普通文件这样
做的结果是对该文件可以加强制性记录锁,我们将在123节中讨论强制性锁。注意,ls命
令将组〖CD#2〗执行许可数表示为l,它表示对该文件可以加强制性记录锁。对文件b
ar,不管其当前许可数位如何,我们将其许可数设置为一绝对值。
程序44〓chmod函数的实例
最后也要注意到。在我们运行程序44后ls命令列出的时间和日期并不改变。在418节中
,我们会了解到chmod函数更新的只是i-node最近一次被更改的时间。按系统默认方式ls-l
列出的是最后修改文件内容的时间。
chmod函数在下列条件下自动清除2个许可数位。
·如果我们试图设置普通文件的粘住位(S〖CD#2〗ISVTX),而且又没有超级用户优先
数,那么mode
中的粘住位自动被关闭。(我们将在下一节说明粘住位)。这意味着只有超级用户才能设置普
通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以此方式填
满交换区(如果系统支持保存〖CD#2〗正文特征的话)。
·新创建文件的组ID可能不是调用进程所属的组。回忆一下46节,新文件的组ID可能是父
目录的组ID。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组ID中的一个
,以及进程没有超级用户优先数,那么设置一组〖CD#2〗ID位自动被关闭。这就防止
了用户创建一个设置一组〖CD#2〗ID文件,而该文件是由并非该用户所属的组拥有的
。
43+BSD和其它贝克莱导出的系统增加了另外的安全性特征以试图防止保获位的错误使用。
如果一个没有超级用户优先数的进程写一个文件,则设置一用户〖CD#2〗ID位和设置
一组〖CD#2〗ID位自动
被清除。如果一个不怀好意的用户找到一个他可以写的设置一组〖CD#2〗ID和设置一
用户〖CD#2〗ID文件,即使他可以修改此文件,但失去了对该文件的特别优先数。
410〓粘住位
S〖CD#2〗ISVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为粘住位。
如果一个可执
行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的一个文本
被保存在交换区。(程序的正文部分是机器指令部分。)这使得下次执行该程序时能较快地将
其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的Unix文件系统中
,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序和编辑程
序的各部分常设置它们所在文件的粘住位。自然,对交换区中可以同时存放的设置了粘住位
的文件数有一定限制,以免过多占用交换区空间,但无论如何这是一个有用的技术。因为在
系统再次自草前,文件的正文部分总是在交换区中,所以使用了名字“粘住”。后来的Unix
版本称之为保存〖CD#2〗正文位,因此也就有了常数S〖CD#2〗ISVTX。现今较
新的Unix系统大多数都具有虚存系统,以及快速文件系统,所以可再需要使用这种技术。
目前粘住位的主要作用是针对目录文件的。如果对一个目录设置了粘住位,则只有对该目录
文件具有写许可数的用户并且满足下列条件之一,才能删除或换名该目录下的文件:
·拥有此文件
·拥有此目录,或者
·是超级用户
目录/tmp和/var/spool/uucppublic是设置粘住位的后选者—这两个目录是任何用户都可在
其中创建文件的目录。这两个目录对任一用户(用户、组和其他)的许可数通常都是读、写和
执行。但是用户不应能删除或换名属于其他人的文件,为此在这两个目录的文件方式中都设
置了粘住位。
POSIX1没有定义粘住位。但SVR4和43+BSD则支持这种特征。
411〓chown,fchown和lchown函数
chown函数可用于更改文件的用户ID和组ID。
#include
#include
int chown(const char *pathname,uid〖CD#2〗t owner,gid〖CD#2
〗t group);
int fchown(int filedes,uid〖CD#2〗t owner,gid〖CD#2〗t gro
up);
int lchown(const char *pathname,uid〖CD#2〗t owner,gid〖CD#2
〗t group);
三个函数的返回:若成功为0,出错为-1
除了所引用的文件是个符号连接以外,这三个函数的操作相类似。在符号连接情况下,lcho
wn更改符号连接本身的属主,而不是该符号连接所指向文件的属主。
fchown函数并不在POSIX 10031—1990标准中,但很可能被加到10031a,SVR4和43+BS
D则支持fchown。
只有SVR4支持lchown函数。在非SVR4系统中(POSIX1和43+BSD),若chown的参数pathnam
e是符号连接,则改变该符号连接的属主关系,而不改变它所指向的文件的属主关系。为了
更改该符号连接所指向的文件的属主关系,我们应指定该实际文件本身的pathname,而不是
指向该文件的连接文件的pathname。
SVR4,43+BSD和XPG3允许我们将参数owner或group指定为-1,以表示不改变相应的ID。这
不是POSIX1的一部分。
基于贝克莱的系统一直规定只有超级用户才能更改一个文件的属主。这样做的原因是防止用
户改变其文件的属主从而摆脱盘空间限额对他们的限制。但是,系统V则允许任一用户更改
他们所拥有的文件的属主。
按照〖CD#2〗POSIX〖CD#2〗CHOWN〖CD#2〗RESTRICTED的值,POSIX1
在这两种形式的操作中选用一种。FIPS |5|〖CD#2〗1要求〖CD#2〗POSIX
〖CD#2〗CHOWN〖CD#2〗RESTRICTED。
对于SVR4,此功能是个配置可选择项,而43+BSD则总对chown施加了限制。
回忆图25,该常数可选地定义在头文件中,而且总是可以用pathconf或fpath
conf函数查询。此可选项还与所引用的文件有关—可在每个文件系统基础上,使该任选项起
作用或不起作用。在下文中,我们如提及“若〖CD#2〗POSIX〖CD#2〗CHOWN〖
CD#2〗RESTRICTED起作用”,则表示
这适用于我们正在谈及的文件,而不管该实际常数是否在头文件中定义。(例如,43+BSD
总有这种限制,而并不在头文件中定义此常数。)
若〖CD#2〗POSIX〖CD#2〗CHOWN〖CD#2〗RESTRICTED对指定的文件起作
用,则
1只有超级用户进程能更改该文件的用户ID。
2若满足下列条件,一个非超级用户进程可以更改该文件的组ID:
a进程拥有此文件(其有效用户ID等于该文件的用户ID),以及
b参数owner等于文件的用户〖CD#2〗ID,参数group等于进程的有效组ID或进程的
添加组ID之一。
这意味着,当〖CD#2〗POSIX〖CD#2〗CHOWN〖CD#2〗RESTRICTED有效时
,你不能更改其
他用户的文件的用户ID。你可以更入你所拥用的文件的组ID,但只能改到你所属于的组。
如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置一用户〖CD#2
〗ID位和设置一组〖CD#2〗ID位都被清除。
412〓文件长度
stat结构的成员st〖CD#2〗sige包含了以字节为单位的该文件的长度。此字段只对普
通文件、目录文件和符号连接才是有意义的。
SVR4对管道也定义了文件长度,它表示可从该管道中读到的字节数,我们将在142中讨论
管道。
对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。
对于目录,文件长度通常是一个数,例如16或512的整倍数,我们将在421节中说明读目录
操作。
对于符号连接,文件长度是在文件名中的实际字节数。例如,
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib->usr/lib
其中,文件长度7就是路径名usr/lib的长度。(注意,因为符号连接文件长度总是由st〖C
D#2〗sige指示,所以符号连接并不包含通常C语言用作名字结尾的null字符。)
SVR4和43+BSD也提供字段st〖CD#2〗blksige和st〖CD#2〗blocks。第一个
是对文件I/O较好的块长度,第
二个是所分配的实际512字节块块数。回忆一下39节,其中提到了当我们将st〖CD#
2〗blksige用
于读操作时,读一个文件所需的最少时间量。为了效率的缘故,标准I/O库(我们将在第五章
中说明)也试图一次读、写st〖CD#2〗blksige字节。
要知道,不同的Unix版本其st-blocks所用的单位可能不是512—字节块。使用此值并不是可
移植的。
文件中的空洞
在36节中,我们提及普通文件可以包含“空洞”。在程序32中例示了这一点。空洞是由
超过文件结尾端的位移量设置,并写了某些数据后造成的。作为一个例子,考虑下列情况:
$ 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字节块块数;SVR4则报告5
12—字节块块数。)很明显,此文件有很多空洞。
正如我们在36节中提及的,read函数对于没有写过的字节位置读到的数据字节是0。如果
我们执行:
$ wc -c core
8483248 core
从此可见,正常的I/O操作读至整个文件长度。带-c选择项的(wc(1)命令计算文件中的字符(
字节)数。)
如果我们使用公用程序,例如cat,复制这种文件,那么所有这些空洞都被写成实际数据字节
0。
$ cat core>corecopy
$ ls -1 core*
-rw-r--r-- 1 stevens 8483248 Nov 18 12:18 core
-rw-rw-r-- 1 stevens 8483248 Nov 18 12:27 corecopy
$ du -s core*
272 core
16592 corecopy
从中可见,新文件所用的字节数是8,495,104(512×16,592)。此长度与ls命令报
告的长度之间的差别是由于文件系统使用了若干块以保持指向实际数据块的各指针。
有兴趣的读者应当参阅Bach〔1986〕的42节和Leffler〔1989〕的72节,以更详细
地了解文件的物理安排。
413〓文件截短
有时我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截短为0是一个特
例,在open一个文件时指定O〖CD#2〗TRUNC标志就可以做到这一点。为了截短文件可
以使用系统调用函数truncate和ftruncate。
#include
#include
int truncate(const char *pathname,off〖CD#2〗t length);
int ftruncate(int filedes,off〖CD#2〗t length);
两个函数的返回;若成功为0,出错为-1
这两个函数将由路径名pathname或打开文件描述符filedes指定的一个现存文件的长度截短
为length。如果该文件以前的长度大于Length,则超过Length以外的数据就不再能存取。如
果以前的长度短于Length,则其后果与系统有关。如果某个实现的处理是扩展该文件,则在
以前的文件尾端和新的文件尾端之间的数据将读作为0(也就是在文件中创建了一个空洞)。
SVR4和43+BSD提供了这两个函数。它们不是POSIX1或XPG3的组成部分。
SVR4截短或扩展一个文件。43+BSD只用这三个函数截短一个文件—不能用它们扩展一个文
件。
Unix从来就没有截短文件的一种标准方法。完全兼容的应用程序必须对文件制作一个副本,
在制作它时只复制所希望的数据字节。
SVR4的fcntl中有一个POSIX1没有规定的命令F〖CD#2〗FREESP,它允许释放一个文
件中的任何一部分,而不只是文件尾端处的一部分。
在程序125中,我们使用了ftruncate函数,以便在获得对该文件的锁后,使一个文件变完
。
414〓文件系统
为了说明文件连接的概念,先要对文件系统的结构有基本了解。同时,了解i〖CD#2
〗node和指向一个i〖CD#2〗node的目录项之间的区别也是很有益的。
现在,有很多Unix文件系统的实现。例如,SVR4支持两种不同类型的盘文件系统:传统的Un
ix系统V文件系统(称为55),以及统一文件系统(称为UFS)。在图26中,我们已看到了这两
种文件系统的一个区别。UFS是以贝克莱快速文件系统为基础的。SVR4也支持另外一些非磁
盘文件系统,两个分布式文件系统,以及一个自举文件系统,这些文件系统都不影响下面的
讨论。本节讨论传统的Unix系统V文件系统。这种类型的文件系统可以回溯到Version7。
我们可以把一个盘驱分成一个或多个分区。如图47中所示,每个分区可以包含一个文件系
统。
图47〓盘驱,分区和文件系统
i〖CD#2〗node是固定长度的记录项,它包含有关文件的信息。
在Version 7中,一个i〖CD#2〗node占用64字节,在43+BSD中,一个i〖CD#
2〗node占用128字节。在SVR4
中,在磁盘上一个i〖CD#2〗node的长度与文件系统的类型有关:一个S5 i〖CD#
2〗node占用64字节,而UFS i〖CD#2〗node占用128字节。
如果在忽略自举块和超级块情况下,更细仔地观察文件系统,则可以得到图48中所示的情
况。
图48〓较详细的文件系统
注意图48中的下列各点:
·在图中有两个目录项指向同一i〖CD#2〗node。每个i〖CD#2〗node中都有一
个连接计数,其值是指向该i〖CD#2〗
node的目录项数。只有当连接计数减少为0时,才可删除该文件(也就是可以释放该文件占用
的数据块)。这就是为什么“解除对一个文件的连接”操作并不总是意味着“释放该文件占
用的盘块”的原因。这也就是为什么删除一个目录项的函数被称之为unlink而不是删除的原
因。在stat结构中,连接计数包含在st〖CD#2〗nlink成员中,其基本系统数据类型
是nlonk〖CD#2〗t。这
种连接类型称之为硬连接。回忆图27,其中,POSIX1常数LINK〖CD#2〗MAX指定
了一个文件连接数的最大值。
·另外一种连接类型称之为符号连接。对于这种连接,该文件的实际内容(在数据块中)包含
了该符号连接所指向的文件的名字。在下列例子中:
lrwxrwxrwx 1 root 7 sep 25 07:14 lib->urs/lib
在该目录项中的文件名是lib,而在该文件中包含了7个数据字节usr/lib。在该i〖CD#
2〗node中的文件类型是S〖CD#2〗IFLNK,于是系统知道这是一个符号连接。
·i〖CD#2〗node包含了所有与文件有关的信息:文件类型,文件存取许可数位,文
件长度,指向该
文件所占用的数据块的指针等等。stat结构中的大多数信息都取自i〖CD#2〗node。
只有
二项数据存放在目录项中:文件名和i〖CD#2〗node编号数。i〖CD#2〗node编
号数的数据类型是ino〖CD#2〗t。
·因为在目录项中的i〖CD#2〗node编号数指向同一文件系统中的一个i〖CD#2
〗node,所以我们不能使一个
目录项指向另一个文件系统的i〖CD#2〗node。这就是为什么ln(1)命令(构造一个指
向
一个现存文件的新目录项),不能跨越文件系统的原因。我们将在下一节说明link函数。
·当不更改文件系统情况下为一个文件改换名字时,该文件的实际内容并未移动,需要做的
是构造一个指向现存i〖CD#2〗node的新目录项,并除去老的目录项。例如,为将文
件/usr/lib/fo
o换名为/usr/foo,如果目录/usr/lib和/usr在同一文件系统上,则文件foo的内容无需移动
。这就是mv(1)命令的通常操作方式。
我们说明了普通文件的连接计数的概念,但是对于目录文件的连接计数字段又如何呢?假定
我们在新目录中构造了一个新目录:
$ mkdir testdir
图49显示了其结果。注意,在该图中,我们显式地显示了和目录项。
图49〓在创建了目录testdir后的样本文件系统
编号2549的i〖CD#2〗node其类型字段表示它是一个目录,而连接计数2。任何一个叶
目录(不包含任
何其它目录,也就是子目录的目录)其连接计数总是2,数值2来自于命名该目录的目录项(te
stdir)以及在该目录中的项。编号为1267的i〖CD#2〗node,其类型字段表示它是
一个目录,而其
连接计数则大于或等于3。它大于或等�发布人:netbull 来自:LinuxAid