APUE读书笔记之第四章 文件和目录

1.stat、fstat、fstatat和lstat函数

函数原型: int  stat(const char *path, struct stat  *buf);
                  int  fstat(int  fd, struct stat  *buf);

                  int  lstat(const char  *path, struct stat  *buf);

                 int fstatat(int  dirfd, const char  *pathname, struct stat  *buf, int  flags);  

头文件:      #include <sys/types.h>
                    #include <sys/stat.h>

                    #include <unistd.h>

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

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

fstatat函数为一个相对于当前打开目录(由fd参数指定)的路径名返回文件统计信息。flags参数控制着是否跟随着一个符号链接。当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat函数不会跟随符号链接,而是返回符号链接本身的信息。否则,在默认情况下,返回的是符号链接所指向的实际文件的信息。如果fd参数的值是AT_FDCWD,并且pathname参数是一个相对路径名,fstatat会计算相对于当前目录的pathname参数。如果pathname是一个绝对路径,fd参数就会被忽略。这两种情况下,根据flags的值,fstatat的作用就跟stat或lstat一样

(类似于open函数跟openat函数)

第2个参数buf是一个指针,它指向一个我们必须提供的结构。函数来填充由buf指向的结构。结构的实际定义可能跟具体实现有所不同,但其基本形式是:

    struct stat={

        mode_t                             st_mode;

        ino_t                                  st_ino;

        dev_t                                  st_dev;

        dev_t                                 st_rdev;

        nlink_t                                st_nlink;

        uid_t                                  st_uid;

        gid_t                                  st_gid;

        off_t                                   st_size;

        struct timespec                 st_atime;

        struct timespec                 st_mtime;

        struct timespec                 st_ctime;

        blksize_t                            st_blksize;

        blkcnt_t                              st_blocks;

    };

timespec结构类型按照秒和纳秒定义了时间,至少包括下面2个字段:

time_t        tv_sec;

long          tv_nsec;

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

2.文件类型

(1)普通文件(regular file)

(2)目录文件(directory file)。这种文件包含了其它文件的名字以及指向这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。进程必须使用本章介绍的函数才能更改目录

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

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

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

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

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

文件类型信息包含在stat结构的st_mode成员中。可以用图4-1中的宏确定文件类型。这些宏的参数都是stat结构中的st_mode成员


POSIX.1允许实现将进程间通信(IPC)对象(如消息队列和信号量等)说明为文件。图4-2中的宏可用来从stat结构中确定IPC对象的类型。这些宏与图4-1中的不同,它们的参数并非st_mode,而是指向stat结构的指针


消息队列、信号量以及共享存储对象将在第15章中讨论。但是,本书所讨论的4中UNIX系统都不将这些对象说明为文件

在第一个命令行末端我们键入了一个反斜杠,通知shell要在下一行继续键入命令,然后,shell在下一行上用其辅助提示符>提示我们。

我们特地使用了lstat函数而不是stat函数以便检测符号链接。如若使用stat函数,则不会观察到符号链接

早期的UNIX版本并不提供S_ISxxx宏,于是就需要将st_mode与屏蔽字S_IFMT进行逻辑“与”运算,然后与名为S_IFxxx的常量相比较。大多数系统在文件<sys/stat.h>中定义了此屏蔽字和相关的常量。如若查看此文件,则可以找到S_ISDIR宏定义为:

#define  S_ISDIR(mode)          (((mode)  &  S_IFMT) == S_IFDIR)   

普通文件是最主要的文件类型

3.设置用户ID和设置组ID

与一个进程相关联的ID有6个或更多,如图4-5所示


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

(2)有效用户ID、有效组ID以及附属组ID决定了我们的文件访问权限

(3)保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本,8.11节说明setuid函数时,将说明这两个保存值的作用

在POSIX.1 2001年版中,要求这些保存的ID。在早期POSIX版本中,它们是可选的。一个应用程序在编译时可测试常量_POSIX_SAVED_IDS,或在运行时以参数_SC_SAVED_IDS调用函数sysconf,以判断此实现是否支持这一功能

通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID

每个文件有一个所有者和组所有者,所有者由stat结构中的st_uid指定,组所有者则由st_gid指定

当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常就是实际组ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”。与此类似,在文件模式字中可以设置另一位,它将执行此文件的进程的有效组ID设置为文件的组所有者ID(st_gid)。在文件模式字中的这两位设置用户ID位和设置组ID位

