CSDN 上的 APUE 读书笔记第四章 -- 文件和目录

20 篇文章 0 订阅

第四章 文件和目录


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

  •  普通文件——各种以 ASCII 和二进制存放的程序和文档都属于普通文件);  
  • 目录文件——注意三个特殊的目录文件:/、.和..; 
  •  字符设备文件——如/dev/tty; 
  •  块设备文件——如/dev/sda;  
  • 管道或 FIFO 文件——FIFO 文件可以用 mkfifo(1)创建。而管道文件在文件系统中是没有文件名的,但可以在/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 -l 查看; 
  •  套接字——一个典型的有名 Unix 套接字为/dev/log,它用于接收系统日志信息
对文件的访问涉及到访问权限的概念,本章中提及的函数都或多或少要求进程对文件具有操作权限。

1、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);

这些函数都用来将指定文件(目录)的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)大致通过以下命令统计(并不完善,只是给出大致的例子):
$ find / -H -exec ./a.out {} \; | grep -c “regular”
对应的 shell 命令为 stat(1);

2、文件访问权限
  •  Unix 对文件定义了三组用户权限,分别对应为属主用户(user)、组用户(group)、其它用户(other),每组用户各有自己对此文件的读、写、执行权限。权限值以八进制的形式表示,也记录在stat 结构的st_mode 字段中。
  • 创建和删除文件时,必需对目录拥有 w 和 x 的权限。目录的 w 和 x 权限只决定对目录下的创建和删除权限,只有内核才能写目录文件的数据项;
  • 测试当前进程对文件访问权限的函数(但不测试 EUID 和 EGID 的权限):
#include <unistd.h>
int access(const char *pathname, int mode);

mode 的取值包括 R_OK、W_OK、X_OK、F_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,则应保存此函数的返回值,留待需要的时候进行恢复。
  •  用于改变文件访问权限模式的函数:chmod、fchmod:
#include <sys/stat.h>
int chmod(const char *filename, mode_t mode);
int fchmod(int filedes, mode_t mode);
chmod 使用文件名,fchmod 使用文件描述符,以 mode 改变文件的权限,mode 的值为S_IXUSR、S_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用户,函数成功返回时,文件的SetUID 及SetGID 将自动被清除。其中,lchown 设置的是符号链接本身而不是其链接到的目标。
系统同时也提供了相应的 chown(1)工具。
  • 在 shell 下执行一个文件时,进程的有效 UID(EUID)一般就是启动这个进程(实际上通过 fork(2)和exec(3)实现)的用户的 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 的文件和目录清单。
3、文件的尺寸

  • 文件尺寸对应的 stat 结构中的 st_size 成员,取值为 0~系统允许的最大值;目录文件和符号链接的长度不为 0;
  • st_blksize 为块大小,是文件系统用于存储和一次 I/O 操作的最小单位;
  • st_blocks 为为文件实际分配的块数;
  • 文件可以包含不含数据的空洞,可以用 lseek 和 write 产生这种空洞;空洞影响文件实际尺寸(st_size),但不会分配块空间(st_blocks);(在 vim(1)中,文件的空洞将显示为^@)
  • 使用 cat(1)等工具复制含有空洞的文件时,空洞将被填充为 0,并占用磁盘空间;
  • 函数 truncate 及 ftruncate 用于截短文件:
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);
length 为函数成功执行后所指定文件的大小,如果比原来的大小要大,加长的部分将填充为文件空洞;

4、Unix 文件系统的基本概念

  •   磁盘上的每个分区都包含了 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 成员维护了该目录下的硬链接对象数,空目录至少拥有"."和".."两个硬链接。不允许跨文件系统进行硬链接;

5、硬链接

#include <unistd.h>
int link(const char *existingfilename, const char *newfilename);
int unlink(const char *filename);

