APUE读书笔记(二)
4 文件和目录
函数stat、fstat、fstatat和lstat
获得指定文件的文件属性结构stat
:
#include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict buf); int fstat(int fd, struct stat *buf); int lstat(const char *restrict pathname, struct stat *restrict buf);//不跟随符号链接 int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);//flag设置AT_SYMLINK_NOFOLLOW时不跟随符号链接 //返回值:成功则返回0,出错则返回-1
文件属性结构stat
其基本形式是:
struct stat { mode_t st_mode;/* file type & mode(permission) */ 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 */ struct timespec st_atime;/* time of last access */ struct timespec st_mtime;/* time of last modification */ struct timespec 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 */ }
timespec
结构类型按照秒和纳秒定义了时间(至少包括下面两个字段):
time_t tv_sec; long tv_nsec;
使用stat
函数最多的地方可能就是ls -l
命令,用其可以获得一个文件的所有信息。
文件类型
UNIX系统中的文件类型:
- 普通文件;
- 目录文件;
- 块特殊文件;
- 字符特殊文件;
- FIFO,有时也称为命名管道;
- 套接字;
- 符号链接。
文件类型信息包含在stat
结构的st_mode
成员中,可用<sys/stat.h>中的文件类型宏来检查。
POSIX.1允许实现将进程间通信(IPC)对象说明为文件(消息队列、信号量与共享存储对象),可以用<sys/stat.h>中的IPC类型宏来检测。
设置用户ID和设置组ID
与一个进程相关联的ID有6个或更多:
ID | 意义 |
---|---|
实际用户ID、实际组ID | 我们实际上是谁 |
有效用户ID、有效组ID、附属组ID | 用于文件访问权限检查 |
保存的设置用户ID、保存的设置组ID | 由exec函数保存 |
每个文件有一个所有者和组所有者,所有者和组所有者分别由stat
中的st_uid
和st_gid
指定。
当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,可在st_mode
中设置一个特殊标志,使得“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户IDst_uid
”,设置组所有者ID与此类似,这两位被称为设置用户ID位和设置组ID位。
文件的设置用户ID位及设置组ID位都包含在stat
的st_mode
中,这两位可分别用常量S_ISUID
和S_ISGID
测试。
文件访问权限
每个文件有9个访问权限位(取自<sys/stat.h>):
st_mode屏蔽 | 含义 |
---|---|
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IROTH | 其他读 |
S_IWOTH | 其他写 |
S_IXOTH | 其他执行 |
- 对于目录其执行权限位常被称为搜索位的原因:打开任一类型的文件时,对该绝对路径的每一级都应具有执行权限。
- 在
open
函数中指定O_TRUNC
标志必须对该文件具有写权限。 - 在一个目录中创建一个新文件必须对该目录具有写权限和执行权限。
- 为删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限,而不需要对该文件本身有读、写权限。
- 用7个exec函数中的任何一个执行某个文件的条件:必须对该文件具有执行权限、该文件必须是一个普通文件。
进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试。
新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID,新文件的组ID可以是进程的有效组ID或它所在目录的组ID(使用POSIX.1所允许的第二个选项即继承目录的组ID)。
函数access和faccessat
按其实际用户ID和实际组ID来测试对文件是否有访问权限:
#include <unistd.h> int access(const char *pathname, int mode); int faccessat(int fd, const char *pathname, int mode, int flag); //返回值:成功则返回0,出错则返回-1 /* mode(取自<unistd.h>): F_OK为测试是否存在; R_OK为测试读权限; W_OK为测试写权限; X_OK为测试执行权限 */ //如果flag设置AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID
函数umask
为进程设置文件模式创建屏蔽字:
#include <sys/stat.h> mode_t umask(mode_t cmask); //返回值:之前的文件模式创建屏蔽字 /* cmask访问权限位: 0400为用户读; 0200为用户写; 0100为用户执行; 0040为组读; 0020为组写; 0010为组执行; 0004为其他读; 0002为其他写; 0001为其他执行 */
在进程创建一个新文件或新目录时,就一定会使用文件模式创建屏蔽字。
函数chmod、fchmod和fchmodat
更改现有文件的访问权限:
#include <sys/stat.h> int chmod(const char *pathname, mode_t mode); int fchmod(int fd, mode_t mode); int fchmodat(int fd, const char *pathname, mode_t mode, int flag);//flag设置AT_SYMLINK_NOFOLLOW时则不会跟随符号链接 //返回值:成功则返回0,出错则返回-1 /* mode(取自<sys/stat.h>): S_ISUID为执行时设置用户ID; S_ISGID为执行时设置组ID; S_ISVTX为保存正文(粘着位); S_IRWXU为用户(所有者)读、写和执行; S_IRUSR为用户(所有者)读; S_IWUSR为用户(所有者)写; S_IXUSR为用户(所有者)执行; S_IRWXG为组读、写和执行; S_IRGRP为组读; S_IWGRP为组写; S_IXGRP为组执行; S_IRWXO为其他读、写和执行; S_IROTH为其他读; S_IWOTH为其他写; S_IXOTH为其他执行 */
为改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。
粘着位
S_ISVTX
位被称为粘着位(即保存正文位):如果一个可执行文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区,这使得下次执行该程序时能较快地将其装载入内存(现今较新的UNIX系统大多数都配置了虚拟存储系统以及快速文件系统,所以不再需要使用这种技术)。
如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足拥有此文件或拥有此目录或是超级用户,才能删除或重命名该目录下的文件。
函数chown、fchown、fchownat和lchown
更改文件的用户ID和组ID:
#include <unistd.h> int chown(const char *pathname, uid_t owner, gid_t group); int fchown(int fd, uid_t owner, gid_t group); int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);//flag设置AT_SYMLINK_NOFOLLOW则不跟随符号链接 int lchown(const char *pathname, uid_t owner, gid_t group);//不跟随符号链接 //返回值:成功则返回0,出错则返回-1 //owner和group中有一个是1则对应ID不变
文件长度
stat
中的st_size
表示以字节为单位的文件的长度(只对普通文件、目录文件和符号链接有意义)。
普通文件:文件长度可以是0,在开始读时将得到文件结束(end-of-file)指示。
目录:文件长度通常是一个数(如16或512)的整倍数。
符号链接:文件长度是在文件名中的实际字节数(不包括null字节)。
现今大多数现代的UNIX系统提供字段st_blksize
(对文件I/O较合适的块长度)和st_blocks
(所分配的实际512字节块块数)。
文件截断
在打开文件时使用O_TRUNC
可以将一个文件长度截断为0。
截断文件:
#include <unistd.h> int truncate(const char *pathname, off_t length); int ftruncate(int fd, off_t length); //返回值:成功则返回0,出错则返回-1 //将一个现有文件长度截断为length //若长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是在文件中创建了一个空洞)
文件系统
磁盘分成多个分区,每个分区包含一个文件系统,每个文件系统由自举块、超级块和多个柱面组组成,每个柱面组中有超级块副本、配置信息、i节点图、块位图、多个i节点和多个数据块。
硬链接:每个i节点中都有一个链接计数(指向该i节点的目录项数),减少至0时才可删除文件(释放该文件占用的数据块)。
符号链接:文件的实际内容包含了该符号链接所指向的文件的名字。
i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等,stat
中的大多数信息都取自i节点(只有两项重要数据存放在目录项中:文件名和i节点编号)。
一个目录项不能指向另一个文件系统的i节点。
在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动(构造一个指向现有i节点的新目录项,并删除老的目录项,链接计数不会改变)。
函数link、linkat、unlink、unlinkat和remove
创建一个指向现有文件的链接(指向文件i节点):
#include <unistd.h> int link(const char *existingpath, const char *newpath); int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);//flag设置AT_SYMLINK_NOFOLLOW则不跟随符号链接 //返回值:成功则返回0,出错则返回-1 //newpath引用existingpath //newpath已经存在则返回出错,newpath中除最后一个分量外路径中的其他部分应当已经存在
创建新目录项和增加链接计数是一个原子操作。
删除一个现有的目录项:
#include <unistd.h> int unlink(const char *pathname); int unlinkat(int fd, const char *pathname, int flag);//flag设置AT_REMOVEDIR时可删除目录 //两个函数的返回值:若成功,返回0;若出错,返回-1 //删除目录项,并将文件的链接计数减1,只有当链接计数达到0时,该文件的内容才可被删除 //为解除对文件的链接必须对包含该目录项的目录具有写和执行权限
没有一个函数能删除由符号链接所引用的文件。
解除对一个文件或目录的链接:
#include <stdio.h> int remove(const char *pathname); //返回值:成功则返回0,出错则返回-1
函数rename和renameat
对文件或目录进行重命名:
#include <stdio.h> int rename(const char *oldname, const char *newname); int renameat(int oldfd, const char *oldname, int newfd, const char *newname); //返回值:成功则返回0,出错则返回-1 /* oldname非目录则为其重命名,newname存在(不能是目录)则先删除,调用进程对oldname与newname所在目录必须有写和执行权限; oldname为目录则为其重命名,newname存在(必须是空目录)则先删除,newname不能包含oldname作为其路径前缀 */ //newname存在则调用进程对它要有写权限 //不跟随符号链接
符号链接
对符号链接以及它指向何种对象并无任何文件系统限制,一般用于将一个文件或整个目录结构移到系统中另一个位置。
创建和读取符号链接
创建符号链接(是对一个文件的间接指针):
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); int symlinkat(const char *actualpath, int fd, const char *sympath); //返回值:成功则返回0,出错则返回-1 //actualpath和sympath不需要位于同一文件系统中
打开符号链接本身并读取该链接中的名字(因为open
跟随符号链接):
#include <unistd.h> ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize); ssize_t readlinkat(int fd, const char *restrict pathname, char *restrict buf, size_t bufsize); //返回值:成功则返回读取的字节数,出错则返回-1 //组合了open、read和close的所有操作 //在buf中返回的符号链接的内容不以null字节终止
文件的时间
与每个文件相关的3个时间字段:
字段 | 说明 | 例子 | ls(1)选项 |
---|---|---|---|
st_atime | 文件数据的最后访问时间(最后一次被访问的时间) | read | -u |
st_mtime | 文件数据的最后修改时间(最后一次被修改的时间) | write | 默认 |
st_ctime | i节点状态的最后更改时间(i节点最后一次被修改的时间) | chmod、chown | -c |
有很多影响i到节点的操作(如更改文件的访问权限、更改用户ID、更改链接数等),但它们并没有更改文件的实际内容。
函数futimens、utimensat和utimes
更改一个文件的访问和修改时间(包含在POSIX.1中):
#include <sys/stat.h> int futimens(int fd, const struct timespec times[2]); int utimensat(int fd, const char *path, const struct timespec times[2], int flag);//flag设置AT_SYMLINK_NOFOLLOW则不跟随符号链接 //返回值:成功则返回0,出错则返回-1 //times[0]表示访问时间,times[1]表示修改时间 /* times为空则两时间值改为当前时间; times任一元素tv_nsec为UTIME_NOW则其设为当前时间; times任一元素tv_nsec为UTIME_OMIT则其不变; 否则按照设置值设置 */
更改文件时间(包含在Single UNIX Specification的XSI扩展选项中):
#include <sys/time.h> int utimes(const char *pathname, const struct timeval times[2]); //返回值:成功则返回0,出错则返回-1 //times[0]表示访问时间,times[1]表示修改时间
两个时间戳是用秒和微秒表示的:
struct timeval { time_t tv_sec;/* seconds */ long tv_usec;/* microseconds */ };
不能更改st_ctime
(i节点最近被修改的时间,因为调用utimes
函数时,此字段会被自动更新)。
函数mkdir、mkdirat和rmdir
创建目录:
#include <sys/stat.h> int mkdir(const char *pathname, mode_t mode); int mkdirat(int fd, const char *pathname, mode_t mode); //返回值:成功则返回0,出错则返回-1 //对于目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名
删除一个空目录(将其链接计数减1):
#include <unistd.h> int rmdir(const char *pathname); //返回值:成功则返回0,出错则返回-1 //使目录的链接计数成为0且也没有其他进程打开此目录则释放由此目录占用的空间 //在链接计数达到0且有其他进程打开此目录则在最后一个进程关闭它之前并不释放此目录
读目录
目录项访问相关操作:
#include <dirent.h> DIR *opendir(const char *pathname); DIR *fdopendir(int fd); //返回值:成功则返回指针,出错则返回NULL //得到目录处理函数需要的DIR结构(打开目录) struct dirent *readdir(DIR *dp);//返回下一个目录项 //返回值:成功则返回指针,在目录尾或出错则返回NULL void rewinddir(DIR *dp);//重置偏移量 int closedir(DIR *dp);//关闭目录 //返回值:成功则返回0,出错则返回-1 long telldir(DIR *dp);//找到当前偏移量 //返回值:与dp关联的目录中的当前位置 void seekdir(DIR *dp, long loc);//设置偏移量
定义在头文件<dirent.h>中的dirent
结构与实现有关。实现对此结构所做的定义至少包含下列两个成员:
ino_t d_ino;/* i-node number */ char d_name[];/* null-terminated filename */ //d_name大小没有指定(它必须能包含至少NAME_MAX个字节,不包含null字节)
目录中各目录项的顺序与实现有关(通常不按字母排列)。
函数chdir、fchdir和getcwd
更改当前工作目录:
#include <unistd.h> int chdir(const char *pathname); int fchdir(int fd); //返回值:成功则返回0,出错则返回-1
内核为每个进程只保存指向该目录v节点的指针等目录本身的信息,并不保存该目录的完整路径名。
获得当前工作目录完整的绝对路径名(逐层上移,直到遇到根):
#include <unistd.h> char *getcwd(char *buf, size_t size); //返回值:成功则返回buf,出错则返回NULL //缓冲区地址为buf(必须足够长以容纳最后的一个终止null字节),缓冲区长度为size(单位是字节)
设备特殊文件
每个文件系统所在的存储设备都由其主、次设备号表示,设备号所用的数据类型是基本系统数据类型dev_t
,主设备号标识设备驱动程序(有时编码为与其通信的外设板),次设备号标识特定的子设备。
两个宏major
和minor
来访问主、次设备号,无需关心它们是如何存放在dev_t
对象中的。
系统中与每个文件名关联的st_dev
值是所在文件系统的设备号。
只有字符特殊文件和块特殊文件才有st_rdev
值(包含实际设备的设备号)。