1、简介
在之前的章节中我们讨论了进行 I/O
的基本函数。前面的讨论集中在正规文件上(就是普通的文件),例如打开文件、读取写入文件。这里我们将要讲述关于文件系统的一些额外信息,以及文件的属性。我们从 stat
函数开始将起,然后讲述 stat
结构的每个成员,这样可以看到文件的所有属性。在这个过程中,我们也会讲到每个修改这些属性的函数:改变属主,改变权限等等。我们将会看到UNIX文件系统以及符号链接更为细节的内容。我们通过操作目录的函数来结束本章,并且基于此我们可以编写一个遍历整个目录的函数。
译者注
原文参考
2、 stat
, fstat
,和 lstat
函数
本章主要描述如下三个函数以及它们的返回值。
#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
返回:三者如果成功均返回0,错误返回1(其实其值一般应该是-1)。
-
stat
返回pathname
所指定的文件的相关信息结构到buf
中; -
fstat
返回filedes
描述符上打开的文件的相关信息到buf
中; -
lstat
返回pathname
所指定的文件的信息到buf
中,与stat
不同的是,如果pathname
代表一个符号链接,那么它返回符号链接的信息而不像stat
那样返回符号链接所指向的文件的信息。
buf
指向的 stat
结构基本定义如下:
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 */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last file status change */
blksize_t st_blksize; /* best I/O block size */
blkcnt_t st_blocks; /* number of disk blocks allocated */
};
POSIX.1
并没有要求有 st_rdev
, st_blksize
,以及 st_blocks
成员。它们在 SUS
(即 Single UNIX Specification
)中的 XSI
扩展中定义。需要注意的是每个成员都被指定成系统基本数据类型。我们将会对这个结构的每个成员进行讲述,来学习文件的属性。实际 ls -l
命令就是调用 stat
函数,返回文件所有信息。
译者注
原文参考
3、文件类型
至今我们已介绍了两种类型的文件——普通文件和目录。UNIX系统的大多数文件是普通文件或目录,此外也有一些其它的文件类型:
- 普通文件(
regular file
,本书有的地方称作正规文件)。这是最常见的文件类型,它包含了某种形式的数据。这种数据可以是文本也可以是二进制数据,这对于内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。 - 目录文件(
directory file
)。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读许可权的任一进程都可以读该目录的内容,但只有内核可以写目录文件。 - 字符特殊文件(
character special file
)。也称字符设备文件,这种文件用于系统中某些类型的设备,例如某些键盘,鼠标等。 - 块特殊文件(
block special file
)。也称块设备文件,磁盘设备就是这种类型的。 - FIFO。这类文件用于进程间的通信,也称为有名管道。
- 套接字( = socket = )。这类文件用于进程间的网络通信。套接字也可用于在一台宿主机上的进程之间的非网络通信。
- 符号连接(
symbolic link
)。这种文件指向另一个文件。相当于"Windows"下指向某一个文件的“快捷方式”。
系统中的所有设备文件要么是字符特殊文件,要么是块特殊文件,用它来表示一个设备,通过系统调用操作这些设备文件,进而达到操作设备的目的,这也是Unix/Linux设备管理,基于文件系统的一个原因。
文件类型信息包含在 stat
结构的 st_mode
成员中。下表给出用来确定文件类型的的宏。这些宏的参数都是 stat
结构中的 st_mode
成员。
<sys/stat.h>中定义的文件类型宏
+--------------------------------------+
| Macro | Type of file |
|------------+-------------------------|
| S_ISREG() | regular file |
|------------+-------------------------|
| S_ISDIR() | directory file |
|------------+-------------------------|
| S_ISCHR() | character special file |
|------------+-------------------------|
| S_ISBLK() | block special file |
|------------+-------------------------|
| S_ISFIFO() | pipe or FIFO |
|------------+-------------------------|
| S_ISLNK() | symbolic link |
|------------+-------------------------|
| S_ISSOCK() | socket |
+--------------------------------------+
另外, POSIX.1
允许实现(这里的实现也就是系统实现,以后都这样说)将内部通信( IPC
)对象(例如消息队列和信号量)做为文件。下表中的宏允许我们通过 stat
结构确定 IPC
对象的类型。这些宏不像上面表中使用 st_mode
成员做为参数,它的参数是指向 stat
结构的指针。
<sys/stat.h>中定义的IPC类型宏
+---------------------------------------+
| Macro | Type of object |
|---------------+-----------------------|
| S_TYPEISMQ() | message queue |
|---------------+-----------------------|
| S_TYPEISSEM() | semaphore |
|---------------+-----------------------|
| S_TYPEISSHM() | shared memory object |
+---------------------------------------+
后面对队消息队列,信号量,和共享内存对象进行讨论(15章)。本书提到的UNIX系统实现中没有将它们当做文件。
译者注
原文参考
4、 Set-User-ID
和 Set-Group-ID
进程对文件操作权限有三对ID:
real user ID
real group ID
这组 ID
表示当前真正的用户究竟是谁,这个字段(例如 real user ID
)是从 passwd
中取得的。
effective user ID
effective group ID
supplementary group IDs
利用这组 ID
来决定进程访问文件的权限。
saved set-user-ID
saved set-group-ID
这组 ID
在进程运行的时候,会保留一份 effective ID
的副本,在旧版本的 posix
中可选(即可有可无)。
另外,在文件的 stat
结构中的 st_mode
描述了文件的类型和权限, st_uid
描述了文件的属主。
read user ID
和 real group ID
用来表示真正的用户究竟是谁。这两个成员在我们登陆的时候从 password
文件中获得。一般来说,这些值在一个登陆会话中,就不会改变了。当然也有一些方法可以修改这些 real ID
,我们在后面(8章11节)讲到。 effective user ID
、 effective group ID
、以及 supplementary group IDs
确定我们对文件的访问权限。我们下一节会讲到。 saved set-user-ID
和 saved set-group-ID
会在程序被执行的时候,包含 effective user ID
和 effective group ID
的拷贝。我们会在8章11节讲述 setuid
函数的时候讨论这些 saved ID
的作用。一般来说, effective user ID = 等于 =real user ID
; effective group ID
等于 real group ID
。每个文件都有一个属主和组属主。属主通过 stat
结构的 st_uid
成员指定;组属主通过 st_gid
成员指定。
具体来说:
一般程序执行的时候,进程的 effective user ID
等于 real user ID
; effective group ID
等于 real group ID
(使用它和文件的 owner
比较,如果一样就可以执行文件)。但是如果文件设置了 set-user-ID
(在 stat
中的 s_mode
中设置),那么程序运行的时候, effectiveID
就和文件的属主 ID
一样,这样就有特殊的权限了。 set-group-ID
类似。
例如一个文件的属主是超级用户,并且这个文件的 set-user-ID
位被设置,那么当那个程序文件被作为一个进程运行的时候,它就有了超级用户的权限。一般在我们执行文件而不考虑 real ID
(注意这个 ID
是针对进程的而不是文件的)的时候,就会发生这个情况。例如,UNIX 系统中,任何用户都可以修改他们自己的密码,修改的命令是 passwd
,这个命令实际修改的是系统文件,而系统文件只能被超级用户修改;但是由于这个 passwd
命令是 set-user-ID
被设置了的程序,这样我们就可以将密码信息写入系统的密码文件中( /etc/passwd
或者 /etc/shaw
文件)。也就是说,这些系统文件通常只有超级用户能够修改,而这个 passwd
的 set-user-ID
位,使得运行这个程序的用户也有了超级用户的权限。因为被设置了 set-user-ID
的程序,可以让其他用户具有更多的权限,所以我们需要仔细考虑其安全性,第8章会讨论。
运行 stat
函数的时候 set-user-ID
位以及 set-group-ID
位被包含到文件的 st_mode
值中,这两个位可以通过 S_ISUID
和 S_ISGID
被检测到。
译者注
原文参考
5、文件访问权限
stat
的 st_mode
成员,其值也包含了文件的访问权限。所有文件类型(目录,字符特别文件等)都有访问权限。每个文件有9个存取许可权位,可将它们分成三类,如下表所示:
<sys/stat.h>中定义的9个文件访问权限位
+--------------------------------+
| st_mode mask | Meaning |
|---------------+----------------|
| S_IRUSR | user-read |
|---------------+----------------|
| S_IWUSR | user-write |
|---------------+----------------|
| S_IXUSR | user-execute |
|---------------+----------------|
| S_IRGRP | group-read |
|---------------+----------------|
| S_IWGRP | group-write |
|---------------+----------------|
| S_IXGRP | group-execute |
|---------------+----------------|
| S_IROTH | other-read |
|---------------+----------------|
| S_IWOTH | other-write |
|---------------+----------------|
| S_IXOTH | other-execute |
+--------------------------------+
在上表开头三行中, user
指的是文件所有者。 chmod(1)
命令可以修改这9个权限位。使用该命令,我们可以用 u
表示用户(所有者), g
表示组, o
表示其他。
为便于直观理解,这里给出使用 ls
列出文件所有属性的命令执行结果,列出当前目录文件,并且显示详细信息的命令和结果如下:
# ls -l
总计 132
drwxrwxr-x 2 quietheart quietheart 4096 04-07 14:23 astah_test
drwxrwsrwx 9 quietheart quietheart 4096 2010-10-26 chrome
drwxrwxr-x 4 quietheart quietheart 4096 04-06 10:35 cpptest
drwxrwxr-x 2 quietheart quietheart 4096 03-28 14:19 grep_test
drwxrwxrwx 7 quietheart quietheart 4096 04-02 08:35 hello
drwxrwxr-x 2 quietheart quietheart 4096 02-01 14:44 hello2
drwxrwxr-x 2 quietheart quietheart 4096 03-24 09:36 lsof_test
-rw-rw-r-- 1 quietheart quietheart 507 12-30 11:32 minicom_script
-rw-rw-r-- 1 quietheart quietheart 25 2010-12-29 minicom_script1
-rw-rw-r-- 1 quietheart quietheart 465 12-31 10:28 minicom.sh
drwxrwxr-x 2 quietheart quietheart 4096 04-02 08:34 pre_test
-rw-r--r-- 1 quietheart quietheart 655 2010-12-29 scriptdemo
lrwxrwxrwx 1 root root 10 06-30 13:51 scriptdemo.symbol -> scriptdemo
drwxrwxr-x 4 quietheart quietheart 4096 01-21 16:19 svn_study
-rwxrwxrwx 1 quietheart quietheart 108 2010-11-26 testsh.sh
-rwxrwxrwx 1 quietheart quietheart 444 2010-11-11 translate.sh
drwxr-xr-x 2 root root 4096 05-11 16:37 wordpress
这里,依次显示了文件的类型权限,硬链接数,属主,属组,大小,日期,时间,名称。这里比较难以理解的是第一列的类型权限,和第二列的硬链接数。类型权限,例如 drwxrwxr-x
,其含义是第一个字符表示类型(这里的d表示目录,l表示链接,c表示字符设备文件其他的类型不细说了),以后的9个字符分别三个一组,每组的三个字符分别表示相应用户的读写执行权限,可以通过 chmod
来修改,第一组 rwx
表示当前用户权限,权限为可读可写可执行,第二组 rwx
表示当前组权限,权限为可读可写可执行,第三组 r-x
表示其他用户权限,权限可读不可写不可执行;硬链接数,表示该文件使用 ln
命令建立了多少个硬链接(通过硬链接引用文件,需要对硬链接有所理解)。如果文件是软链接(通过 ln -s
建立的)那么同时会显示那个软链接指向谁,例如这里的 symbol
。
有些书中把这三种用户类型分别称之为所有者,组和世界。而 chmod
命令用 o
表示其他,而不是所有者。我们这里将使用术语用户、组和其他,以便和 chmod
命令一致。图中的三类存取许可权——读、写及执行——以各种方式由不同的函数使用。我们将这些不同的使用方法列在下面,当说明这些函数时,再进一步作讨论。
-
我们用路径名打开任一类型的文件时,对该路径中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行许可权。因此,对于目录,其执行许可权位常被称为搜索位。
例如,为了打开文件
/usr/include/stdio.h
,需要具有对目录/
,/usr
,/usr/include
的执行权限。然后,还要有对该文件本身的适当权限,这取决于以何种方式打开它(只读,读-写等)。如果当前目录是/usr/inlcude
,那么为了打开文件stdio.h
,我们需要有对该目录的执行权限。如在指定打开文件stdio.h
时,可以隐含当前目录,而不用显式地提及/usr/include
,也可使用./stdio.h
。注意,对于目录的读权限和执行权限的意义不同。读权限允许我们读目录,获得在该目录中所有文件名的列表。而当一个目录是我们要存取文件的路径名的一个分量时,对该目录的执行权限使我们可通过该目录(也就是搜索该目录,寻找一个特定的文件名)。另一个例子是,当PATH
环境变量(8.4节将说明)指定了一个我们不具有执行权的目录之时,那么shell
决不会在该目录下找到可执行文件。 -
对于一个文件的读权限决定了我们是否能够打开该文件进行读操作。这对应于
open
函数的O_RDONL
和O_RDWR
标志。 -
对于一个文件的写权限决定了我们是否能够打开该文件进行写操作。这对应于
open
函数的O_WRONL
和O_RDWR
标志。 -
在
open
函数中对一个文件指定O_TRUNC
标志之时,首先必须对该文件具有写权限。 -
在一个目录中创建一个新文件之时,首先必须对该目录具有写权限和执行权限。
-
删除一个文件,必须对包含该文件的目录具有写权限和执行权限。而对该文件本身则不需要有读、写权。
-
如果用6个
exec
函数(见8.9节)中的任何一个执行某个文件,都必须对该文件具有执行权限。
进程每次打开、创建或删除一个文件时,内核就进行文件存取权检查,而这种检查可能涉及文件的所有者( stat
结构的 st_uid
和 st_gid
成员),进程的有效 ID
(有效用户 ID
和有效组 ID
)以及进程的添加组 ID
(若支持的话)。两个所有者 ID
是文件的性质,而有效 ID
和添加组 ID
则是进程的性质。内核判断进程访问文件权限进行检查的过程是:
-
如果进程的
effective user ID
是0,那么访问被无条件允许。 -
如果进程的
effective user ID
等于文件的owner ID
(也就是进程拥有这个文件),那么访问权限依照文件的user
访问权限而定。也就是说,如果
user
是可读的,那么进程能够读取,如果user
可写的那么进程可以写,等等,如果设置user
权限不可读写,那么即使进程effective user ID
和owner ID
(也就是user ID
)一样,也是无法进行相应的访问的。 -
如果进程的
effective group ID
或者supplementary group ID
和文件的group ID
一样,那么访问权限会依照文件组权限的设定而定。 -
如果文件的
other
组的指定的访问权限被指定了,那么相应权限的访问会被允许。
以上过程是依照次序进行的,如果在1-4步骤中,进入了某一步骤的判断,并且确定了权限,那么下一个步骤会被忽略的。
另外,本书练习4.4提到过,如果创建一个已经存在的文件,那么这个文件的权限不会被改变,但是这个文件的内容有可能被 truncate.
网友评论