Chinaunix首页 | 论坛 | 博客
  • 博客访问: 249799
  • 博文数量: 51
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 575
  • 用 户 组: 普通用户
  • 注册时间: 2006-09-22 13:49
文章分类
文章存档

2009年(1)

2008年(13)

2007年(7)

2006年(30)

我的朋友

分类: 系统运维

2008-10-30 01:02:46

第四章 文件和目录

UNIX下“一切皆文件”。即对于外部对象均抽象为文件的形式进行访问,这样就可以通过统一的openreadwritecloseI/O函数对它们进行操作。但各种文件细究也分了好几种类别:

  • 普通文件——各种以ASCII和二进制存放的程序和文档都属于普通文件);

  • 目录文件——注意三个特殊的目录文件:/...

  • 字符设备文件——如/dev/tty

  • 块设备文件——如/dev/sda

  • 管道或FIFO文件——FIFO文件可以用mkfifo创建。而管道文件在文件系统中是没有文件名的,但可以在/proc文件系统中看到某些进程打开了此种类型的文件,例如使用以下命令:

$ tail -f /var/log/syslog | grep “log”

假设tailpid2342grep2344,则可以从系统的另一个tty上看到文件/proc/2342/fd/1/proc/2344/fd/0链接到了同一个管道文件上。

  • 符号链接——其内容即为链接目标,可以用readlink(1)或者ls -1查看;

  • 套接字——一个典型的有名UNIX套接字为/dev/log,它用于接收系统日志信息。

    对文件的访问涉及到访问权限的概念,本章中提及的函数都或多或少要求进程对文件具有操作权限。

1statfstatlstat函数

#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);

这些函数都用来将指定文件(目录)的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)

2、文件访问权限

  • UNIX对文件定义了三组用户权限,分别对应为属主用户(user)、组用户(group)、其它用户(other),每组用户各有自己对此文件的读、写、执行权限。权限值以八进制的形式表示,也记录在stat结构的st_mode字段中。

  • 创建和删除文件时,必需对目录拥有wx的权限。目录的wx权限只决定对目录下的创建和删除权限,只有内核才能写目录文件的数据项;

  • 测试当前进程对文件访问权限的函数(但不测试euidegid的权限):

#include <unistd.h>

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

mode的取值包括R_OKW_OKX_OKF_OK。最后一个参数测试文件是否存在。返回0时,表示测试结果为成功。返回-1时表示失败,根据errno的结果获知失败原因。

  • open(2)函数的最后一个参数mode设置文件创建时的权限。此外进程的umask设定了创建文件时应该屏蔽哪些权限位的默认权限,可以用umask(1)查看和修改。所以open函数创建的文件权限实际上是mode|(~umask)的结果。

  • umask函数:

#include <sys/stat.h>

mode_t umask(mode_t cmask);

此函数以参数cmask改变进程的文件创建模式umask值,并返回之前的旧值。如果只是临时更改umask,则应保存此函数的返回值,留待需要的时候进行恢复。

  • 用于改变文件访问权限模式的函数:chmodfchmod

#include <sys/stat.h>

int chmod(const char *filename, mode_t mode);
int fchmod(int filedes, mode_t mode);

chmod使用文件名,fchmod使用文件描述符,以mode改变文件的权限,mode的值为 S_IXUSRS_IROTH等的按位或。使用此函数要求进程的euid==文件的uid或者进程有root权限。

    系统同时也提供了相应的chmod(1)工具。

  • 改变文件属主用户及组用户:chown函数

#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);

相应参数为-1时表示原值不变。如果进程非root用户,函数成功返回时,文件的setuidsetgid将自动被清除。其中,lchown设置的是符号链接本身而不是其链接到的目标。

