关于stat函数和根据st_mode 的值获取目标文件的文件类型和
用户权限,以及对相关系统宏的一点想法
谈论之前写说一下 stat 这个函数,这是个很有用的系统函数,
大家都知道调用stat函数可以获取到由参数(文件路径)指定的文
件的相关属性,包括文件类型,文件权限,硬链接数,用户,用
户组,修改时间等等信息,而这一系列状态信息都是保存在对应
的结构体里面的。与stat函数同出一胎的还有fstat,lstat这两个函
数,极其相似,区别这里不谈了。
在linux 命令行下,输入命令“ man 2 stat ”可以查看 stat 函数
的原型:
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *path, struct stat *buf);
path参数对应目标文件的地址;
buf 参数是结构体指针类型。
在系统内,struct stat 结构体是这样定义的:
可以看到这个结构体内的各个域都用于保存文件的各种类型信
息,例如:
st_mode 记录文件类型+文件权限
st_nlink 记录文件的硬链接数,新建目录的硬链接数为2
结构体内右边的一列自然是变量,而左边的一列看起来凌而不乱
的字符串是变量的类型,其实也就是那些char型,unsigned,long
int类型等等的重定义而已。
调用这个stat 函数之前应先定义一个用于接受文件相关信息的
结构体变量,这样:
struct stat sb;
stat(pathname,&sb);
就可以获取文件信息,stat函数获取状态信息成功返回0,有错误返
回-1。
大家可以尝试用printf将这些变量的数据一一打印出来,看看获取
到了文件的什么信息。
接下里详细谈谈 struct stat 结构体里面 st_mode 这个东西。
st_mode 上面看到它定义为mode_t类型,它是用来获取文件的文
件类型+文件属性的。这就有意思了,这一个变量它是如何做到同
时保存文件类型和文件属性两个信息的。
我们不妨写一个小程序测试一下,将他的数据打印到屏幕瞧
瞧:
#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> int main(int argc, char *argv[]) { struct stat sb; int i=0; while(++i { if(stat(argv[i],&sb)==-1) exit (1); printf("%s的 st_mode is %o\n",argv[i],sb.st_mode); } return 0; }
先用 ls -l查看几个类型相同,权限不同的目录属性:
再用程序测试下这几个目录的st_mode值
可以看到他们后3位数字不同。
事实上后3位数字代表的就是3种用户权限。而我们看到的
st_mode 的值是八进制数。
先说说权限,权限有3类,可读取 r,可写入w,可执行x。他
们分别对应一个值,r=4,w=2,x=1.对应二进制r=100,w=010,x=001,
似乎有点规律,这3个数确实不是随便取的,后面会说到他的妙
处。
用户类型有3种,所有者,用户组,其他用户。后面那3个数字
正好分别对应一种用户类型。
拿/proc/目录的st_mode值为例,40555后3位是555,数字5显然
只能拆分为4+0+1,所以是r_x,555组合为r_xr_xr_x,这就得到了3种
用户的权限。
而一个数字拿拆分成4,2,1的哪种组合,我们一眼就能看出来,
可以计算机看不出来,这就需要给计算机设计一种算法,比如怎
么让他知道5是由4+0+1组成,而且555这3个数字还是连在一起
的,我们当然可以40555的后3个数字分别提取出来,然后将4
,2,1的所有组合尝试一遍最后找到正确的权限解。但这只是为了获
取文件的权限而已,这个算法也太繁琐了。
我们用man 手册查看stat函数时,会看到一堆的大写字母的宏
定义,后面还给出一些确定的0,1组成的二进制数字,但是这一堆
乱七八糟的宏定义有何用呢?我们先来看看下面的方法是如何解
出具体的权限的。
发现就是用st_mode 和那些宏取“&”运算来判断用户的各种权限
的,只用了一条简单的语句就解决了我们的困扰,妙哉。我们来
分析一下这是如何做到的。
拿用户所有者权限为例:
先看看对应的宏和相关数据:
宏 (前3个) 八进制值 二进制值 权限
S_IRUSR 00400 000 000 100 000 000 r=4
S_IWUSR 00200 000 000 010 000 000 w=2
S_IXUSR 00100 000 000 001 000 000 x=1
st_mode ***** *** *** *** *** ***
/proc/ 40555 100 000 101 101 101
为了便于观察将5位八进制数各位编号
e d c b a
0 0 0 0 0
将15位二进制数各位编号
文件类型 ID 所有者 用户组 其他用户
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
r w x r w x r w x
第10~12位是ID位,第10位称粘附位,11,12位用于获取uid,gid;
第13~15位用于获取文件类型,后面谈论。
先看看上面判断文件所有者可读取权限r的算法语句:
sb.st_mode & S_IRUSR
对于/proc/目录,st_mode是一个八进制数
=40555=100000101101101
对应的S_IRUSR=000 000 100000 000
两者取 &运算:
st_mode 100 000 101 101 101
S_IRUSR 000 000 100 000 000
= 000 000 100 000 000
= 1
运算值为1,所以判定为可读取,这方法简单吧。
系统为什么用取 &运算就可以判定呢,而不用其他逻辑运算。
st_mode是一个八进制数,仅仅5位,却代表了多个类型的信息,
而这5位之间的联系剪不断,理还乱。但我们要判定所有者可读取
权限,仅仅需要看的是决定他的第9位数字,这就使得任何一种算
法只要是其他数位也参与了运算就会影响我们的判断,从而达不
到判断用户权限的作用。而“&”运算在这里完美的解决了这个问
题,&运算的法则是只有1&1=1,其他情况取&都是0。这就可看出
为什么要用到宏,宏S_IRUSR为什么要取值00400。
观察st_mode&S_IRUSR的运算可以看到,S_IRUSR除了第9
位其他位都为0,0与0,1的&运算都是0,所以st_mode除了第9位,其
他位与0取&后都得0,因此0在这里就起到了屏蔽其他位数字影响的
效果,起判定作用的就只有第九位。很巧妙的利用st_mode与
S_IRUSR的&运算识别到了所有者用户的可读取权限。
再看看如何判断所有者用是否具有可写入权限w,如果有可写
入权限,那么八进制数的c位置数字一定能拆分出2,如6=4+2。如
果能拆分出2,那么对应的二进制数字位(第8位)一定是1,如
6=110(中间位为1)。因此判断是否可写入就转换成了第8位是
否为1的问题。我们回过头看上面的判定语句
if(st_mode &S_IWUSR):
st_mode=40555=100 000 101 101 101
& S_IWUSR 000 000 010 000 000
=000 000 000 000 000
=0
第8位取&后为0,所以st_mode的第8位为0,无可写入权限。
同理,我要判断所有者可执行权限x时,起决定作用的是第7
位,所有让st_mode与只有第7位为1的数,
即000000 001 000 000=00100进行&运算即可判定。
看出来了吧,这些宏的取值都不是随意的,而是有意义的。
很显然,剩下的宏的八进制值为:
用户组权限:
S_IRGRP 000 000 000 100 000=00040
S_IWGRP 000 000 000 010 000=00020
S_IXGRP 000 000 000 001 000=00010
其他用户权限
S_IROTH 000 000 000 000 100=00004
S_IWOTH 000 000 000 000 010=00002
S_IXOTH 000 000 000 000 001=00001
了解了它运算方法,再看看权限r,w,x为什么就要取4,2,1这3个数
值呢,我们已知道st_mode是个八进制数,权限位的数字都能拆
分成3个数字,每个每个数字都代表一种权限,而这种拆分必须是
唯一确定的。r,w,x对应的不同权限组合共有8种,正好可以对应八
进制基数0,1,2,3,4,5,6,7,8,所以将st_mode的数据类型定为八进制
数最为合适,而也只有4,2,1,0(无权限)这3个取值才能正好唯一的
对应权限的8种组合:
0+0+0=0;0+0+1=1,0+2+0=2,0+2+1=3,
4+0+0=4, 4+0+1=5,4+2+0=6, 4+2+1=7,
想到了这些,系统的宏也就不那么神秘了。
说完了文件权限,再谈谈文件类型的获取吧。
文件类型主要有如下几种:
d directoryfile 目录文件
c characterdevice 字符设备文件
b blockdevice 块设备文件
p FIFO (namedpipe) 管道文件
l symboliclink 符号链接文件
s socket 套接字文件
_ regularfile 一般文件
先看一段获取文件类型的代码:
看到判断文件类型时都用到了类似于 S_ISDIR(m) 这样的语句,
stat 的man 手册里,给出的判断文件类型的语句如下:
似乎有点不好理解,怎么S_IS...(m)就可以判断出文件类型,
再看看 man 手册里还有这样一段代码:
可以看出这一段代码也是获取文件类型的,与前面那一段代码
相比较就可以想到了