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

4.2 函数statfstatfstatatlstat

#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);
所有4个函数的返回值:若成功;返回0;若出错,返回-1

fstat函数获得已在描述符fd上打开文件的有关信息。

lstat函数类似于 stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息。

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

struct stat {
mode_t st_mode; /* file type & mode (permissions) */
ino_t st_ino; /* i-node number (serial number) */
dev_t st_dev; /* device number (file system) */
dev_t st_rdev; /* device number for special files */
nlink_t st_nlink; /* number of links */
uid_t st_uid; /* user ID of owner*/
gid_t st_gid; /* group ID of owner*/
off_t st_size; /* size in bytes, for regular files */
struct timespec st_atime; /* time of last access*/
struct timespec st_mtime; /* time of last modification */
struct timespec st_ctime; /* time of last file status change */
blksize_t st_blksize; /* best I/O block size*/
blkcnt_t st_blocks; /* number of diskblocks allocated */
};

4.3 文件类型

(1)普通文件(regular file)。这是最常用的文件类型,这种文件 包含了某种形式的数据。至于这种数据是文本还是二进制数据,对于UNIX内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。

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

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

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

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

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

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

 

例子:打印文件类型

 

4.4 设置用户ID和设置组ID

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

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

4.5 文件访问权限

st_mode值也包含了对文件的访问权限位。当提及文件时,指的是前面所提到的任何类型的文件。所有文件类型(目录、字符特别文件等)都有访问权限(access permission)。很多人认为只有普通文件有访问权限,这是一种误解。

文件:

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

读权限允许我们读目录,获得在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时,对该目

录的执行权限使我们可通过该目录。

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

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

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

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

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

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

进程:进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,而这种测试可能涉及文件的所有者(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.6 新文件和目录的所有权 

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

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

4.7 函数accessfaccessat

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

#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1

如果测试文件是否已经存在,mode就为F_OK;

否则mode是常量的按位或。

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

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

而不是实际用户ID和实际组ID。

 

下面是该程序的示例会话:

4.8 函数umask

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

#include <sys/stat.h>
mode_t umask(mode_t cmask);
返回值:之前的文件模式创建屏蔽字

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

在进程创建一个新文件或新目录时,就一定会使用文件模式创建屏蔽字、

实例:创建了两个文件,创建第一个时,umask值为0,创建第 二个时,umask值禁止所有组和其他用户的访问权限。

通常在登录时,由shell的启动文件设置一次,然后,再不改变。尽管如此,当编写创建新文件的程序时,如果我们想确保指定的访问权限位已经激活,那么必须在进程运行时修改 umask 值。例如,如果我们想确保任何用户都能读文件,则应将umask设置为0。否则,当我们的进程运行时,有效的umask值可能关闭该权限位。

4.9 函数chmodfchmodfchmodat

chmod、fchmod和fchmodat这3个函数使我们可以更改现有文件的访问权限。

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode,int flag);
3个函数返回值:若成功,返回0;若出错,返回−1

chmod 函数在指定的文件上进行操作,而 fchmod 函数则对已打开的文件进行操作。

 

fchmodat函数与chmod函数在下面两种情况下是相同 的:一种是pathname参数为绝对路径,另一种是fd参数取值为

AT_FDCWD而pathname参数为相对路径。否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname。flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。

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

 

例子:

不管文件bar的当前权限位如何,我们都将其权限设置 为一个绝对值。对文件foo,我们相对于其当前状态设置权限。为此,

先调用stat获得其当前权限,然后修改它。我们显式地打开了设置组ID位、关闭了组执行位。

4.10 粘着位

现代有了虚拟存储系统以及快速文件系统。

如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:

•拥有此文件;

•拥有此目录;

•是超级用户。

目录/tmp 和/var/tmp 是设置粘着位的典型候选者—任何用户都可在这两个目录中创建文件。任一用户(用户、组和其他)对这两个目录的权限通常都是读、写和执行。但是用户不应能删除或重命名属于其他人的文件,为此在这两个目录的文件模式中都设置了粘着位。

4.11 函数chown、fchown、fchownat和lchown

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

#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner,gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);
//4个函数的返回值:若成功,返回0;若出错,返回-1

....

4.12 文件长度

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

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

文件中的空洞

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

4.13 文件截断

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

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

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

4.14 文件系统

4.15 函数link、linkat、unlink、unlinkat和remove 

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

#include <unistd.h>
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd,const char *newpath, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1

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

 

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

#include

int unlink(const char *pathname);

int unlinkat(int fd, const char *pathname, int flag);

两个函数的返回值:若成功,返回0;若出错,返回-1

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