系统同时也提供了相应的chown(1)工具。

  • shell下执行一个文件时,进程的有效uid(euid)一般就是启动这个进程(实际上通过fork(2)exec(2)实现)的用户的uid。如果文件进行了setuid,则执行时进程的euid就成为了文件的属主用户的uid,即进程将以属主的身份执行此文件。setgid的概念类似。命令"chmod u+s""chmod g+s"分别可以对文件进行setuidsetgid

  • 对应setuidsetgid的其它用户的特殊访问权限称为sticky位。设置了sticky位的目录下的文件,进程除了应对目录具有wx权限外,还必须为文件属主或目录属主或root才能删除对应文件。sticky位通常用于公共目录,典型的设置了sticky位的目录为/tmp。对应的shell命令为chmod o+t"

  • setuidsetgidsticky文件由于其权限的特殊性,设置时应特别谨慎小心。对于系统管理员,完成系统的安装和配置后,在将其放入生产环境和联网前,应使用find(1)等方法扫描整个系统,备份setuidsetgidsticky的文件和目录清单。

3、文件的尺寸

  • 文件尺寸对应的stat结构中的st_size成员,取值为0~系统允许的最大值;目录文件和符号链接的长度不为0

  • st_blksize为块大小,是文件系统用于存储和一次I/O操作的最小单位;

  • st_blocks为为文件实际分配的块数;

  • 文件可以包含不含数据的空洞,可以用lseekwrite产生这种空洞;空洞影响文件实际尺寸(st_size),但不会分配块空间(st_blocks);(在vim中,文件的空洞将显示为^@

  • 使用cat等工具复制含有空洞的文件时,空洞将被填充为0,并占用磁盘空间;

  • 函数truncateftruncate用于截短文件:

#include <unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);

length为函数成功执行后所指定文件的大小,如果比原来的大小要大,加长的部分将填充为文件空洞;

4UNIX文件系统的基本概念

  • 磁盘上的每个分区都包含了0个或1个文件系统,文件系统决定了磁盘分区数据的组织和管理;

  • UNIX抽象了四种对象概念以管理不同的文件系统:文件、目录项、索引节点、挂载点(对于LinuxVFS虚拟文件系统,超级块superblock代表了一个已挂载的文件系统。可参考《Understanding Linux Kernel》和《Linux Kernel Development》了解更多虚拟文件系统的实现细节)。

  • stat结构的数据除了inode(索引节点,"i" for index)编号外的数据都从inode对象中取得,inode编号(以及文件名)从目录项(dentry)中取得;命令"ls -i"可以列出inode编号;

  • 目录stat结构的st_nblink成员维护了该目录下的硬链接对象数,空目录至少拥有"."".."两个硬链接。不允许跨文件系统进行硬链接;

5、硬链接

#include <unistd.h>

int link(const char *existingfilename, const char *newfilename);
int unlink(const char *filename);

link用于创建已存在文件的一个硬链接,成功后的newfilenameexistingfilename拥有相同的inode编号,同时相应的st_nblink1

unlink 用于删除文件(在文件系统上解除对此文件的链接),需要进程的euid对文件目录项所属的目录有wx的权限,若目录设置了sticky位,还需要euid为文件属主或root用户;

只有在对文件的硬链接全部解除(st_nblink0)或者没有被进程打开时,文件的内容才会真正从文件系统中删除并释放磁盘空间;这种特点常可以被利用来创建临时文件,打开后马上unlink之,使这个文件只能被打开的进程访问;

linkunlink不能用于目录;而remove函数即可以删除文件和也可以删除目录:


#include <stdio.h>

int remove(const char *filename);

这三个函数在成功时均返回0,失败时返回-1并设置errno。对应的shell工具为:link(1)unlink(1)rm(1)

5、符号链接

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);
ssize_t readlink(const char* restrict pathname, char *restrict buf, size_t bufsize);

symlink创建actualpath的一个符号链接为sympathactualpath可以是不存在的,这时通过符号链接访问将会出错;

符号链接相当于虚拟目录,其数据项的内容即为所链接到的文件的路径名(可能是相对路径也可能是绝对路径),readlink读取这个内容到指定的buf中并返回读到的数据长短;

对应的shell工具为ln(1)readlink(2),也可以用"ls -l"直接列出符号链接的内容;


使用以文件名引用文件的函数时,应注意这个函数是否将符号链接的内容当真实文件处理。函数lstatlchownunlinkremovereadlinkrename均直接处理符号链接本身。而使用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)将无法识别。

