List
文章目录
- List
- 获取文件信息
获取文件信息
获取文件信息相对而言是一个比较简单的过程,但是其中的设计其实比较的复杂,这涉及到了Linux的文件系统的设计
我想通过对这些信息的整理强化我对于文件系统的理解
文件信息结构体
在进行函数调用之前,先得对文件信息结构体进行一个了解,以便在函数的使用时可以知道我们可获取的文件信息
文件结构体如下所示
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
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; /* blocksize 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 */
};
我们一个个解释其含义
dev_t st_dev/* ID of device containing file */
- dev_t st_dev; /* ID of device containing file */
st_dev 存的是文件本身存储设备的设备号,也就是硬盘的设备号
我们可以通过df查看我们的设备信息
Blankspace@ubuntu:~/blankspace_Gnu/blankspace_bin$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 2.0G 4.0K 2.0G 1% /dev
tmpfs 394M 1.5M 392M 1% /run
/dev/sda1 36G 4.7G 29G 14% /
none 4.0K 0 4.0K 0% /sys/fs/cgroup
none 5.0M 0 5.0M 0% /run/lock
none 2.0G 152K 2.0G 1% /run/shm
none 100M 84K 100M 1% /run/user
- Filesystem:代表该文件系统时哪个分区,所以列出的是设备名称。
- 1K-blocks:说明下面的数字单位是1KB,可利用-h或-m来改变单位大小,也可以用-B来设置。
- Used:已经使用的空间大小。
- Avail(Available):剩余的空间大小。
- Use%:磁盘使用率。如果使用率在90%以上时,就需要注意了,避免磁盘容量不足出现系统问题,尤其是对于文件内容增加较快的情况(如/home、/var/spool/mail等)。
- Mounted on:磁盘挂载的目录,即该磁盘挂载到了哪个目录下面。
这个和Linux文件系统有关
《鸟哥私房菜》一书中有详细描述,有需要的可以进行一个阅读,在这就没有进行一个纪录
ino_t st_ino /* inode number */
- ino_t st_ino; /* inode number */
文件的INode号
文件数据存储在块中,那么还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种存储文件元信息的区域就叫做inode
谈到INode就不得不和block一起提一提
inode和block概述
我们知道,文件是存储在硬盘中的,硬盘最小的存储单位是扇区(sector),每个扇区存储 512字节
操作系统在读取硬盘是会采取一次性读入多个扇区,这多个扇区我们称之为一个块 (block)。
块的大小一般是4KB,也就是连续八个扇区(sector)组成一个块(block)。
文件数据存储在块(block)中,当然不能只存储而不能读取,如果不告诉操作系统文件的存储位置等信息,操作系统是无法进行读取的
举个例子大致就是,如果一个朋友想来找你,如果你只是回答:我住在房子里,这样的回答没有任何意义,而且可能会被朋友骂一顿,得需要告诉朋友你所住地址的详细信息,这里也类似,硬盘也需要告诉操作系统文件存储的位置,硬盘中会有一块记录着文件的元信息的区域,也就是我们所说的INode,中文译名索引节点
这里需要注意的是
一个文件必须占用一个iNode,iNode也至少会占用一个block
大致的关系是
- 元信息存储的区域 -------> iNode
- 数据存储的区域 ---------->block
iNode的内容
iNode包括很多的内容
大致如下
- 文件类型: 普通文件,目录,管道等等
- 权限:可读,可写,可执行
- 链接数:链接到该inode的硬链接数
- User ID:文件所有者
- Group ID:所有者组ID
- 文件大小
- 时间信息
- 属性:比如,不可改变位
- 访问控制列表
- 文件数据存储的实际位置
- 其他元数据
注意iNode并不包含文件名,因为Linux系统内部不使用文件名来查找文件,而是使用iNode号码来识别文件
对于文件系统来说,文件名只是iNode号便于识别的别称而已
并且硬盘分区的INode总数在格式化后就已经固定,而每个文件必须有一个iNode,所以如果 iNode耗尽,即使硬盘中还剩余存储空间,也无法新建文件
mode_t st_mode /* protection */
st_mode 用来判断文件类型和存储权限
st_mode内容详解
先来讲讲mode_t,其实就是一个无符号整型 unsigned int
但是整个st_mode只使用了后16位
st_mode主要包括三个部分
- 15bit ~ 12bit 保存文件类型
- : 普通文件(regular file)
d : 目录(directory)
c : 字符设备(character device)
b : 块设备(block device)
p : 管道(FIFO)
l : 符号链接文件(symbolic link)
s : 套接口文件(socket)
- 11bit ~ 9bit 保存执行文件时设置的信息
bit11:set-user-ID位,执行时设置用户ID
bit10:set-group-ID位,执行时设置组ID
bit9:sticky位,仅对目录有效。设置后所有用户都可在这个目录下创建文件,但该目录下的文件只能被owner和root删除。
- 8bit ~ 0bit 保存文件权限信息
st_mode字段的最低9位,代表文件的许可权限
标识了文件所有者(owner)、组用户(group)、其他用户(other)的读(r)、写(w)、执行(x)权限。
st_mode 宏详解
虽说我们已经了解了整个st_mode的设计
但是如果去手动分析st_mode的话,效率太低,且容易出错,
可使用 st_mode & 掩码来得到 st_mode 中特定的部分。比如:
st_mode & 0170000 : 得到文件类型
st_mode & 0007000 : 得到执行文件时设置信息
st_mode & 0000777 : 得到权限位
st_mode & 00100: 判断所有者是否可执行
这样还是比较麻烦于是设计了一系列的宏来对其进行判断
宏设计如下
#include <sys/stat.h>
//bit15 ~ bit12 , 文件类型属性区域
#define S_IFMT 0170000 文件类型的位遮罩(掩码)
#define S_IFSOCK 0140000 socket
#define S_IFLNK 0120000 符号链接(symbolic link)
#define S_IFREG 0100000 一般文件
#define S_IFBLK 0060000 区块装置(block device)
#define S_IFDIR 0040000 目录
#define S_IFCHR 0020000 字符装置(character device)
#define S_IFIFO 0010000 先进先出(fifo)
//提供了一些宏来帮助用户执行&操作,是则返回1
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
//bit11 ~ bit9,权限的特殊属性区域
#define S_ISUID 0004000 文件的(set user-id on execution)位
#define S_ISGID 0002000 文件的(set group-id on execution)位
#define S_ISVTX 0001000 文件的sticky位
//bit8 ~ bit0,权限属性区域
//文件所有者(owner)
#define S_IRWXU 00700 /* mask for file owner permissions */
#define S_IRUSR 00400 /* owner has read permission */
#define S_IWUSR 00200 /* owner has write permission */
#define S_IXUSR 00100 /* owner has execute permission */
//组用户(group)
#define S_IRWXG 00070 /* mask for group permissions */
#define S_IRGRP 00040 /* group has read permission */
#define S_IWGRP 00020 /* group has write permission */
#define S_IXGRP 00010 /* group has execute permission */
//其他用户(other)
#define S_IRWXO 00007 /* mask for permissions for others (not in group) */
#define S_IROTH 00004 /* others have read permission */
#define S_IWOTH 00002 /* others have write permission */
#define S_IXOTH 00001 /* others have execute permission */
nlink_t st_nlink; /* number of hard links */
连到该文件的硬链接数目, 刚建立的文件 值为1.
硬链接和软链接之间的区别
- 硬链接
通过文件系统的inode链接来产生的新的文件名,而不是产生新的文件,称为硬链接。
一般情况下,每个inode号码对应一个文件名,但是Linux允许多个文件名指向同一个inode号码。意味着可以使用不同的文件名访问相同的内容。
ln 源文件 目标
运行该命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中的链接数这时就会增加1。
当一个文件拥有多个硬链接时,对文件内容修改,会影响到所有文件名;但是删除一个文件名,不影响另一个文件名的访问。删除一个文件名,只会使得inode中的链接数减1。
需要注意的是不能对目录做硬链接。
通过mkdir命令创建一个新目录,其硬链接数应该有2个,因为常见的目录本身为1个硬链接,而目录下面的隐藏目录.(点号)是该目录的又一个硬链接,也算是1个连接数。
- 软链接
类似于Windows的快捷方式功能的文件,可以快速连接到目标文件或目录,称为软链接。
ln -s 源文件或目录 目标文件或目录
软链接就是再创建一个独立的文件,而这个文件会让数据的读取指向它连接的那个文件的文件名。例如,文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。这时,文件A就称为文件B的软链接soft link或者符号链接symbolic link。
这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode链接数不会因此发生变化。
uid_t st_uid; /* user ID of owner */
文件所有者的用户识别码
表示文件由谁创建,他与用户名唯一对应
我们可以使用
id
来查看我的用户名,UID,组名,GID等
与之对应的就是setuid,setuid可以改变这种设置
如果是一个可执行文件, 那么在执行时, 一般该文件只拥有调用该文件的用户(当前用户)具有的权限.
setuid: 设置使文件在执行阶段具有文件所有者的权限. 典型的文件是 /usr/bin/passwd. 如果一般用户执行该文件, 则在执行过程中, 该文件可以获得root权限, 从而可以更改用户的密码.
这个对应的命令为
chmod u+s temp -- 为temp文件加上setuid标志. (setuid 只对文件有效)
一般文件的权限通过三组八进制数字来标识,如
chmod 777 [filename]
chmod 666 [filename]
等等
而如果设置了特殊的标志位,则会在这组数字之外另加一组八进制数字,对应上述
bit11:set-user-ID位,执行时设置用户ID
bit10:set-group-ID位,执行时设置组ID
bit9:sticky位,仅对目录有效。设置后所有用户都可在这个目录下创建文件,**但该目录下的文件只能被owner和root删除**。
我们设置完这些标志后,可以使用ls-l来查看,如果有这些标志,则会在执行标志位置上显示
如:
rwsrw-r-- 表示有setuid标志
rwxrwsrw- 表示有setgid标志
rwxrw-rwt 表示有sticky标志
至于原有的执行标志X,他其实以另一种形式表示出来
系统是这样规定的, 如果本来在该位上有x, 则这些特殊标志显示为小写字母 (s, s, t). 否则, 显示为大写字母 (S, S, T)
相关函数
gid_t st_gid; /* group ID of owner */
文件所有者的组识别码
chmod g+s tempdir -- 为tempdir目录加上setgid标志 (setgid 只对目录有效)
注意
要删除一个文件,你不一定要有这个文件的写权限,但你一定要有这个文件的上级目录的写权限。也就是说,你即使没有一个文件的写权限,但你有这个文件的上级目录的写权限,你也可以把这个文件给删除,而如果没有一个目录的写权限,也就不能在这个目录下创建文件。
如何才能使一个目录既可以让任何用户写入文件,又不让用户删除这个目录下他人的文件,sticky就是能起到这个作用。stciky一般只用在目录上,用在文件上起不到什么作用。
在一个目录上设了sticky位后,(如/tmp,权限为1777)所有的用户都可以在这个目录下创建文件,但只能删除自己创建的文件,这就对所有用户 能写的目录下的用户文件启到了保护的作用。(/tmp没有设sticky位,而在文件上设了,这也就是为什么设了sticky位,还能删除自己创建的文件的原因了)
dev_t st_rdev; /* device ID (if special file) */
若此文件为装置设备文件, 则为其设备编号
文件大小三个属性
off_t st_size; /* total size, in bytes */
文件的大小
blksize_t st_blksize; /* blocksize for filesystem I/O */
文件所占块大小
blkcnt_t st_blocks; /* number of 512B blocks allocated */
文件所占块的数量
由上面的文件系统的知识可知道:
st_blocks * 512 = st_blksize
三个时间戳
ctime:change time
是最后一次改变文件或目录(属性)的时间,例如执行chmod,chown等命令。
atime:access time
是最后一次访问文件或目录的时间。
mtime:modify time
是最后一次修改文件或目录(内容)的时间。
这三个是由一个结构体 struct timespec定义的
##获取文件信息系列函数
了解完上面的结构体信息后,我们便可以对文件信息获取函数进行一个讲解了,相信会非常快地进行一个了解,因为获取文件信息系列函数,其实就是对文件结构体赋值的过程,Linux中获取文件信息的函数有三个
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
函数原型
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
返回值
成功返回0,失败返回-1,并且将详细错误信息赋值给errno全局变量。
区别
他们大致的功能差不多,但是本身还是一些的区别
- fstat和stat功能一致,只是第一个形参是文件描述符,也就是说需要先打开文件句柄
如
struct stat file_stat;
FILE * file_ret = open(filepath, O_RDONLY);
fstat(file_ret, &file_stat);
- lstat函数的形参跟stat函数的形参一样。其功能也跟stat函数功能一样,仅有一点不同:stat函数是穿透(追踪)函数,即对软链接文件进行操作时,操作的是链接到的那一个文件,不是软链接文件本身;而lstat函数是不穿透(不追踪)函数,对软链接文件进行操作时,操作的是软链接文件本身。
下面是一个lstat函数使用的实例
int main(int argc, char* argv[])
{
struct stat file_stat;
lstat(argv[1], &file_stat);
printf("st_dev:%lld\n", file_stat.st_dev); //unsigned long long int
printf("st_ino:%ld\n", file_stat.st_ino); //unsigned long int
printf("st_mode:%o\n", file_stat.st_mode); //unsigned int
printf("st_nlink:%d\n", file_stat.st_nlink); //unsigned int
printf("st_uid:%d\n", file_stat.st_uid); //unsigned int
printf("st_gid:%d\n", file_stat.st_gid); //unsigned int
printf("st_rdev:%lld\n", file_stat.st_rdev); //unsigned long long int
printf("st_size:%ld\n", file_stat.st_size); //long int
printf("st_blksize:%ld\n", file_stat.st_blksize);//long int
printf("st_blocks:%ld\n", file_stat.st_blocks); //long int
printf("Last status change: %s\n", ctime(&file_stat.st_ctime));// struct timespec
printf("Last file access: %s\n", ctime(&file_stat.st_atime));// struct timespec
printf("Last file modification: %s\n", ctime(&file_stat.st_mtime));// struct timespec
return 0;
}
输出的结果如下所示:
这也是实现 ls-l的基础