设置用户ID位和设置组ID位都包含在文件的st_mode值中。这两位可分别用常量S_ISUID和S_ISGID测试

4.文件访问权限

st_mode值也包含了对文件的访问权限位。访问权限(access permission)

每个文件都有9个访问权限位,可将它们分为3类,见图4-6


在图4-6的前3行中,术语用户指的是文件所有者(owner),chmod(1)命令用于修改这9个权限位。该命令允许我们用u表示用户(所有者),用g表示组,用o表示其他。

图4-6中的3类访问权限(即读、写以及执行)以各种方式由不同的函数使用。我们将这些不同的使用方式汇总在下面。当说明相关函数时,再进一步讨论

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

例如,为了打开/usr/include/stdio.h,需要对目录/、/usr和/usr/include具有执行权限。然后,需要具有对文件本身的适当权限,这取决于以何种模式打开它(只读、读-写等)

如果当前目录是/usr/include,那么为了打开stdio.h文件,需要对当前目录有执行权限。这是隐含当前目录的一个示例。打开stdio.h与打开./stdio.h作用相同

注意,对于目录的读权限跟执行权限的意义是不同的。读权限允许我们读目录,获得在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时,对该目录的执行权限使我们可通过该目录(也就是搜索该目录,寻找一个特定的文件名)。引用隐含目录的另一个示例是,如果PATH环境变量指定了一个我们不具有执行权限的目录,那么shell绝不会在该目录下找到可执行文件

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

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

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

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

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

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

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

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

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

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

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

按顺序执行这4步。注意,如果进程拥有此文件(第2步),则按用户访问权限批准或拒绝该进程对文件的访问-----不查看组访问权限。类似的,若进程并不拥有该文件。但进程属于某个适当的组,则按组访问权限批准或拒绝该进程对文件的访问-----不查看其它用户的访问权限

5.新文件和目录的所有权

关于新目录的所有权规则与本节将说明的新文件所有权规则相同

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

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

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

6.access和faccessat函数

当用open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。有时,进程也希望按其实际用户ID和实际组ID来测试其访问能力。access和faccessat函数是按实际用户ID和实际组ID进行访问权限测试的(该测试也分4步,与4.5节中所述的一样,但把有效改为实际)

 #include  <unistd.h>

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

 int  faccessat(int  dirfd, const  char  *pathname, int  mode, int  flags);

返回值:若成功,返回0;若失败,返回-1

其中,如果测试文件是否存在,mode就为F_OK;否则mode是图4-7所列常量的按位或


faccessat函数和access函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是dirfd参数取值为AT_FDCWD而pathname参数为相对路径。否则,faccessat计算相对于打开目录(由dirfd参数指向)的pathname

flags参数可以改变faccessat的行为,如果flags设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID

7.umask函数

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值(这是少数几个没有出错返回函数中的一个)

        #include <sys/types.h>
        #include <sys/stat.h>


       mode_t  umask(mode_t  mask);

返回值:之前的文件创建屏蔽字

其中,参数mask是由图4-6中列出的9个常量(S_IRUSR、S_IWUSR等)中的若干个按位“或”构成的

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

在前面的示例中,我们用shell的umask命令在运行程序的前、后打印文件模式创建屏蔽字。从中可见,更改进程的文件模式屏蔽字并不影响其父进程(通常是shell)的屏蔽字

umask值通常表示出8进制数(如0400、0200、0001等)。umask值中设置了相应位后,它所对应的权限就会被拒绝

8.chmod、fchmod和fchmodat函数

       #include <sys/stat.h>


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

       int  fchmod(int  fd, mode_t  mode);

       int  fchmodat(int  dirfd, const char  *pathname, mode_t  mode, int  flags);

功能:更改现有文件的访问权限

返回:若成功,返回0;若失败,返回-1

chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。fchmodat函数和chmod函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是dirfd参数取值为AT_FDCWD而pathname参数为相对路径。否则,fchmodat计算相对于打开目录(由dirfd参数指向)的pathname。flags参数可以改变fchmodat的行为,如果flags设置为AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。

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

参数mode是图4-11中所示常量的按位或


