UNIX环境高级编程之文件和目录
函数stat、fstat、fstatat和lstat
函数原型
#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);
返回值
所有4个函数的返回值:若成功,返回0;若出错,返回-1。
功能
- stat函数将pathname确定的文件的信息写入buf中。
- fstat函数将fd确定的文件的信息写入buf中。
- lstat与stat类似,但是当文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的的文件的信息。
- fstatat函数为一个相对于当前打开目录(fd参数指向的)的路径名返回统计信息
- 当fstatat函数的flag参数被设置为AT_SYMLINK_NOFOLLOW时,fstatat函数不跟踪符号链接(默认跟随符号链接)。
- 如果fd参数的值是AT_FDCWD,并且pathname参数是一个相对路径名,fstatat会计算相对于当前目录的pathname参数。如果pathname是一个绝对路径,fd参数会被忽略。
stat结构体的结构
struct stat
{
mode_t st_mode; //文件的类型和访问权限权限
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号
dev_t st_rdev; //特殊文件设备号
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //文件最后被访问的时间
time_t st_mtime; //文件内容最后被修改的时间
time_t st_ctime; //文件状态改变时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //内容对应的块数量
};
文件类型
文件类型 | 说明 |
---|---|
普通文件 | 最常见的文件类型,包含了某种形式的数据。 |
目录文件 | 包含了其他文件的名字以及指向这些文件有关信息的指针。对于一个目录文件具有读权限的任意进程都可以读该目录的内容,但只有内核可以写目录文件。 |
块特殊文件 | 提供对设备(如磁盘)带缓冲的访问,每次访问以固定的长度为单位进行。 |
字符特殊文件 | 提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。 |
FIFO | 用于进程间通信。有时也被称为命名管道。 |
套接字 | 用于进程间的网络通信。 |
符号链接 | 这种类型的文件指向另一个文件。 |
文件类型信息包含在stat结构的st_mode成员中。可以用以下的宏确定文件类型(宏的参数是st_mode):
宏 | 文件类型 |
---|---|
S_ISREG() | 普通文件 |
S_ISDIR() | 目录文件 |
S_ISCHR() | 字符特殊文件 |
S_ISBLK() | 块特殊文件 |
S_ISFIFO() | 管道或FIFO |
S_ISLNK() | 符号链接 |
S_ISSOCK() | 套接字 |
使用以下宏来确定IPC对象的类型(参数是指向stat结构的指针):
宏 | 对象的类型 |
---|---|
S_TYPEISMQ() | 消息队列 |
S_TYPEISSEM() | 信号量 |
S_TYPEISSHM() | 共享存储对象 |
可以使用<sys/stat.h>头文件中的屏蔽字S_IFMT与st_mode进行“与”运算,然后与S_IFxxx宏进行比较来判断是否为某种文件类型。例如,S_ISDIR()的定义是:
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
设置用户ID和设置组ID
与一个进程相关的ID至少有6个:
- 实际用户ID、实际组ID:我们实际上是谁。这两个字段在登录时取自口令文件中的登录项。通常,在一个登录会话期间这些值不会改变。
- 有效用户ID、有效组ID、附属组ID:用于文件访问权限检查。
- 保存的设置用户ID、保存的设置组ID:由exec函数保存。保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。
通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
每个文件有一个所有者和组所有者,所有者由stat结构中的st_uid指定,组所有者有stat结构中的st_gid指定。
文件访问权限
st_mode值包含了对文件的访问权限位。
st_mode屏蔽 | 含义 |
---|---|
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IROTH | 其他读 |
S_IWOTH | 其他写 |
S_IXOTH | 其他执行 |
- 当我们用名字打开任一类型的文件时,对该名字包含的每一个目录,包括他可能隐含的当前工作目录都应具有执行权限。如果PATH环境变量指定了一个我们不具有执行权限的目录,那么shell绝不会在该目录下找到可执行文件
- 对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作。
- 对于一个文件的写权限决定了我们是否能够打开现有文件进行写操作。
- 为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限。
- 为了在一个目录中创建一个新文件,必须对目录具有写权限和执行权限。
- 为了删除一个现有文件,必须对目录具有写权限和执行权限。
- 如果用7个exec函数中的任意一个执行某个文件,都必须对该文件具有执行权限。该文件还必须是普通文件。
内核对文件访问测试的步骤:
- 如果进程的有效ID是0(超级用户),则允许访问。
- 若进程的有效用户ID等于文件的所有者ID,那么如果所有者适当的访问权限被设置,则允许访问;否则拒绝访问。
- 若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问;否则拒绝访问。
- 若其他用户适当的访问权限位被设置,则允许访问;否则拒绝访问。
新文件和目录所有权
- 新文件的用户ID设置为进程的有效用户ID
- 新文件的组ID可以是进程的有效组ID
- 新文件的组ID可以是它所在目录的组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就设为F_OK;否则mode是下表所列常量(位于<unistd.h>头文件中)的按位或:
mode | 说明 |
---|---|
R_OK | 测试读权限 |
W_OK | 测试写权限 |
X_OK | 测试执行权限 |
faccessat函数与access函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。否则,faccessat计算相对于打开目录(由fd参数指向)的pathname。
flag参数设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID。
函数umask
用途
为进程设置文件模式创建屏蔽字,并返回之前的值。
函数原型
#include <sys/stat.h>
mode_t umask(mode_t cmask);
返回值
返回之前的文件模式创建屏蔽字。
参数
参数cmask由上上节表中的常量按位或构成的。
函数chmod、fchmod和fchmodat
用途
更改现有文件的访问权限
函数原型
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(inr fd, mode_t, mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
用法
-
chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。fchmodat函数与chmod函数在下面两种情况下是相同的:一种是pathname参数为绝对路径,另一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname。当flag参数AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。
-
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。
-
参数mode是下表常量的按位或:
mode 说明 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 其他执行 -
chmod函数更新的只是i节点最近一次被修改的时间。按系统默认方式,ls -l列出的是最后修改文件内容的时间。
粘着位
S_ISVTX位被称为粘着位。如果一个可执行程序文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保留在交换区(程序的正文部分是机器指令)。
函数chown、fchowm、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);
int lchown(const char *pathname, uid_t owner, gid_t group);
返回值
若成功,返回0;若失败,返回-1。
说明
- 在符号链接的情况下,lchown和fchownat在设置AT_SYMLINK_NOFOLLOW时更改符号链接本身的所有者。
- 如果owner或group中的任意一个是-1,则对应的ID不变。
文件长度
stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。
对于目录,文件长度通常是一个数(16或512)的整数倍。
对于符号链接,文件长度是文件名中的实际字节数。
大多数现代的UNIX系统提供字段st_blksize和st_block。其中第一个是对文件I/O较合适的块长度,第二个是所分配的实际512字节块块数。
文件截断
函数truncate和ftruncate
- 函数原型
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
- 功能
将现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的数据就不再被访问。如果该文件以前的长度小于length,则形成文件空洞。 - 返回值
若成功,返回0,;若出错,返回-1。
函数link、linkat、unlink、unlinkat和remove
函数link和linkat
用途
创建一个指向现有文件的链接的方法。
函数原型
#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);
返回值
若成功,返回0,;若出错,返回-1。
描述
- 这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
- 对于linkat函数,现有文件通过efd和existingpath参数确定,新的路径名是通过nfd和newpath确定的。
- flag参数在设置AT_SYMLINK_FOLLOW标志时创建指向符号链接目标的链接,否则创建指向符号链接本身的链接。
函数unlink和unlinkat
用途
删除一个现有的目录项。
函数原型
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
返回值
若成功,返回0,;若出错,返回-1。
unlink的特性
进程用open或creat创建一个文件,然后立即调用unlink,因为该文件仍旧是打开的,所以不会将其内容删除。只有进程关闭该文件或终止时,该文件的内容才被删除。
remove函数
用途
解除对一个文件或目录的链接。
函数原型
#include <stdio.h>
int remove(const char *pathname);
返回值
若成功,返回0,;若出错,返回-1。
函数rename和renameat
用途
文件或目录可以用rename函数或者renameat函数进行重命名。
函数原型
#include <stdio.h>
int rename(const char *oldname, const name *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
返回值
若成功,返回0;若失败,返回-1。
用法
- 如果oldname指的是一个文件而不是一个目录,那么为该文件或符号链接重命名。在这种情况下,如果newname已存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则将该目录项删除后将oldname重命名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限。
- 如果oldname指的是一个目录,那么为该目录重命名。如果newname已经存在,则它必须引用一个空目录。这两个函数先将空目录删除,再将oldname重命名为newname。newname不能包含oldname作为其前缀路径。
- 如果oldname或newname引用符号链接,则处理的是符号链接本身。
- 不能对.和…重命名。
- 如果oldname和newname相同,则函数不作任何更改并返回
- renameat函数中,oldname是相对于fd的相对路径,newname是相对于newfd的相对路径。当oldfd或者newfd被设置为AT_FDCWD时,相对于当前目录计算相对路径。
创建和读取符号链接
函数symlink和symlinkat
用途
创建一个符号链接
函数原型
#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。在创建符号链接时,并不要求actualpath已经存在。并且actualpath和sympath不需要位于同一文件系统中。
函数readlink和readlinkat函数
用途
打开符号链接本身,并读该链接的名字。
函数原型
#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。
说明
buf中的内容不以null字节终止。
文件的时间
对每个文件维护3个时间字段。
字段 | 说明 | 例子 | ls(1)选项 |
---|---|---|---|
st_atim | 文件数据的最后访问时间 | read | -u |
st_mtim | 文件数据的最后修改时间 | write | 默认 |
st_ctim | i节点状态的最后修改时间 | chmod、chown | -c |
函数futimens、utimensat和utimes
用途
更改文件的访问和修改时间。
函数原型
#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);
#include <sys/time.h>
int utime(const char *pathname, const struct timeval times[2]);
返回值
若成功,返回0;若出错,返回-1。
说明
- futimens函数和utimensat函数的times数组参数的第一个元素包含访问时间,第二个元素包含修改时间。
- 时间戳用以下四种方式之一指定:
如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间。
如果times参数指向两个timespec结构的数组,任意数组元素的tv_nsec字段的值为U_TIME_NOW,相应的时间戳就设置为当前时间,忽略相应的tv_nsec字段。
如果times参数指向两个timespec结构的数组,任意数组元素的tv_nsec字段的值为U_TIME_OMIT,相应的时间戳保持不变,忽略相应的tv_nsec字段。
如果times参数指向两个timespec结构的数组,且tv_nsec字段的值既不是U_TIME_NOW也不是U_TIME_OMIT,在这种情况下,相应的时间戳设置为相应的tv_sec和tv_nsec字段。
函数mkdir、mkdirat和redir
用途
mkdir和mkdirat用于创建目录,rmdir用于删除空目录。
函数原型
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirar(int fd, const char *pathname, mode_t mode)
#include <unistd.h>
int rmdir(const char *pathname);
返回值
若成功,返回0;若出错,返回-1。
读目录
函数原型
#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
// 两个函数返回值:若成功,返回指针;若出错,返回NULL
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);
说明
由opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项。目录中的各目录项的顺序与实现有关。它们通常并不按字母顺序排列。
函数chdir、fchdir和getcwd
函数chdir和fchdir
用途
改变当前工作目录
函数原型
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
返回值
若成功,返回0;若出错,返回-1。
函数getcwd
用途
获取当前工作目录
函数原型
#include <unistd.h>
char *getcwd(char *buf, size_t size);
返回值
若成功,返回buf;若出错,返回NULL。
设备特殊文件
- 每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序;次设备号标识特定的子设备。
- 系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点。
- 只有字特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号。
文件访问限制位小结
常量 | 说明 | 对普通文件的影响 | 对目录的影响 |
---|---|---|---|
S_ISUID | 设置用户ID | 执行时设置有效用户ID | 未使用 |
S_ISGID | 设置组ID | 若组执行位设置,则执行时设置有效组ID;否则使强制性锁起作用(若支持) | 将在目录中创建的新文件的组ID,设置为目录的组ID |
S_ISVTX | 粘着位 | 在交换区缓存程序正文(若支持) | 阻止在目录中删除和重命名文件 |
S_IRUSR | 用户读 | 许可用户读文件 | 许可用户读目录项 |
S_IWUSR | 用户写 | 许可用户写文件 | 许可用户在目录中删除和创建文件 |
S_IXUSR | 用户执行 | 许可用户执行文件 | 许可用户在目录中搜索指定路径名 |
S_IRGRP | 组读 | 许可组读文件 | 许可组读目录项 |
S_IWGRP | 组写 | 许可组写文件 | 许可组在目录中删除和创建文件 |
S_IXGRP | 组执行 | 许可组执行文件 | 许可组在目录中搜索指定路径名 |
S_IROTH | 其他读 | 许可其他读文件 | 许可其他读目录项 |
S_IWOTH | 其他写 | 许可其他写文件 | 许可其他在目录中删除和创建文件 |
S_IXOTH | 其他执行 | 许可其他执行文件 | 许可其他在目录中搜索指定路径名 |