我们也可以用 remove 函数解除对一个文件或目录的链接。对于文
件,remove 的功能与unlink相同。对于目录,remove的功能与rmdir相
同。
#include <stdio.h>
int remove(const char *pathname);
返回值:若成功,返回0;若出错,返回-1

4.16 函数rename和renameat

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

#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd,const char *newname);
两个函数的返回值:若成功,返回0;若出错,返回-1

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

(2)如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有.和..项)。如果 newname存在(而且是一个空目录),则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr/foo重命名为/usr/foo/testdir,因为旧名字(/usr/foo)新名字的路径前缀,因而不能将其删除

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

(4)不能对.和..重命名。更确切地说,.和..都不能出现在oldname和newname的最后部分。

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

如若newname已经存在,则调用进程对它需要有写权限(如同删除情况一样)。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行权限。

除了当oldname或newname指向相对路径名时,其他情况下renameat函数与rename函数功能相同。如果oldname参数指定了相对路径,就相对于oldfd参数引用的目录来计算oldname。类似地,如果newname指定了相对路径,就相对于newfd引用的目录来计算newname。oldfd或newfd参数(或两者)都能设置成AT_FDCWD,此时相对于当前目录来计算相应的路径名。

4.17 符号链接

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

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

•只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下)。

任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置。

图4-17列出了本章中所说明的各个函数是否处理符号链接。

没有列出 mkdir、mkinfo、mknod和rmdir这些函数,其原因是,当名是符号链接时,它们都出错返回。

以文件描述符作为参数的一些函数(如fstat、fchmod等)也未在该图中列出,其原因是,对符号链接的处理是由返回文件描述符函数(通常是open)进行的。

chown是否跟随符号链接取决于实现。在所有现代的系统中,chown函数都跟随符号链接。

4.18 创建和读取符号链接 。

可以用symlink或symlinkat函数创建一个符号链接。

#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
两个函数的返回值:若成功,返回0;若出错,返回-1

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

symlinkat函数与symlink函数类似,但sympath参数根据相对于打开文件描述符引用的目录(由 fd 参数指定)进行计算。如果 sympath 参数指定的是绝对路径或者 fd 参数设置了AT_FDCWD值,那么symlinkat就等同于symlink函数。

 

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

#include <unistd.h>
ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
ssize_t readlinkat(int fd, const char* restrict pathname,char *restrict buf, 
size_t bufsize);
两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1

两个函数组合了 open、read 和 close 的所有操作。如果函数成功执行,则返回读入buf的字节数。在buf中返回的符号链接的内容不以null字节终止。

当pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWD,readlinkat函数的行为与readlink相同。但是,如果fd参数是一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于由fd代表的打开目录的路径名。

4.19 文件的时间

4.20 函数futimens、utimensat和utimes。。。

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

#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct
timespec times[2], int flag);
两个函数返回值:若成功,返回0;若出错,返回-1

4.21 函数mkdir、mkdirat和rmdir

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

#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
两个函数返回值:若成功,返回0;若出错,返回-1

这两个函数创建一个新的空目录。其中,.和..目录项是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

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

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

如果调用此函数使目录的链接计数成为 0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及.和..项。另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。(即使另一些进程打开该目录,它们在此目录下也不能执行其他操作。这样处理的原因是,为了使rmdir函数成功执行,该目录必须是空的。)

4.22 读目录

对某个目录具有访问权限的任一用户都可以读该目录,但是,为了防止文件系统产生混乱,只有内核才能写目录。

#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent *readdir(DIR *dp);返回值:若成功,返回指针;若在目录尾或出错,返回NULL
void rewinddir(DIR *dp);
int closedir(DIR *dp);返回值:若成功,返回0;若出错,返回-1
long telldir(DIR *dp);返回值:与dp关联的目录中的当前位置
void seekdir(DIR *dp, long loc);

fdopendir函数最早出现在SUSv4(Single UNIX Specification第4版)
中,它提供了一种方法,可以把打开文件描述符转换成目录处理函数需
要的DIR结构。

4.23 函数chdir、fchdir和getcwd

每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)。当用户登录到 UNIX 系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第6个字段—用户的起始目录(home directory)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。

进程调用chdir或fchdir函数可以更改当前工作目录。
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
两个函数的返回值:若成功,返回0;若出错,返回-1

按照这种方法,逐层上移,直到遇到根,这样就得到了当前工作目录完整的绝对路径名。很幸
运,函数getcwd就提供了这种功能。
#include <unistd.h>
char *getcwd(char *buf, s i z e_t size);
返回值:若成功,返回buf;若出错,返回NULL

 

4.24 设备特殊文件

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

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

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

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

 

4.25 文件访问权限位小结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值