注意,在图4-11中,有9项是取自图4-6中的9个文件访问权限位。我们另加了6个,它们是两个设置ID常量(S_ISUID和S_ISGID)、保存正文常量(S_ISVTX)以及3个组合常量(S_IRWXU、S_IRWXG和S_IRWXO)。

注意,ls命令将组执行权限表示为S,它表示设置组ID位已经设置,同时,组执行位未设置。

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

chmod函数在下列条件下自动清除两个权限位:

(1)如果我们试图设置普通文件的粘着位(S_ISVTX),而且又没有超级用户权限,那么mode中的粘着位自动被关闭。这意味着只有超级用户才能设置普通文件的粘着位。这样做的理由是防止恶意用户设置粘着位,由此影响系统性能。(粘着位对linux的普通文件并无意义)

(2)新创建文件的组ID可能不是调用进程所述的组(可能是父目录的组ID)。特别地,如果新文件的组ID不等于进程的有效组ID或者进程附属组ID中的一个,而且进程没有超级用户权限,那么设置组ID位会被自动关闭。

9.chown、fchown、fchownat和lchown函数

下面几个函数可用于更改文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-1,则对应的ID不变。

#include <unistd.h>

int  chown(const char  *path, uid_t  owner, gid_t  group);
int  fchown(int  fd, uid_t  owner, gid_t  group);

int  lchown(const char  *path, uid_t  owner, gid_t  group);

 int  fchownat(int  dirfd, const char  *pathname, uid_t  owner, gid_t  group, int  flags);

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

除了所引用的文件是符号链接以外,这4个函数的操作类似。在符号链接情况下,lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向文件的所有者。

fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号链接的所有者。

fchowndat函数和chown或lchown函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是dirfd参数取值为AT_FDCWD而pathname参数为相对路径。在这两种情况下,如果flags参数中设置了AT_SYMLINK_NOFOLLOW标志,则fchownat跟lchown行为相同,如果flags参数中清除了AT_SYMLINK_NOFOLLOW标志,则fchownat跟chown行为相同。否则,fchowndat计算相对于打开目录(由dirfd参数指向)的pathname。

10.文件长度

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

(FreeBSD 8.0、MacOSX10.68和Solaris10对管道也定义了文件长度,它表示可从该管道中读到的字节数)

对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束提示。对于目录,文件长度通常是一个数(如16或512)的整数倍。

对于符号链接,文件长度是在文件名中的实际字节数。例如,lib -> usr/lib,文件长度为7,就是usr/lib的长度。

(注意,因为符号链接文件长度总是由st_size指示,所以它并不包含通常C语言用作名字结尾的null字节)

大多数现代的UNIX系统提供字段st_blksize和st_blocks。其中,第一个是对文件I/O较合适的块长度(Linux ext4文件系统中,其长度为4096个字节,也被称为磁盘块长度),第二个是所分配的实际512字节块块数。3.9节中,我们将st_blksize用于读操作时,读一个文件所需的时间量最少。为了提高效率,标准I/O库也试图一次读、写st_blksize个字节。

(不同的UNIX版本其st_blocks所用的单位可能不是512字节的块。使用此值并不是可移植的。)

11.文件截断

有时候我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截断为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点。为了截断文件可以调用函数truncate和ftruncate。

       #include <unistd.h>
       #include <sys/types.h>

       int  truncate(const char  *path, off_t  length);

       int  ftruncate(int  fd, off_t  length);

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

这两个函数将一个现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的数据就不能再访问。如果以前的长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

图13-6程序使用了ftruncate函数,以便在获得对一个文件的锁后,清空该文件。

12.文件系统

UFS是以Berkeley快速文件系统为基础的。本书讨论该文件系统。

我们可以把磁盘分成一个或多个分区。每个分区可以包含一个文件系统(见图4-13)。i节点是固定长度的记录项,它包含有关文件的大部分信息。


如果更仔细地观察一个柱面组的i节点和数据块部分,则可以看到图4-14中所示的情况。

(1)在图中有两个目录项都指向同一个i节点。每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可删除该文件(也就是可以释放该文件占用的数据块)。这就是为什么“解除对一个文件的链接”操作并不总是意味着“释放该文件占用的磁盘块”的原因。这也是为什么删除一个目录项的函数称为unlink而不是delete的原因。在stat结构中,链接计数包含在st_nlink成员中,其基本数据系统类型是nlink_t。这种链接类型称为硬链接。

