| 41〓引言 在上一章我们说明了执行I/O操作的基本函数。其讨论是围绕普通文件的I/O进行的 -打开- 文件,读或写一个文件。本章将观察文件系统的其它特征和文件的性质。我们从s tat函数开 始,并逐个说明stat结构的每一个成员以了解文件的所有属性。在此过程中,我们 的说明修 改这些属性的各个函数(更改属主,更改许可数等)。我们也将更详细地察看Unix文 件系统的 结构以及符号连接。本章结束部分介绍对目录进行操作的各个函数,并且开发了一 个以降序 遍历目录层次结构的函数。 42〓stat,fstat以及lstat函数 本章的讨论的中心是三个stat函数以及它们所返回的信息。 #include<sys/typesh> #include<sys/stath> 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和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字节块块数 }; POSIX1未定义st 迹茫模*常病絩devst 迹茫模*常病絙lksige和st 迹茫模?nbsp; 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 迹茫模*常病絤ode成员中。我们可以用图4 1中的宏 确室文件类型。这些宏的参数都是stat结构中的st 迹茫模*常病絤ode成员。 图41〓在<sys/stath>中的文件类型宏 实例 程序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 迹茫模*常病絀SXXX宏,于是就需要将st 迹茫模*常?nbsp; 〗mode与 屏蔽字S 迹茫模*常病絀FMT逻辑与,然后与 名为s 迹茫模*常病絀FXXX的常数相比较。SVR4和43+BSD在文件<sys/stath> 中定义了 此屏蔽字和相关的常数。如若我们查看此文件,则可找到S 迹茫模*常病絀SDIR宏 定义为: 我们说过,普通文件是最主要的文件类型,但是观察一下在一个给定的系统中各种 文件的比 例是很有兴趣的。图42中显示了在一个中等规模的系统中的统计值。这一数据是 由421 节中的程序得到的。 图42〓不同类型文件的计数值和比例 44〓设置一用户 迹茫模*常病絀D和设置一组 迹茫模*常病絀D 与一个进程相关联的ID有六个或更多。它们示于图43中。 图43〓与每个进程相关联的用户ID和组ID ·实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自我们在口令 文件中的 记录项。通常,在一个登录会话期这些值并不改变,但是超级用户进程有方法,改 变它们, 在810节中将说明这些方法。 ·有效用户ID,有效组ID以及添加组ID决定了我们的文件存取数,下一节将对此进 行说明。 (我们已在18节中说明了添加组ID)。 ·保存的设置一用户 迹茫模*常病絀D和保存的设置一组 迹茫模*常病絀D在执行 一个程序 时包含了有效用户I D和有效组ID的副本,在810节中说明setuid函数时,我们将说明这两个保存值的 作用。 在POSIX1中,这些保存ID是可选择的。一个应用程序在编译时可测试常数 CD *常病?nbsp; POSIX 迹茫模*常病絊AVED 迹茫模*常病絀 DS,或在运行时以参数 迹茫模*常病絊C 迹茫模*常病絊AVED 迹茫模*常病絀D S调用函数 sysconf,以判断此实现是否支持这种特征。SVR4支持此特征。 FIPS 。 5|-1要求POSIX1的这种可选择特征。 通常,有效用户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测试。 45〓文件存取许可数 st 迹茫模*常病絤ode值也包含了对文件的存取数位。当我们说及文件时,我们指 的是前面 所提到的任何 类型的文件。所有文件类型(目录,字符特别文件等)都有许可数。很多人认为只有 普通文件 有存取许可数,这是一种误解。 每个文件有9个存取数位,可将它们分成三类。这些都示于图44中。 图44〓9个存取数位(在<sys/stath>中定义) 在图44开头三行中,术语用户指的是文件属主。chmod(1)命令用于修改这9个许 可数位。 该命令允许我们用u表示用户(属主),用g表示组,用o表示其他。有些书把这三种 用户夫妇 别称之为属主,组和世界。这会造成混乱,因为chmod命令用o表示其他,而不是属 主(owner )。我们将使用术语用户,组和其他,以便与chmod命令一致。 图中的三类存取数-读、写及执行-以各种方式由不同的函数使用。我们将这些不同 的使用 方法摘要列在下面,当说明这些函数时,再进一步作讨论。 第一个规则是,我们用名字要打开任一类型的文件时,对该名字中包含的每一个目 录,包括 它可能隐含的当前工作目录都应具有执行许可数。这就是为什么对于目录其执行许 可数位常 被称为搜索位的原因。 例如,为了打开文件/usr/dict/words,我们需要具有对目录/,/usr,/usr/dict的 执行许可 数。然后,我们需要对该文件本身的适当许可数,这取决于我们要以何种方式打开 它(只读,读-写等)。 如果当前目录是/usr/dic,那么为了打开文件words,我们需要有对该目录的执行许 可数。这 就是隐含了当前目录的例子,我们在指定打开文件words时,没有显式地提及/usr /dicwor ds与/words两种表示方法是一致的。 注意,对于目录的读许可数和执行许可数的意义是不相同的。读许可数允许我们读 目录,获 得在该目录中所有文件名的列表。当一个目录是我们要存取文件的路径名的一个分 量时,对 该目录的执行许可数使我们可通过该目录。(也就是搜索该目录,寻找一个特定的 文件名。 ) 引用隐含目录的另一个例子是,如果PATH环境变量(在84节中说明)指定了一个我 们不具有 存取数的目录,那么shell决不会在该目录下打到可执行文件。 ·对于一个文件的读许可数决定了我们是否能够打开该文件进行读操作。这对应于 open函数 的O 迹茫模*常病絉DONLY和O 迹茫模*常病絉DWR标志。 ·对于一个文件的写许可数决定了我们是否可能够打开该文件进行写操作这对应于 open函数 的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。 ·对于一个文件的写许可数决定了我们是否能够打开该文件进行写操作。这对应于 open函数 的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。 ·为了在open函数中对一个文件指定O 迹茫模*常病絋RUNC标志,我们必须对该文 件具有写 操作许可数。 ·为了在一个目录中创建一个新文件,我们对该目录需要具有写许可数和执行许可 数。 ·为了删除一个文件,我们需要对包含该文件的目录具有写许可数和执行许可数。 对该文件 本身则不需要有读、写许可数。 ·如果我们用6个exec函数(89节)中的任何一个执行某个文件,则我们需要对该 文件具有 执行许可数。 进程每次打开、创建或删除一个文件时,系统核就进行文件存取数测试,而这种测 试可能涉 及文件的属主(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步),则按用户存取数批准或 拒绝该进 程对文件的存取-不查看组存取数。相类似,若进程并不拥有该文件。但进程属于 某个适当 的组,则按组存取数批准拒绝该进程对文件的存取-不查看其它用户存取数。 46〓新文件和目录的属主关系 在第三章中,当说明用open或creat创建新文件时,我们没有说明赋与新文件的用 户ID和组I D的值是什么。在420中,我们将说明如何创建一个新目录以及mkdir函数。关于 新目录的 属主关系的规则与本节将说明的新文件的属主关系的规则相同。 新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX1允许选择下列之一 作为新文 件的组ID。 1新文件的组ID可以是进程的有效组ID。 2新文件的组ID可以是它所在目录的组ID。 在SVR4中,新文件的组ID决取于它所在的目录的设置一组 迹茫模*常病絀D位是否 设置。如 果该目录的这 一位已经设置,则新文件的组ID设置为目录的组ID;否则新文件 组ID设置为进程 的有效组I D。 43+BSD总是使用目录的组ID作为新文件的组ID。 其它系统允许以一个文件系统作为单位在POSIX1所允许的两种方法中选择一种, 为此在mo unt(1)命令中使用了一个特殊标志。 FIPS |5|-1要求一个新文件的组ID是它所在目录的组ID。 使用POSIX1所允许的第二种方法(继承目录的组ID)使得在某个目录下创建的文件 和目录都 有该目录的组ID。于是文件和目录的组属主关系从该点就向下传递。例如,在/va r/spcol目 录中就使用这种方法。 正如前面提到的,这种设置组属主关系的方法对43+BSD是系统默认的,对SVR4则 是可选择 的。在SVR4之下,我们必须设置设置一组 迹茫模*常病絀D位。更进一步,为供这 种方法能 够正常工作, SVR4的mkdir函数要自动地传递一个目录的设置一组 迹茫模*常病絀D位。(在4 20节中我 们将说明,mkdir就是这样做的)。 47〓access函数 正如前面所说明的,当用open函数打开一个文件时,系统核以进程的有效用户ID和 有效组ID 为基础执行其存取数限测试。有时,进程也希望按其实际用户ID和实际组ID来测试 其存取能 力。例如当一个进程使用设置一用户 迹茫模*常病絀D,或设置一组 迹茫模*常?nbsp; 〗ID特征 作为另一个用户(或组)运行 时,这可能就是需要的。即使一个进程可能已经设置一用户 迹茫模*常病絀D为根 ,它仍可 能想验证实际 用户能否存取一个给定的文件。access函数是按实际用户ID和实际组ID进行存取数 测试的。 (经过45节结束部分中所述的4个步骤,但将有效改为实际。) #include<unistdh> 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函数的实例。 在本例中,设置一用户 迹茫模*常病絀D程序可以确定实际用户不能读某个文件, 而open函 数却能打开该文件。 在上面例子以及在第八章中,我们有时要成为超级用户,以便例示某些功能是如何 工作的。 如果你使用多用户系统,但无超级用户许可数,那么你就不能完整地重复这些实例 。 48〓umask函数 至此我们已说明了与每个文件相关联的9个存取数位,在此基础上我们可以说明与 每个进程 相关联的文件方式创建屏蔽字。 umask函数为进程设置文件方式创建屏蔽字,并返回以前的值。9这是少数几个没有 出错返回 的函数中的一个。) #include<sys/typesh> #include<sys/stath> mode 迹茫模*常病絫 umask(mode 迹茫模*常病絫 cmask); 返回:以前的文件方式创建 帘为?nbsp; 其中,参数cmask是由图44中的9个常数(S 迹茫模*常病絀RUSR,S 迹茫模*常?nbsp; 〗IWUSR等 )按位或构成的。 在进程创建一个新文件或一个新目录时,就一定会使用文件方式创建屏蔽字。(回 忆33和3 4节,在那里我们说明了open和creat函数。这两个函数都有一个参数mode,它指 定了新文 件的存取许可数位。)我们将在420节说明如何创建一个新目录,在文件方式创建 屏蔽字中 为1的位,在文件mode中的相应位则一定被转成0。 实例 程序43创建了两个文件,创建第一个时,umask值为0,创建第二个时,umask值 禁止所有 组和其它存取数。若运行此程序可得如下结果,从中可见存取数是如何设置的。 $ umask〓第一次打印当前文件方式创建 帘为?nbsp; 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<sys/typesh> #include<sys/stath> int chmod(const char *pathname,mode 迹茫模*常病絫 mode); int fchmod(int filedes,mode 迹茫模*常病絫 mode); 二个函数返回:若成功为0,出错为-1 chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。 fchmod函数并不是POSIX1的组成部分。这是SVR4和43+BSD的扩充部分。 为了改变一个文件的许可数位,进程的有效用户ID必须等于文件的属主,或者该进 程必须具 有超级用户许可数。 参数mode是图46中所示常数的某种按位或。 图46〓chmod函数的mode常数(取自<sys/stath>) 注意,在图46中,有9项是取自图44中的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)不是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获得其 当前许可 数,然后修改它。我们已显式地打开了设置一组 迹茫模*常病絀D位、关闭了组〖 茫模*?nbsp; 2〗执行位。对普通文件这样 做的结果是对该文件可以加强制性记录锁,我们将在123节中讨论强制性锁。注 意,ls命 令将组 迹茫模*常病 执行许可数表示为l,它表示对该文件可以加强制性记录锁 。对文件b ar,不管其当前许可数位如何,我们将其许可数设置为一绝对值。 程序44〓chmod函数的实例 最后也要注意到。在我们运行程序44后ls命令列出的时间和日期并不改变。在4 18节中 ,我们会了解到chmod函数更新的只是i-node最近一次被更改的时间。按系统默认 方式ls-l 列出的是最后修改文件内容的时间。 chmod函数在下列条件下自动清除2个许可数位。 ·如果我们试图设置普通文件的粘住位(S 迹茫模*常病絀SVTX),而且又没有超级 用户优先 数,那么mode 中的粘住位自动被关闭。(我们将在下一节说明粘住位)。这意味着只有超级用户才 能设置普 通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以 此方式填 满交换区(如果系统支持保存 迹茫模*常病 正文特征的话)。 ·新创建文件的组ID可能不是调用进程所属的组。回忆一下46节,新文件的组I D可能是父 目录的组ID。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组I D中的一个 ,以及进程没有超级用户优先数,那么设置一组 迹茫模*常病絀D位自动被关闭。 这就防止 了用户创建一个设置一组 迹茫模*常病絀D文件,而该文件是由并非该用户所属的 组拥有的 。 43+BSD和其它贝克莱导出的系统增加了另外的安全性特征以试图防止保获位的错 误使用。 如果一个没有超级用户优先数的进程写一个文件,则设置一用户 迹茫模*常病絀 D位和设置 一组 迹茫模*常病絀D位自动 被清除。如果一个不怀好意的用户找到一个他可以写的设置一组 迹茫模*常病絀 D和设置一 用户 迹茫模*常病絀D文件,即使他可以修改此文件,但失去了对该文件的特别优 先数。 410〓粘住位 S 迹茫模*常病絀SVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为 粘住位。 如果一个可执 行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的 一个文本 被保存在交换区。(程序的正文部分是机器指令部分。)这使得下次执行该程序时能 较快地将 其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的Unix文 件系统中 ,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序 和编辑程 序的各部分常设置它们所在文件的粘住位。自然,对交换区中可以同时存放的设置 了粘住位 的文件数有一定限制,以免过多占用交换区空间,但无论如何这是一个有用的技术 。因为在 系统再次自草前,文件的正文部分总是在交换区中,所以使用了名字"粘住"。后来 的Unix 版本称之为保存 迹茫模*常病 正文位,因此也就有了常数S 迹茫模*常病絀SVT X。现今较 新的Unix系统大多数都具有虚存系统,以及快速文件系统,所以可再需要使用这种 技术。 目前粘住位的主要作用是针对目录文件的。如果对一个目录设置了粘住位,则只有 对该目录 文件具有写许可数的用户并且满足下列条件之一,才能删除或换名该目录下的文件 : ·拥有此文件 ·拥有此目录,或者 ·是超级用户 目录/tmp和/var/spool/uucppublic是设置粘住位的后选者-这两个目录是任何用户 都可在 其中创建文件的目录。这两个目录对任一用户(用户、组和其他)的许可数通常都是 读、写和 执行。但是用户不应能删除或换名属于其他人的文件,为此在这两个目录的文件方 式中都设 置了粘住位。 POSIX1没有定义粘住位。但SVR4和43+BSD则支持这种特征。 411〓chown,fchown和lchown函数 chown函数可用于更改文件的用户ID和组ID。 #include<sys/typesh> #include<unistdh> 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 10031-1990标准中,但很可能被加到10031a,SVR4和 43+BS D则支持fchown。 只有SVR4支持lchown函数。在非SVR4系统?POSIX1和43+BSD),若chown的参 数pathnam e是符号连接,则改变该符号连接的属主关系,而不改变它所指向的文件的属主关 系。为了 更改该符号连接所指向的文件的属主关系,我们应指定该实际文件本身的pathnam e,而不是 指向该文件的连接文件的pathname。 SVR4,43+BSD和XPG3允许我们将参数owner或group指定为-1,以表示不改变相应 的ID。这 不是POSIX1的一部分。 基于贝克莱的系统一直规定只有超级用户才能更改一个文件的属主。这样做的原因 是防止用 户改变其文件的属主从而摆脱盘空间限额对他们的限制。但是,系统V则允许任一 用户更改 他们所拥有的文件的属主。 按照 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED的值, POSIX1 在这两种形式的操作中选用一种。FIPS | 担迹茫模*常病?要求 迹茫模*常?nbsp; 〗POSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED。 对于SVR4,此功能是个配置可选择项,而43+BSD则总对chown施加了限制。 回忆图25,该常数可选地定义在头文件<unistdh>中,而且总是可以用pathco nf或fpath conf函数查询。此可选项还与所引用的文件有关-可在每个文件系统基础上,使该 任选项起 作用或不起作用。在下文中,我们如提及"若 迹茫模*常病絇OSIX 迹茫模*常病?nbsp; CHOWN〖 茫模*常病絉ESTRICTED起作用",则表示 这适用于我们正在谈及的文件,而不管该实际常数是否在头文件中定义。(例如, 43+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位都被清除。 412〓文件长度 stat结构的成员st 迹茫模*常病絪ige包含了以字节为单位的该文件的长度。此字 段只对普 通文件、目录文件和符号连接才是有意义的。 SVR4对管道也定义了文件长度,它表示可从该管道中读到的字节数,我们将在14 2中讨论 管道。 对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。 对于目录,文件长度通常是一个数,例如16或512的整倍数,我们将在421节中说 明读目录 操作。 对于符号连接,文件长度是在文件名中的实际字节数。例如, lrwxrwxrwx 1 root 7 Sep 25 07:14 lib->usr/lib 其中,文件长度7就是路径名usr/lib的长度。(注意,因为符号连接文件长度总是 由st〖C 模*常病絪ige指示,所以符号连接并不包含通常C语言用作名字结尾的null字符 。) SVR4和43+BSD也提供字段st 迹茫模*常病絙lksige和st 迹茫模*常病絙locks 。第一个 是对文件I/O较好的块长度,第 二个是所分配的实际512字节块块数。回忆一下39节,其中提到了当我们将st〖 茫模*?nbsp; 2〗blksige用 于读操作时,读一个文件所需的最少时间量。为了效率的缘故,标准I/O库(我们将 在第五章 中说明)也试图一次读、写st 迹茫模*常病絙lksige字节。 要知道,不同的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字节块块数;SV R4则报告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〔19 福丁 的42节和Leffler〔1989〕的72节, 以更详细 地了解文件的物理安排。 413〓文件截短 有时我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截短为0 是一个特 例,在open一个文件时指定O 迹茫模*常病絋RUNC标志就可以做到这一点。为了截 短文件可 以使用系统调用函数truncate和ftruncate。 #include<sys/typesh> #include<unistdh> int truncate(const char *pathname,off 迹茫模*常病絫 length); int ftruncate(int filedes,off 迹茫模*常病絫 length); 两个函数的返回;若成功为0,出错为-1 这两个函数将由路径名pathname或打开文件描述符filedes指定的一个现存文件的 长度截短 为length。如果该文件以前的长度大于Length,则超过Length以外的数据就不再能 存取。如 果以前的长度短于Length,则其后果与系统有关。如果某个实现的处理是扩展该文 件,则在 以前的文件尾端和新的文件尾端之间的数据将读作为0(也就是在文件中创建了一个 空洞)。 SVR4和43+BSD提供了这两个函数。它们不是POSIX1或XPG3的组成部分。 SVR4截短或扩展一个文件。43+BSD只用这三个函数截短一个文件-不能用它们扩 展一个文 件。 Unix从来就没有截短文件的一种标准方法。完全兼容的应用程序必须对文件制作一 个副本, 在制作它时只复制所希望的数据字节。 SVR4的fcntl中有一个POSIX1没有规定的命令F 迹茫模*常病紽REESP,它允许释 放一个文 件中的任何一部分,而不只是文件尾端处的一部分。 在程序125中,我们使用了ftruncate函数,以便在获得对该文件的锁后,使一个 文件变完 。 414〓文件系统 为了说明文件连接的概念,先要对文件系统的结构有基本了解。同时,了解i〖C 模*常?nbsp; 〗node和指向一个i 迹茫模*常病絥ode的目录项之间的区别也是很有益的。 现在,有很多Unix文件系统的实现。例如,SVR4支持两种不同类型的盘文件系统: 传统的Un ix系统V文件系统(称为55),以及统一文件系统(称为UFS)。在图26中,我们已看 到了这两 种文件系统的一个区别。UFS是以贝克莱快速文件系统为基础的。SVR4也支持另外 一些非磁 盘文件系统,两个分布式文件系统,以及一个自举文件系统,这些文件系统都不影 响下面的 讨论。本节讨论传统的Unix系统V文件系统。这种类型的文件系统可以回溯到Vers ion7。 我们可以把一个盘驱分成一个或多个分区。如图47中所示,每个分区可以包含一 个文件系 统。 图47〓盘驱,分区和文件系统 i 迹茫模*常病絥ode是固定长度的记录项,它包含有关文件的信息。 在Version 7中,一个i 迹茫模*常病絥ode占用64字节,在43+BSD中,一个i〖 茫模*?nbsp; 2〗node占用128字节。在SVR4 中,在磁盘上一个i 迹茫模*常病絥ode的长度与文件系统的类型有关:一个S5 i 迹茫模?nbsp; 2〗node占用64字节,而UFS i 迹茫模*常病絥ode占用128字节。 如果在忽略自举块和超级块情况下,更细仔地观察文件系统,则可以得到图48中 所示的情 况。 图48〓较详细的文件系统 注意图48中的下列各点: ·在图中有两个目录项指向同一i 迹茫模*常病絥ode。每个i 迹茫模*常病絥od e中都有一 个连接计数,其值是指向该i 迹茫模*常病?nbsp; node的目录项数。只有当连接计数减少为0时,才可删除该文件(也就是可以释放该 文件占用 的数据块)。这就是为什么"解除对一个文件的连接"操作并不总是意味着"释放该文 件占 用的盘块"的原因。这也就是为什么删除一个目录项的函数被称之为unlink而不是 删除的原 因。在stat结构中,连接计数包含在st 迹茫模*常病絥link成员中,其基本系统 数据类型 是nlonk 迹茫模*常病絫。这 种连接类型称之为硬连接。回忆图27,其中,POSIX1常数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 图49显示了其结果。注意,在该图中,我们显式地显示了和目录项。 图49〓在创建了目录testdir后的样本文件系统 编号2549的i 迹茫模*常病絥ode其类型字段表示它是一个目录,而连接计数2。任 何一个叶 目录(不包含任 何其它目录,也就是子目录的目录)其连接计数总是2,数值2来自于命名该目录的 目录项(te stdir)以及在该目录中的项。编号为1267的i 迹茫模*常病絥ode,其类型字段 表示它是 一个目录,而其 连接计数则大于或等于3。它大于或等于3的原因是,至少有由三个目录项指向它: 一个是命 名它的目录项(在图49中段有表示出来),第二个是在该目录中的项,第三个是 在其子目 录testdir中的项。注意,在工作目录中的每个子目录都使该工作目录的连接 计数增1。 正如前面所述,这是Unix文件系统的经典格式,在Bach〔1986〕一市的第四章中对 此作了说 明。关于贝克莱快速文件系统对此所作的更改请参阅Leffler etal〔1989〕中 的第七章 。 415〓link,unlink,remove和rename函数 如上节所所述,任何一个文件可以有多个目录项指向其i 迹茫模*常病絥ode。创 建一个向 现存文件的连接的方法是使用link函数。 #include<unistdh> int link(const char *existingpath,const char *newpath); 返回:若成功为0,出错为-1 此函数创建一个新目录项,newpath,它引用现存文件existingpath。如若newpath 已经存在 ,则出错返回。 创建新目录项以及增加连接计数应当是个原子操作。(请回忆在311节中对原子操 作的讨论 。) 大多数实现,例如SVR4和43+BSD要求这两个路径名在同一个文件系统中。 POSIX1允许支持跨越文件系统的连接的实现。 只有超级用户进程可以创建指向一个目录的新连接。其理由是这样做可能在文件系 统中形成 循环,大多数处理文件系统的公用程序都不能处理这种情况。(我们在416节中将 说明一个 由符号连接引入的循环的例子。) 为了移去一个现存的目录项,我们调用unlink函数。 #include <unistdh> int unlink(const char *pathname); 返回:若成功为0,出错为-1 此函数移去目录项,并将由pathname所引用的文件的连接计数减1。如果该文件还 有其它连 接,则仍可通过其它连接存取该文件的数据。如果出错,则该文件作任何更改。 我们在前面已经提及,为了解除一个对文件的连接,我们必须对包含该目录项的目 录具有写 和执行许可数。正如我们在410节中所述,如果对该目录设置了粘住位,则我们 对该目录 必须具有写许可数,并且 ·拥有该文件,或者 ·拥有该目录,或者 ·具有超级用户优先数 只有当连接计数达到0时,该文件的内容再可被删除。另一个条件也阻止删除文件 的内容- 只要有一个进程使该文件打开,其内容也不能删除。当关闭一个文件时,系统核首 先检查使 该文件打开的进程计数。如果该计数达到0,然后系统核检查其连接计数,如果这 也是0,那 么就删除该文件的内容。 实例 程序45打开一个文件,然后unlink它。执行该程序的进程然后睡眠15秒钟,接着 就终止。 程序45〓打开一个文件,然后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 $ aout &〓在后台运行程序45 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 现在,盘空间区增加了92Mbytes unlink的这种特性常由程序用来确保即便程序崩溃它所创建的临时文件也不会遗留 下来。进 程用open或creat创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的 ,所以不 会将其内容删除。只有当进程关闭该文件或终止时(在这种情况下,系统核关闭该 进程所打 开的全部文件),该文件的内容再被删除。 如若pathname是一符号连接,那么unlink涉及的是符号连接而不是由该连接所引用 的文件。 超级用户可以调用其参数pathname指定一个目录的unlink,但是通常不使用这种方 式,而应 当使用函数rmdir我们将在420节中说明rmdir函数。 我们也可以用remove函数解除对一个文件或目录的连接。对于文件,remove的功能 与unlink 相同。对于目录,remove的功能与rmdir相同。 #include<stdioh> int remove(const char *pathname); 返回:若成功为0,出错为-1 ANSI C指定remove函数删除一个文件。这更改了Unix历来使用的名字unlink,其原 因是实现C 标准的大多数非Unix系统并不支持文件连接。 文件或目录用rename函数换名。 #include<stdioh> int rename(const char *oldname,const char *newname); 返回:若成功为0,出错为-1 ANSI C 对文件定义了此函数。(C标准不处理目录。)POSIX1扩展此定义包含了目 录。 依赖于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的目录具有写和执行许可数。 416〓符号连接 一个符号连接是对一个文件的间接指针,它与上一节所述的硬连接有所不同,硬连 接直接指 向文件的i 迹茫模*常病絥ode。引进符号连接的原因是为了避免硬连接的一些限 制:(a)硬 连接通常要求 连接和文件位于同一文件系统中,(b)只有超级用户才能创建一个到一个目录的硬 连接。对 符号连接以及它指向什么没有文件系统限制,任何用户都可创建指向目录的符号连 接。符号 连接典型地用于将一个文件或整个目录结构移到系统中的某个其它位置。 符号连接由42BSD引进,后来又得到SVR4的支持。在SVR4中,传统的系统V文件系 统(S5)和 统一文件系统(UFS)都支持符号连接。 POSIX 10031-1990标准并不包括符号连接。但很可能会加到10031a中。 当使用以名字引用一个文件的函数时,我们应当了解该函数是否处理符号连接功能 。也就是 是否跟随符号连接到达它所连接的文件。如若该函数处理符号连接功能,则该函数 的路径名 参数引用由符号连接指向的文件。否则,一个路径名参数引用连接本身,而不是由 该连接指 向的文件。图410摘要列出了本章中所说明的各个函数是否处理符号连接功能。 因为rmdir 并不是针对符号连接进行定义的(宏path是符号连接则出错返回),所以在图410 中没有列 出这一函数。因为对符号连接的处理是由返回文件描述符的 |