6、文件重命名

#include <stdio.h>

int rename(const char *oldname, const char *newname);

newname已存在且不是目录,oldname将原子地先将其删除再执行重命名;

oldnamenewname为同一个文件的硬链接(inode编号相同)rename将什么也不做而直接返回0

oldname是个目录,要求newname要么不存在,要么是个空目录,否则失败;

rename函数直接错作符号链接本身;

rename(1)本身不是shell常用工具(coreutils或者busybox中的工具),我登录的几台机器上,solarisrename命令,fedora上是经编译的二进制命令,ubuntu上是个perl脚本。通常shell中以mv(1)执行重命名功能;

7、文件的时间

UNIX文件系统的文件时间信息包括

atime:最后一次访问数据(而不是访问inode和目录项)的时间(read等);

ctime:最后一次修改文件状态的时间(chmodchownunlink等);

mtime:最后一次修改数据的时间(write等)。

注意:并没有文件创建时间,另外它们都是time_t结构,将引起问题;

习题4.14说明了利用时间信息的一个典型应用;

删除文件系统内在一周内没有访问过的core文件的例子:

$ find / -name core -atime +7 -exec rm {} \;

对文件和目录进行操作时,会更改哪个时间值是值得注意的,不正常的表现可能是系统被人蓄意更改的蛛丝马迹:对某目录中的文件进行读写不会影响目录的时间,但创建、删除和更名(覆盖)会影响;父目录的atime不因其子文件而受影响;

atimemtime可由utime(2)更改,同时ctime将自动更新为执行时的时间:

#include <utime.h>

int utime(const char *pathname, const struct utimbuf *times);

utimbuf包括了actimemodtime两个time_t成员;

若进程对文件有写权限,可将times设置为NULL是文件时间更新到当前时间;直接修改时间需要属主或者root权限;

8、目录访问

#include <sys/stat.h>

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

创建时注意设置x位以使其可以引用目录中的文件;

#include <unistd.h>

int rmdir(const char *pathname);

此函数只能删除空目录。删除后,已经打开了此目录的进程将不能进行创建文件之类的操作;

remove(2)也可以删除目录。对应shell工具包括了mkdir(1)rmdir(1)rm(1)-r选项)。


#include <dirent.h>

DIR *opendir(const char *pathname);
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp);
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);

以上为打开、关闭、读取、定位目录的一系列API,它使用目录流指针DIR*而不是文件描述符,并将读取的内容放到dirent中;rewinddir用于重置dpDIR*流的起点,telldir返回DIR*流的当前位置,seekdir用于在DIR*中定位;目录数据项没有写操作;


#include <unistd.h>

char *getcwd(char *buf, size_t size);
int chdir(const char *pathname);
int fchdir(int filedes);

以上为获取和设置当前工作目录的一系列API,对应的shell命令为pwd(1)cd(1);可以通过用open打开当前目录取文件描述符,chdir之后使用fchdir(fd)的方法快速回到原目录(相当于"cd -");

#include <unistd.h>

int chroot(const char *pathname);

该函数用于将当前进程的/根目录目录更改为pathname,有时还为其构造一个包括了binetcvar等目录,使得pathname成为进程的一个蜜罐子系统。对应shell工具为chroot(8)。一个典型应用为将守护进程锁在pathname下,以防止该进程访问其它目录提高安全性,《Hardening Linux》中有一个使用chroot构造蜜罐的详例。另一个典型应用是使用光盘恢复系统时,通过chroot重新获取原系统的/目录。

9、设备文件

设备文件包括字符设备(如/dev/tty)和块设备(如/dev/cdrom);

文件的stat结构的st_dev指文件所在目录所属文件系统的设备信息。而st_rdev在文件是一个设备文件时,为该设备的信息,否则一般为0

设备信息分主、次设备号,可以用宏major()minor()来获取;

10、其它文件

FIFO文件将在进程间通信一章中详说用途,套接字文件将在网络IPC一章中详说用途;

阅读(1026) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~