link 用于创建已存在文件的一个硬链接,成功后的 newfilename 和 existingfilename 拥有相同的inode 编号,同时相应的 st_nblink 加 1;
unlink 用于删除文件(在文件系统上解除对此文件的链接),需要进程的 EUID 对文件目录项所属的目录有 w 和 x 的权限,若目录设置了 Sticky 位,还需要 EUID 为文件属主或 root用户;
只有在对文件的硬链接全部解除(st_nblink为0)或者没有被进程打开时,文件的内容才会真正从文件系统中删除并释放磁盘空间;这种特点常可以被利用来创建临时文件,打开后马上 unlink 之,使这个文件只能被打开的进程访问;
link 和 unlink 不能用于目录;而 remove 函数即可以删除文件和也可以删除目录:
#include <stdio.h>
int remove(const char *filename);

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

6、符号链接

#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 的一个符号链接为 sympath,actualpath 可以是不存在的,这时通过符号链接访问将会出错;
符号链接相当于虚拟目录,其数据项的内容即为所链接到的文件的路径名(可能是相对路径也可能是绝对路径),readlink读取这个内容到指定的buf 中并返回读到的数据长短;
对应的 shell 工具为 ln(1)和 readlink(1),也可以用"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)将无法识别。

7、文件重命名
#include <stdio.h>
int rename(const char *oldname, const char *newname);

若newname 已存在且不是目录,oldename 将原子地先将其删除再执行重命名;
若oldname 和newname 为同一个文件的硬链接(inode 编号相同),rename 将什么也不做而直接返回0;
若oldname 是个目录,要求newname 要么不存在,要么是个空目录,否则失败;
rename 函数直接错作符号链接本身;
rename(1)本身不是 shell 基本工具(coreutils 或者 busybox 中的工具),我登录的几台机器上,Solaris 无 rename 命令,Fedora 上是经编译的二进制命令,Ubuntu 上是个 perl(1)脚本。通常 shell 中以 mv(1)执行重命名功能;

8、文件的时间
Unix 文件系统的文件时间信息包括
atime:最后一次访问数据(而不是访问 inode 和目录项)的时间(read 等);
ctime:最后一次修改文件状态的时间(chmod、chown、unlink 等);
mtime:最后一次修改数据的时间(write 等)。

注意:并没有文件创建时间,另外它们都是time_t结构,将引起 2038bug 问题;
习题4.14 说明了利用时间信息的一个典型应用;
删除文件系统内在一周内没有访问过的core文件的例子:
$ find / -name core -atime +7 -exec rm {} \;

对文件和目录进行操作时,会更改哪个时间值是值得注意的,不正常的表现可能是系统被人蓄意更改的蛛丝马迹:对某目录中的文件进行读写不会影响目录的时间,但创建、删除和更名(覆盖)会影响;父目录的atime 不因其子文件而受影响;
atime 和 mtime 可由 utime(2)更改,同时 ctime 将自动更新为执行时的时间:
#include <utime.h>
int utime(const char *pathname, const struct utimbuf *times);

utimbuf 包括了 actime 和 modtime 两个 time_t 成员;
若进程对文件有写权限,可将times 设置为NULL 是文件时间更新到当前时间;直接修改时间需要属主或者 root权限;
修改文件时间对应的shell 工具为touch(1);

9、目录访问
#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 用于重置dp 到DIR*流的起点,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,有时还为其构造一个包括了bin、etc和var 等目录,使得 pathname 成为进程的一个蜜罐子系统。对应 shell 工具为 chroot(8)。一个典型应用为将守护进程锁在pathname 下,以防止该进程访问其它目录提高安全性,Hardening Linux 一书中有一个使用 chroot 构造蜜罐的详例。另一个典型应用是使用光盘恢复系统时,通过 chroot重新获取原系统的/目录。

10、设备文件

设备文件包括字符设备(如/dev/tty)和块设备(如/dev/cdrom);
文件的 stat 结构的 st_dev 指文件所在目录所属文件系统的设备信息。而 st_rdev 在文件是一个设备文件时,为该设备的信息,否则一般为 0;
设备信息分主、次设备号,可以用宏 major()和 minor()来获取;

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值