(2)另外一种链接类型称为符号链接(symbolic link)。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。该i节点中的文件类型是S_IFLNK,于是系统就知道这是个符号链接。

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

(4)因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点。这就是为什么ln(1)命令(构造一个指向一个现有文件的目录项)不能跨越文件系统的原因。

(5)当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数没改变。例如,为/usr/lib/foo重命名为/usr/foo,如果目录/usr/lib和/usr在同一文件系统中,则文件foo的内容无需移动。这就是mv(1)命令通常的操作。




我们说明了普通文件的链接计数概念,但是对于目录文件的链接计数字段又如何呢?假定我们在工作目录中创建了一个新目录:

图4-15显示了其结果。注意,改图显示的显示了.和..目录项。

编号为2549的i节点,其类型字段显示其是一个目录,链接计数为2。任何一个叶目录(不包含任何其它目录的目录)的链接计数总是2,数值2来自于命名该目录的目录项(testdir)以及在该目录中的.项。编号为1267的i节点,其类型字段表示它是一个目录,链接计数大于或等于3。它大于或等于3的原因是,至少有3个目录项指向它:一个是命名它的目录项(图中未标出),第二个是在该目录中的.项,第三个是在其子目录中testdir中的..项。注意,在父目录中的每一个子目录都使该父目录的链接计数增加1。


13.link、linkat、unlink、unlinkat和remove函数

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

       #include <unistd.h>

       int  link(const char  *oldpath, const char  *newpath);

       int  linkat(int  olddirfd, const char  *oldpath,int  newdirfd, const char  *newpath, int  flags);

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

这两个函数创建一个新目录项newpath,它引用现有文件oldpath。如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其它部分应当已经存在。

对于linkat函数,现有文件是通过olddirfd和oldpath参数指定的,新的路径名是通过newdirfd和newpath参数指定的。默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算。如果两个文件描述符中的任一个设置为AT_FDCWD,那么相应的路径名(如果它是相对路径)就通过相对于当前目录进行计算。如果任一路径名是绝对路径,相应的文件描述符参数就会被忽略。

当现有文件是符号链接时,由flags参数来控制linkat函数是创建指向现有符号链接的链接还是创建指向现有符号链接指向的文件的链接。如果flags参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。如果这个标志被清除了,则创建一个指向符号链接本身的链接。

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

虽然POSIX.1允许实现支持跨越文件系统的链接,但是大多数实现要求现有的和新建的两个路径名在同一文件系统中。很多文件系统实现不允许对于目录的硬链接。

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

       #include <unistd.h>

       int unlink(const char  *pathname);

       int unlinkat(int dirfd, const char  *pathname, int  flags);

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

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

我们在前面已经提及,为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。如果对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:

(1)拥有该文件

(2)拥有该目录

(3)称为超级用户

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

如果pathname参数是相对路径名,那么unlinkat函数计算相对于由dirfd文件描述符参数代表的目录的路径名。如果dirfd参数设置为AT_FDCWD,那么通过相对于调用进程的当前工作目录来计算路径名。如果pathname参数是绝对路径名,那么dirfd参数被忽略。

flags参数给出了一种方法,使调用进程可以改变unlinkat函数的默认行为。当AT_REMOVEDIR标志被设置时,unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除,unlinkat与unlink执行同样的操作。

14.rename和renameat函数

文件或目录可以用rename函数或renameat函数进行重命名。

        #include <stdio.h>

       int  rename(const char  *oldpath, const char  *newpath);

       int  renameat(int  olddirfd, const char  *oldpath,int  newdirfd, const char  *newpath);

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

根据oldpath是指文件、目录还是符号链接,有几种情况需要加以说明。我们也必须说明如果newpath已经存在时将会发生什么。

(1)如果oldpath指的是一个文件而不是目录,那么为该文件或符号链接重命名。在这种情况下,如果newpath已存在,则它不能引用一个目录。如果newpath已存在,而且不是一个目录,则先将该目录项删除然后将oldpath重命名为newpath,对包含oldpath的目录以及newpath的目录,调用进程必须具有写权限,因为将更改这两个目录。

(2)如果oldpath指的是一个目录,那么为该目录重命名。如果newpath已存在,则它必须引用一个目录,而且该目录必须是空目录。如果newpath存在(而且是一个空目录),则先将其删除,然后将oldpath重命名为newpath。

