【TLPI读书笔记】 十五、文件属性
获取文件信息:stat()
利用系统调用stat()
、lstat()
以及fstat
,可获取与文件有关的信息,其中大部分提取自文件i节点
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
All return 0 on success, or -1 on error
以上3个系统调用之间仅有的区别在于对文件的描述方式不同
stat()
会返回所命名文件的相关信息lstat()
与stat()
类似,区别在于如果文件属于符号链接,那么所返回的信息针对的是符号链接自身fstat()
则会返回由某个打开文件描述符所指代文件的相关信息参数
pathname
:指定文件路径fd
:指定文件描述符statbuf
:用来存储文件信息的缓冲区地址,为stat
结构:struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ time_t st_atime; /* Time of last access */ time_t st_mtime; /* Time of last modification */ time_t st_ctime; /* Time of last status change */ };
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
下面对stat
结构的一些重要字段加以说明:
- 设备ID和i节点号
st_dev
字段标识文件所驻留的设备。st_ino
字段则包含了文件的i节点号。 - 文件所有权
st_uid
和st_gid
字段分别标识文件的属主和属组。 - 链接数
st_nlink
字段包含了指向文件的(硬)链接数。 - 文件类型及权限
st_mode
字段内含有位掩码,起标识文件类型和指定文件权限的双重作用。与常量S_IFMT
相与(&
),可从该字段中解析文件类型。 文件大小、已分配块以及最优I/O块大小
- 对于常规文件,
st_size
字段表示文件的字节数。对于符号链接,则表示链接所指路径名的长度,以字节为单位。对于共享内存对象,该字段则表示对象大小。 st_blocks
字段表示实际分配给文件的总块数,块大小为512字节,其中包括了为指针块所分配的空间。现代UNIX使用更大尺寸的逻辑块,st_blocks
的取值总是2、4、8的倍数。st_blksize
字段所指并非底层文件系统的块大小,而是针对文件系统上文件进行I/O操作时的最优块大小(以字节为单位)。一般而言st_blksize
的值为4096
- 对于常规文件,
- 文件时间戳
st_atime
、st_mtime
和st_ctime
字段分别记录了对文件的上次访问时间、上次修改时间、以及文件状态发生改变的上次时间。这3个字段的类型均属于time_t
,是标准的UNIX时间格式。
文件时间戳
stat
结构的st_atime
、st_mtime
和st_ctime
字段所含为文件时间戳,分别记录了对文件的上次访问时间、上次修改时间以及文件状态上次发生变更的时间。
使用utime()
和utimes()
来改变文件时间戳
使用utime()
或与之相关的系统调用集之一,可显式改变存储于文件i节点中的文件上次访问时间戳和上次修改时间戳
#include <utime.h>
int utime(const char *pathname, struct utimbuf *buf);
Return 0 on success, or -1 on error
参数
pathname
:标识欲修改时间的文件。若为符号链接会进一步解除引用buf
:指定需要修改数据的值。为utimbuf
结构指针,可为NULL
则置为当前时间
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
使用utimensat()
和futimens()
改变文件时间戳
utimensat()
系统调用和futimens()
库函数为设置对文件的上次访问和修改时间戳提供了扩展功能。优点如下:
- 可按纳秒级精度设置时间戳
- 可独立设置某一时间戳
- 可独立将任一时间戳置为当前时间
utimensat()
系统调用会把由pathname
指定文件的时间戳更新为由数组times指定的值。
#define _XOPEN_SOURCE 700
#include <sys/stat.h>
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
Returns 0 on success, or -1 on error
参数
dirfd
:目录的文件描述符pathname
:标识欲修改时间的文件times
:指定需要修改数据的值。为timespec
结构数组,可为NULL
则将以上两文件都置为当前时间flags
:指定是否为符号链接解引用,为0
或者AT_SYMLINK_NOFOLLOW
则不会解引用
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
使用futimens()
库函数可更新打开文件描述符fd所指代文件的各个文件时间戳
#define _GNU_SOURCE
#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
Returns 0 on success, or -1 on error
参数与utimensat()
所对应参数的用法相同
文件属主
每个文件都有一个与之关联的用户ID(UID)和组ID(GID),藉此可以判定文件的属主和属组
新建文件的属主
文件创建时,其用户ID“取自”进程的有效用户ID。而新建文件的组ID则“取自”进程的有效组ID(等同于System V系统默认行为)或父目录的组ID(BSD系统行为)。
此外,其行为还受文件系统装配选项影响。
改变文件属主:chown()
、fchown()
和lchown()
系统调用chown()
、fchown()
和lchown()
可用来改变文件的属主和属组
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
#=====================================================================
#define _XOPEN_SOURCE 500
#include <unistd.h>
int lchown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
All returns 0 on success, or -1 on error
以上3个系统调用的区别类似stat()
一族:
chown()
改变指定的文件所有权lchown()
与chown()
相同,不同在于若参数指定为符号链接不会解引用而操作符号链接本身fchown()
同样改变文件所有权,只是指定文件的参数由文件描述符指定参数
pathname
:指定文件路径fd
:指定文件描述符owner
:指定属主(UID)group
:指定属组(GID)
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
文件权限
普通文件的权限
stat
结构中st_mod
字段的低12位定义了文件权限。其中的前3位为专用位,分别是set-user-ID
位、set-group-ID
位和sticky
位。其余9位则构成了定义权限的掩码,分别授予访问文件的各类用户。
文件权限掩码分为三类:
- Owner:属主
- Group:属组
- Other:其他人
可为每一类用户授予的权限如下:
- Read:可读取文件内容
- Write:可更改文件内容
- Execute:可执行文件
目录权限
目录与文件拥有相同的权限方案,只是对3种权限的含义另有所指。
- Read:可列出目录之下的内容(仅仅是文件名列表)
- Write:可在目录内创建、删除文件(需要同时拥有 Execute 权限,且操作的仅仅是文件,不涉及文件内容)
- Execute:可访问目录中的文件(可查看文件内容,即使没有 Read 权限)
访问文件时,需要拥有对路径名所列所有目录的执行权限。
权限检查算法
检查文件权限时,内核所遵循的规则如下:
- 对于特权级进程,授予其所有访问权限
- 若进程的有效用户ID与文件的用户ID(属主)相同,内核会根据文件的属主授权,授予进程相应的访问权限
- 若进程的有效组ID或任一附属组ID与文件的组ID(属组)相匹配,内核会根据文件的属组权限,授予进程对文件的相应访问权限
- 若以上三点皆不满足,内核会根据文件的其他人(other)权限,授予进程相应权限
内核会依次执行上述检查,只要匹配检查规则之一,便会停止检查。
检查对文件的访问权限:access()
系统调用access()
就是根据进程的真实用户ID和组ID(以及附属组ID),去检查对pathname
参数所指定文件的访问权限
#include <unistd.h>
int access(const char *pathname, int mode);
Returns 0 if all permissions are granted, otherwise -1
参数
pathname
:指定文件mode
:指定所要检查的权限位掩码,用指定常量相或(|
)而成的值
返回值
- 成功:返回
0
(即满足权限) - 失败:返回
-1
(即不满足权限或者发生错误)
- 成功:返回
由于对某一文件调用
access()
与对同一文件的后续操作之间存在时间差,因此(不论间隔多么短暂)执行后续操作时,也无法保证在对文件对后续操作时由access()
所返回的信息依然正确。所以建议杜绝使用access()
。
Set-User-ID、Set-Group-ID 和 Sticky 位
除了9位用来表明属主、属组和其他用户的权限之外,文件权限掩码还另设有3个附加位,分别为Set-user-ID
(bit 0400)、Set-group-ID
(bit 0200) 和Sticky
(bit 0100)位。
Set-group-ID
位还有其他两种用途:对于在nogrpid
选项装配的目录下所新建的文件,控制其群组从属关系;还可用于强制锁定文件Sticky
位,在老的UNIX实现中,目的是为了常用程序的运行速度更快;但在现代UNIX实现中,作用于目录时,Sticky
权限位起限制删除位的作用。- 为目录设置该位,则表明仅当非特权进程具有对目录的写权限,且为文件的属主时,才能对目录下的文件进行删除(
unlink()、rmdir()
)和重命名(rename()
)操作。 - 可藉此机制来创建为多个用户共享一个目录,各个用户可在其下创建或删除属于自己的文件,但不能删除隶属于其他用户的文件。
- 可通过
chmod
命令(chmod +t file
)或chmod()
系统调用来设置文件的Sticky
权限位。
- 为目录设置该位,则表明仅当非特权进程具有对目录的写权限,且为文件的属主时,才能对目录下的文件进行删除(
进程的文件模式创建掩码:umask()
对于新建文件(open()
)或目录(mkdir()
),都会有一个mode
参数来指定文件的权限。然而文件模式创建掩码(简称umask
)会对这个设置进行修改,umask
是一种进程属性,当进程新建文件或目录时,该属性用于指明应屏蔽哪些权限位。
系统调用umask()
将进程的umask
改变为mask
参数所指定的值(原值是继承自其父 shell)
#include <sys/stat.h>
mode_t umask(mode_t mask);
Always successfullly returns the previous process umask
参数mask
可以以八进制或指定常量相或(|
)来指定;该系统调用总会成功,并返回进程的前一 umask。
更改文件权限:chmod()
和fchmod()
可利用系统调用chmod()
和fchmod()
去修改文件权限
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
#define _OPEN_SOURCE 500
#include <sys/stat.h>
int fchmod(int fd, mode_t mode);
Both return 0 on success, or -1 on error
两系统调用的区别仅仅是指定文件的方式不同。
上述系统调用修改权限默认是针对整个所有权限位,如果要修改特定权限位,需先调用stat()
来获取文件的现有权限,调整想要修改的权限位,然后使用该系统调用去更新权限。
i 节点标志
某些Linux文件系统允许为文件和目录设置各种各样的i-node flags
(I 节点标志)。该特性是一种非标准的Linux扩展功能。
- 在 shell 中,可通过执行
chattr
和lsattr
命令来设置和查看 i 节点标志 - 在程序中,可利用
ioctl()
系统调用来获取并修改 i 节点标志
总结
本文主要介绍的内容是文件属性,然后由此展开文件权限。