常见的文件类型:
- 普通文件(reg):包括文本和二进制文件;
- 目录文件(dir)
- 块特殊文件(blk):提供对设备(磁盘等)带缓冲的访问,每次访问以固定长度为单位;
- 字符特殊文件(chr):提供对设备不带缓冲的访问, 每次访问长度可变;
- FIFO:用于进程间通信,也叫命名管道;
- 套接字(socket):用于进程间的网络通信;
- 符号链接(symbolic):这种类型文件指向另一个文件。
文件类型信息存在于
stat
结构的st_mode
成员中。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
stat和lstat的区别:对于符号文件,lstat返回的是该符号文件本身的信息,而stat返回的是该符号文件所链接的文件的信息。
struct stat结构的基本形式
struct stat{ mode_t st_mode; //文件类型和文件权限控制 ino_t st_ino; //索引结点(i node)编号 dev_t st_dev; //设备编号 dev_t st_rdev; //某些特殊文件的设备编号 nlinks_t st_nlink; //硬链接数目 uid_t st_uid; //所有者的用户ID gid_t st_gid; //所有者的组ID off_t st_size; //普通类型文件的大小,以字节为单位 time_t st_atime; //上次访问时间(数据部分) time_t st_mtime; //上次修改时间(数据部分) time_t st_ctime; //上次状态更改时间(索引结点) blksize st_blksize;//最适合的I/O缓冲块大小 blkcnt_t st_block; //分配的硬盘快数 }
设置用户ID和用户组ID
与进程关联的ID有如下:
- 用户ID和用户组ID:标志该进程属于谁
- 有效用户ID、有效用户组ID和附加组ID:用于文件访问权限检查
- 保存的设置用户ID和设置组ID(第八章会讲)
当执行一个程序文件时,通常有效用户ID和组ID等于实际用户ID和组ID,但在文件模式字
st_mode
中有两位:设置用户ID位,设置用户组ID位。若设置了这两位,则执行该程序文件时,新进程的有效用户ID和组ID都被设置为该文件所有者的用户ID和组ID。例如Unix passwd命令允许任一用户改变其登录口令,但口令文件只有超级用户才能写,所以passwd程序设置了用户ID位,任何执行该程序的用户都具有超级用户权限。文件访问权限
st_mode值还包含了文件的访问权限位。每个文件有9个访问权限位,可分为下表所示的三类:
st_mode 屏蔽 意义 S_IRUSR 用户-读 S_IWUSR 用户-写 S_IXUSR 用户-执行 S_IRGRP 组-读 S_IWGRP 组-写 S_IXGRP 组-执行 S_IROTH 其他-读 S_IWOTH 其他-写 S_IXOTH 其他-执行 表4-1 文件访问权限
目录的读权限指获取目录中所有的目录项列表,目录的执行权限指通过搜索该目录,寻找一个特定的文件名,即可以通过该目录访问某个文件。在目录中创建一个新文件,必须对该目录具有写权限和执行权限。删除一个现有文件,只需对包含该文件的目录具有写权限和执行权限,不需要对该文件本身具有读、写权限。进程每次访问一个文件进行的权限测试按如下步骤进行:
- 若进程的有效用户ID是0(超级用户),则允许访问;
- 若进程的有效用户ID等于该文件的所有者ID(即该进程拥有此文件),那么若所有者适当的访问权限位被设置,则允许访问,否则拒绝访问;
- 若进程的有效组ID或进程的附加组ID之一等于该文件的组ID,那么若组适当的访问权限位被设置,则允许访问,否则拒绝访问;
- 若其它用户适当的访问权限被设置,则允许访问,否则拒绝。
权限相关函数
access函数:根据进程实际用户ID和实际用户组ID来测试进程对文件的权限。 #include <unistd.h> int access (const char* pathname, int mode); 返回值:若成功返回0,失败返回-1。 umask函数:为进程设置文件模式创建屏蔽字 #include <sys/stat.h> mode_t umask(mode_t cmask); 返回值:以前的文件模式创建屏蔽字。 chmod函数和fchmod函数:更改现有文件的访问权限 #include <sys/stat.h> int chmod(const char* pathname, mode_t mode); int fchmod(int filedes, mode_t mode); 返回值:若成功返回0,出错返回-1。 chown、fchown和lchown函数:更改文件的用户ID和组ID #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); 返回值:若成功返回0,若出错返回-1。 lchown更改符号链接本身的所有者,而不是该符号链接指向的文件。 两个参数中若任意一个是-1,则对应的ID不变。
参数
cmask
表示用户将要设置的新屏蔽字,是以下九个常量的按位或运算组成的,设置了相应的屏蔽位后,它所对应的权限就会被拒绝:屏蔽位 0400 0200 0100 0040 0020 0010 0004 0002 0001 意义 用户读 用户写 用户执行 组读 组写 组执行 其他读 其他写 其他执行 表4-2 文件创建模型屏蔽字位 chmod函数的mode参数,由15个常量按位或运算组成,其中9位来自4-1表,其余6位分别是两个设置ID位(S_ISUID和S_ISGID)、粘住位(S_ISVTX)、和三个组合常量(S_IRWXU、S_IRWXG、S_IRWXO)。
文件长度和文件截短
stat结构成员st_size表示以字节为单位的文件长度,此字段只对普通文件、目录文件和符号链接有用。对符号链接,文件长度是文件名中的实际字节数。普通文件可以包含空洞,正常的I/O操作读取整个文件长度,包括空洞部分,read函数读取空洞的内容时,读的字节是0。
文件截短函数:truncate和ftruncate#include <unistd.h> int truncate(const char * pathname, off_t length); int ftruncate(int filedes, off_t length); 返回值:成功返回0,出错返回-1。
文件系统
磁盘可分为多个区,每个分区可以包含一个文件系统,如下图:
图4-1 磁盘、分区和文件系统
i结点是固定长度的记录项,它包含有关文件的大部分信息。每个i结点中都有一个链接计数,其值是指向i结点的目录项的个数。只有当链接计数减少至0时,才可删除文件,所以删除一个目录项的函数叫unlink
,而不是delete
,链接计数包含在st_link中,这种链接称为硬链接。
在不更新文件系统的情况下,更改文件名,只需增加一个新的目录项,同时将旧的目录项删除。
对于目录文件的i结点而言,其链接计数项至少为2,这是来自于命名该目录的目录项和该目录项中的.
项。link
、unlink
、remove
和rename
函数#include <unistd.h> int link(const char* existingpath, const char* newpath); 返回值:若成功则返回0,出错返回-1; #include<unistd.h> int unlink(const char* pathname); 返回值:若成功则返回0,出错返回-1; #include<stdio.h> int remove(const char* pathname); 返回值: 若成功则返回0,出错返回-1; #include<stdio.h> int rename(const char* oldname, const char* newname); 返回值:若成功返回0,出错返回-1。
link
创建一个新的硬链接,它引用现有的文件existingpath,如果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其它部分应当已存在。link函数一般不允许对目录创建硬链接,因为会导致循环。
unlink
删除一个目录项,并将由pathname所引用文件的计数减一,为删除一个目录项,必须对该目录具有写和执行权限。当关闭一个文件时,内核首先检查打开该文件的进程数,如果该数为0,然后内核检查其链接数,如果这个数也为0,就删除该文件。 unlink的这种性质经常用来确保程序崩溃时,它创建的临时文件也不会被保留,如open创建新文件后,立即调用unlink。若pathname是符号链接,则只删除该符号链接。
还可以用remove解除对文件或目录的链接,对于文件,remove功能和unlink相同,对于目录,remove功能和rmdir相同。
rename
为文件或目录更名。有以下几种情况需要注意:- 如果oldname指的是文件而不是目录,那么为该文件或符合链接更名,如果newname已存在,则它不能引用一个目录,如果newname不是一个目录,则将其目录项删除,然后将oldname更改为newname,对包含oldname和newname的目录,必须具有写权限。
- 如果oldname是一个目录,且newname存在,则其必须是一个目录,且应当是空目录,若不是空目录,则删除先将其删除,再将oldname更改为newname。newname中不能包含oldname作为前缀。
- 如果newname和oldname是引用同文件,则不作任何更改返回。
符号链接
硬链接有以下限制:要求链接和文件位于同一文件系统中;只有超级用户才能创建指向目录的硬链接。所以引入符号链接,又称软链接,是指向一个文件的间接指针,符号链接一般用于将一个文件或整个目录结构移到系统中的另一位置。符号链接可能会引起循环,所以查找路径时应尽量使用不跟随符号链接的函数。
symlink
和readlink
函数:创建符号链接和打开符号链接#include <unistd.h> int symlink(const char* actualpath, const char* sympath); 返回值:若成功则返回0,出错则返回-1; #include<unistd.h> ssize_t readlink(const char* restrict pathname, char* restrict buf, size_t bufsize); 返回值:若成功返回读到的字节数,出错返回-1。
文件访问时间
对每个文件保存有三个时间段:字段 说明 例子 ls(l)选项 st_attime 文件数据的最后访问时间 read -u st_mtime 文件数据的最后修改时间 write 默认 st_ctime i结点状态最后的更改时间 chmod、chown -c 表4-3 与每个文件相关联的三个时间值
系统并不保存对一个i结点的最后一次访问时间,所以access和stat函数并不更改这三个时间中的任一个。
utime
函数更改文件访问时间和修改时间#include<utime.h> int utime(const char* pathname,const struct utimbuf* times); 返回值:若成功返回0,若出错返回-1。 struct utimebuf{ time_t actime; /* access time */ time_t modtime; /* modification time */ }
如果times是一个空指针,则将该文件访问和修改时间两者都设为当前时间,且必须满足进程有效ID等于文件所有者ID,或进程对该文件具有写权限;如果times是非空指针,则将更改为times所指的时间,且必须满足进程有效ID等于文件所有者ID,或进程是超级用户进程。
mkdir
和rmdir
函数
用mkdir创建目录,rmdir删除目录#include <sys/stat.h> int mkdir(const char* pathname, mode_t mode); 返回值:若成功返回0,出错返回-1。 #include <unistd.h> int rmdir(const char* pathname); 返回值:若成功返回0,出错返回-1。
对目录有访问权限的任何一用户都可以读目录,但是只有内核能写目录。目录的写权限和执行权限决定能否在该目录创建和删除文件,但不表示能否更改目录本身。
当前工作目录是进程的一个属性,可以通过调用chdir
和fchdir
函数更改当前工作目录。#include <unistd.h> int chdir(const char* pathname); int fchdir(int filedes); 返回值:若成功返回0,出错返回-1
内核为每个进程只保存指向当前工作目录v结点的指针等目录本身的信息,并不保存该目录的完整路径名。要获取目录的完整路径名,可以使用
getcwd
函数#include <unistd.h> char * getcwd(char* buf, size_t size); 返回值:若成功则返回buf,出错返回-1