分类: LINUX
2008-12-09 12:59:10
UNIX下“一切皆文件”。即对于外部对象均抽象为文件的形式进行访问,这样就可以通过统一的open、read、write、close等I/O函数对它们进行操作。但各种文件细究也分了好几种类别:
普通文件——各种以ASCII和二进制存放的程序和文档都属于普通文件);
目录文件——注意三个特殊的目录文件:/、.和..;
字符设备文件——如/dev/tty;
块设备文件——如/dev/sda;
管道或FIFO文件——FIFO文件可以用mkfifo创建。而管道文件在文件系统中是没有文件名的,但可以在/proc文件系统中看到某些进程打开了此种类型的文件,例如使用以下命令:
$ tail -f /var/log/syslog | grep “log” |
假设tail的pid是2342,grep是2344,则可以从系统的另一个tty上看到文件/proc/2342/fd/1和/proc/2344/fd/0链接到了同一个管道文件上。
符号链接——其内容即为链接目标,可以用readlink(1)或者ls -1查看;
套接字——一个典型的有名UNIX套接字为/dev/log,它用于接收系统日志信息。
对文件的访问涉及到访问权限的概念,本章中提及的函数都或多或少要求进程对文件具有操作权限。
|
这些函数都用来将指定文件(目录)的stat结构到buf指针所指内容处。对于fstat,使用打开的文件描述符表示指定的文件或目录。对于lstat,它在指定的文件是一个符号链接时,取的是符号链接本身的stat信息,其它两个函数都将取符号链接目标的stat信息。
这三个函数调用成功时返回0,出错时将返回-1并设置相应的errno。
stat结构给出了UNIX下文件(目录)各种属性的相关信息,是个重要的数据结构,应仔细了解其各个成员字段代表的含义。对于其成员有几点值得注意的地方:
st_mode值标明了文件的类型。以其为参数调用以下宏函数进行测试,可以根据返回值是否为真,来确定指定的文件的类型:S_ISREG()、S_ISDIR()、S_ISCHR()、S_ISBLK()、S_ISFIFO()、S_ISLNK()、S_ISSOCK()
st_blksize一般根据文件系统直接相关,但在创建时也可以自己指定,例如使用dd(1)命令时可以用bs选项来设置块大小;
ls(1)命令用各种选项可以访问stat结构的所有属性。例如-l、-i。
对于书中的表4-3,可以利用程序清单4-1的程序(假设编译后为a.out)大致通过以下命令统计(使用grep过滤的方法并不完善,只是给出大致的例子):
$ find / -H -exec ./a.out {} \; | grep -c “regular” |
对应的shell命令为stat(1);
UNIX对文件定义了三组用户权限,分别对应为属主用户(user)、组用户(group)、其它用户(other),每组用户各有自己对此文件的读、写、执行权限。权限值以八进制的形式表示,也记录在stat结构的st_mode字段中。
创建和删除文件时,必需对目录拥有w和x的权限。目录的w和x权限只决定对目录下的创建和删除权限,只有内核才能写目录文件的数据项;
测试当前进程对文件访问权限的函数(但不测试euid和egid的权限):
|
mode的取值包括R_OK、W_OK、X_OK、F_OK。最后一个参数测试文件是否存在。返回0时,表示测试结果为成功。返回-1时表示失败,根据errno的结果获知失败原因。
open(2)函数的最后一个参数mode设置文件创建时的权限。此外进程的umask设定了创建文件时应该屏蔽哪些权限位的默认权限,可以用umask(1)查看和修改。所以open函数创建的文件权限实际上是mode|(~umask)的结果。
umask函数:
|
此函数以参数cmask改变进程的文件创建模式umask值,并返回之前的旧值。如果只是临时更改umask,则应保存此函数的返回值,留待需要的时候进行恢复。
用于改变文件访问权限模式的函数:chmod、fchmod:
|
chmod使用文件名,fchmod使用文件描述符,以mode改变文件的权限,mode的值为 S_IXUSR、 S_IROTH等的按位或。使用此函数要求进程的euid==文件的uid或者进程有root权限。
系统同时也提供了相应的chmod(1)工具。
改变文件属主用户及组用户:chown函数
|
相应参数为-1时表示原值不变。如果进程非root用户,函数成功返回时,文件的setuid、setgid将自动被清除。其中,lchown设置的是符号链接本身而不是其链接到的目标。
系统同时也提供了相应的chown(1)工具。
在shell下执行一个文件时,进程的有效uid(euid)一般就是启动这个进程(实际上通过fork(2)和exec(2)实现)的用户的uid。如果文件进行了setuid,则执行时进程的euid就成为了文件的属主用户的uid,即进程将以属主的身份执行此文件。setgid的概念类似。命令"chmod u+s"和"chmod g+s"分别可以对文件进行setuid和setgid。
对应setuid和setgid的其它用户的特殊访问权限称为sticky位。设置了sticky位的目录下的文件,进程除了应对目录具有w和x权限外,还必须为文件属主或目录属主或root才能删除对应文件。sticky位通常用于公共目录,典型的设置了sticky位的目录为/tmp。对应的shell命令为chmod o+t"
setuid和setgid及sticky文件由于其权限的特殊性,设置时应特别谨慎小心。对于系统管理员,完成系统的安装和配置后,在将其放入生产环境和联网前,应使用find(1)等方法扫描整个系统,备份setuid和setgid和sticky的文件和目录清单。
文件尺寸对应的stat结构中的st_size成员,取值为0~系统允许的最大值;目录文件和符号链接的长度不为0;
st_blksize为块大小,是文件系统用于存储和一次I/O操作的最小单位;
st_blocks为为文件实际分配的块数;
文件可以包含不含数据的空洞,可以用lseek和write产生这种空洞;空洞影响文件实际尺寸(st_size),但不会分配块空间(st_blocks);(在vim中,文件的空洞将显示为^@)
使用cat等工具复制含有空洞的文件时,空洞将被填充为0,并占用磁盘空间;
函数truncate及ftruncate用于截短文件:
|
length为函数成功执行后所指定文件的大小,如果比原来的大小要大,加长的部分将填充为文件空洞;
磁盘上的每个分区都包含了0个或1个文件系统,文件系统决定了磁盘分区数据的组织和管理;
UNIX抽象了四种对象概念以管理不同的文件系统:文件、目录项、索引节点、挂载点(对于Linux的VFS虚拟文件系统,超级块superblock代表了一个已挂载的文件系统。可参考《Understanding Linux Kernel》和《Linux Kernel Development》了解更多虚拟文件系统的实现细节)。
stat结构的数据除了inode(索引节点,"i" for index)编号外的数据都从inode对象中取得,inode编号(以及文件名)从目录项(dentry)中取得;命令"ls -i"可以列出inode编号;
目录stat结构的st_nblink成员维护了该目录下的硬链接对象数,空目录至少拥有"."和".."两个硬链接。不允许跨文件系统进行硬链接;
|
link用于创建已存在文件的一个硬链接,成功后的newfilename和existingfilename拥有相同的inode编号,同时相应的st_nblink加1;
unlink 用于删除文件(在文件系统上解除对此文件的链接),需要进程的euid对文件目录项所属的目录有w和x的权限,若目录设置了sticky位,还需要euid为文件属主或root用户;
只有在对文件的硬链接全部解除(st_nblink为0)或者没有被进程打开时,文件的内容才会真正从文件系统中删除并释放磁盘空间;这种特点常可以被利用来创建临时文件,打开后马上unlink之,使这个文件只能被打开的进程访问;
link和unlink不能用于目录;而remove函数即可以删除文件和也可以删除目录:
|
这三个函数在成功时均返回0,失败时返回-1并设置errno。对应的shell工具为:link(1)、unlink(1)、rm(1);
|
symlink创建actualpath的一个符号链接为sympath,actualpath可以是不存在的,这时通过符号链接访问将会出错;
符号链接相当于虚拟目录,其数据项的内容即为所链接到的文件的路径名(可能是相对路径也可能是绝对路径),readlink读取这个内容到指定的buf中并返回读到的数据长短;
对应的shell工具为ln(1)和readlink(2),也可以用"ls -l"直接列出符号链接的内容;
使用以文件名引用文件的函数时,应注意这个函数是否将符号链接的内容当真实文件处理。函数lstat、lchown、unlink、remove、readlink、rename均直接处理符号链接本身。而使用O_CREAT | O_EXCL标志执行open(2)时,文件名若引用了符号链接将出错返回。
符号链接可能会引起循环的目录结构。例如
$ ln -s . `pwd | xargs basename` |
即创建了一个符号链接引起的循环目录,它的名字和当前目录相同,并链接到当前目录形成循环。通常的shell工具如grep(1)、find(1)等在递归的搜索目录时,都能自动识别循环目录并给出警告。
另一个存在循环目录的例子是cygwin。它将windows的文件系统都挂载在/cygdrive下,而cygwin的/根文件系统通常又挂载在windows下的一个分区(如C盘)内,从而将形成循环目录:/cygdrive/c/cygwin/cygdrive/c/......这种情况下find(1)将无法识别。
|
若newname已存在且不是目录,oldname将原子地先将其删除再执行重命名;
若oldname和newname为同一个文件的硬链接(inode编号相同),rename将什么也不做而直接返回0;
若oldname是个目录,要求newname要么不存在,要么是个空目录,否则失败;
rename函数直接错作符号链接本身;
rename(1)本身不是shell常用工具(coreutils或者busybox中的工具),我登录的几台机器上,solaris无rename命令,fedora上是经编译的二进制命令,ubuntu上是个perl脚本。通常shell中以mv(1)执行重命名功能;
UNIX文件系统的文件时间信息包括
atime:最后一次访问数据(而不是访问inode和目录项)的时间(read等);
ctime:最后一次修改文件状态的时间(chmod、chown、unlink等);
mtime:最后一次修改数据的时间(write等)。
注意:并没有文件创建时间,另外它们都是time_t结构,将引起问题;
习题4.14说明了利用时间信息的一个典型应用;
删除文件系统内在一周内没有访问过的core文件的例子:
|
对文件和目录进行操作时,会更改哪个时间值是值得注意的,不正常的表现可能是系统被人蓄意更改的蛛丝马迹:对某目录中的文件进行读写不会影响目录的时间,但创建、删除和更名(覆盖)会影响;父目录的atime不因其子文件而受影响;
atime和mtime可由utime(2)更改,同时ctime将自动更新为执行时的时间:
|
utimbuf包括了actime和modtime两个time_t成员;
若进程对文件有写权限,可将times设置为NULL是文件时间更新到当前时间;直接修改时间需要属主或者root权限;
|
创建时注意设置x位以使其可以引用目录中的文件;
|
此函数只能删除空目录。删除后,已经打开了此目录的进程将不能进行创建文件之类的操作;
remove(2)也可以删除目录。对应shell工具包括了mkdir(1)、rmdir(1)和rm(1)(-r选项)。
|
以上为打开、关闭、读取、定位目录的一系列API,它使用目录流指针DIR*而不是文件描述符,并将读取的内容放到dirent中;rewinddir用于重置dp到DIR*流的起点,telldir返回DIR*流的当前位置,seekdir用于在DIR*中定位;目录数据项没有写操作;
|
以上为获取和设置当前工作目录的一系列API,对应的shell命令为pwd(1)和cd(1);可以通过用open打开当前目录取文件描述符,chdir之后使用fchdir(fd)的方法快速回到原目录(相当于"cd -");
|
该函数用于将当前进程的/根目录目录更改为pathname,有时还为其构造一个包括了bin、etc和var等目录,使得pathname成为进程的一个蜜罐子系统。对应shell工具为chroot(8)。一个典型应用为将守护进程锁在pathname下,以防止该进程访问其它目录提高安全性,《Hardening Linux》中有一个使用chroot构造蜜罐的详例。另一个典型应用是使用光盘恢复系统时,通过chroot重新获取原系统的/目录。
设备文件包括字符设备(如/dev/tty)和块设备(如/dev/cdrom);
文件的stat结构的st_dev指文件所在目录所属文件系统的设备信息。而st_rdev在文件是一个设备文件时,为该设备的信息,否则一般为0;
设备信息分主、次设备号,可以用宏major()和minor()来获取;
FIFO文件将在进程间通信一章中详说用途,套接字文件将在网络IPC一章中详说用途;