(3)如若oldpath或newpath引用符号链接,则处理的是符号链接本身,而不是它所引用的文件。

(4)不能对.和..重命名。

(5)作为一个特例,如果oldpath和newpath引用同一文件,则函数不做任何更改而成功返回。

15.符号链接

符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。引入符号链接的原因是为了避开硬链接的一些限制。

(1)硬链接通常要求链接和文件位于同一文件系统中。

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

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

16.创建和读取符号链接

17.文件的时间

对每个文件维护3个时间字段,它们的意义示于图4-19中。


注意,修改时间(st_mtim)和状态更改时间(st_ctim)之间的区别。修改时间是文件内容最后一次被修改的时间。状态更改时间是该文件的i节点最后一次被修改的时间。在本章中我们说明了很多影响到i节点的操作,如更改文件的访问权限、更改用户ID、更改链接数等,但它们并没有更改文件的实际内容。因为I节点中的所有信息都是与文件的实际内容分开存放的,所以,除了要记录文件数据修改时间外,还需要记录状态更改时间,也就是更改i节点中信息的时间。

注意,系统并不维护对一个i节点的最后一次访问时间,所以,access和stat函数并不更改这3个时间中的任一个。

18.futimens、utimensat和utimes函数

一个文件的访问和修改时间可以用以下几个函数修改。futimens和utimensat函数可以指定纳秒级精度的时间戳,用到的数据结构是与stat函数簇相同的timespec结构。

     #include <sys/stat.h>

    int  utimensat(int  dirfd, const  char  *pathname,const struct  timespec times[2], int  flags);

    int  futimens(int  fd, const struct  timespec  times[2]);

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

这两个函数的times数组参数的第一个元素包含访问时间,第二个元素包含修改时间。

时间戳可以按下列4种方式之一进行指定。

(1)如果times参数是一个空指针,则访问时间和修改时间都设置为当前时间。

(2)如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_NOW,相应的时间戳就设置为当前时间,忽略相应的tv_sec字段。

(3)如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_OMIT,相应的时间戳保持不变,忽略相应的tv_sec字段。

(4)如果times参数指向两个timespec结构的数组,且tv_nsec字段既不是UTIME_NOW也不是UTIME_OMIT,在这种情况下,相应的时间戳设置为tv_sec和tv_nsec字段的值。

        #include <sys/time.h>

        int  utimes(const char  *filename, const struct timeval  times[2]);

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

utimes函数对对路径名进行操作。times参数是指向包含两个时间戳(访问时间和修改时间)元素的数组的指针。两个时间戳用秒和微秒表示。

注意,我们不能对状态更改时间st_ctim(i节点最近被修改的时间)指定一个值,因为调用utimes函数时,此字段会被自动更新。

19.mkdir、mkdirat和rmdir函数

        #include <sys/stat.h>
        #include <sys/types.h>

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

        int mkdirat(int dirfd, const char *pathname, mode_t mode);

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

所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

常见的错误是是指定与文件相同的mode(只指定读、写权限)。但是,对于目录通常要至少设置一个执行权限位,以允许访问该目录中的文件名。

      #include <unistd.h>

      int rmdir(const char *pathname);

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

20.读目录

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

       #include <sys/types.h>
       #include <dirent.h>

       DIR *opendir(const char *name);

       DIR *fdopendir(int fd);

返回值:若成功,返回指针;若出错,返回NULL。

      struct dirent *readdir(DIR *dirp);

返回值:若成功,返回指针;若在目录尾或出错,返回NULL。

      void rewinddir(DIR *dirp);

      int closedir(DIR *dirp);

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

      long telldir(DIR *dirp);

返回值:与dirp相关联的目录中的当前位置

      void seekdir(DIR *dirp, long loc);

21.chdir、fchdir和getcwd函数

当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。

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

       #include <unistd.h>

       int chdir(const char *path);

       int fchdir(int fd);

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

在这两个函数中,分别用path或打开文件描述符来指定新的当前工作目录。

       #include <unistd.h>

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

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

22.设备特殊文件

有关st_dev和st_rdev的规则很简单:

(1)每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型都是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为与其通信的外设板;次设备号标示为特定的子设备。回忆图4-13,一个磁盘驱动器经常包含若干个文件系统。在同一磁盘驱动器上的各文件系统通常具有相同的主设备号,但次设备号却不同。

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

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

(4)只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值