一、stat.st_mode数据位分析
stat.st_mode数据成员包含着文件很多属性,对st_mode的操作都是在位级进行,包括文件类型、S_ISGID位和S_ISUID位。下面是st_mode其中一些位的分布汇总表,是从GUN C库的头文件中对一些常量的定义做出来的分析和汇总。
位 | 文件属性 | 相关常量(八进制) |
bit12-bit15 | 文件类型 | S_IFMT = 0170000 (文件类型屏蔽字) S_IFIFO = 0010000 (管道或FIFO) S_IFCHR = 0020000 (字符特殊文件) S_IFDIR = 0040000 (目录文件) S_IFBLK = 0060000 (块特殊文件) S_IFREG = 0100000 (普通文件) S_IFLNK = 0120000 (链接文件) S_IFSOCK = 0140000 (套接字) |
bit9-bit11 | 文件访问权限控制 | S_ISVTX = 01000 (粘住位) S_ISGID = 02000(将进程的有效组ID设置为文件的组所有者ID) S_ISUID = 04000(将进程的有效用户ID设置为文件的用户ID) |
bit0-bit8 | 文件访问权限 | S_IXOTH = 0001 (其他-执行) S_IWOTH = 0002 (其他-可写) S_IROTH = 0004 (其他-可读) S_IXGRP = 0010 (组-执行) S_IWGRP = 0020 (组-可写) S_IRGRP = 0040 (组-可读) S_IXUSR = 0100 (用户-执行) S_IWUSR = 0200 (用户-可写) S_IRUSR = 0400 (用户-可读) |
下文是对上表第三行(bit0-bit8位)文件访问权限的分析。
二、Unix文件访问权限
一个文件的访问权限无非就是指哪些人可以对本文件进行操作以及可以进行哪些操作。不妨用法律术语表述为权力主体、权力客体和权力内容。
权力主体划分为三个:文件所有者(owner/user,USR)、用户组(group,GRP)和其他人(others,OTH)。
权力客体就一个:就是要操作的文件(file,F)。
权力内容划分为三个:可读(read,R)、可写(write,W)、可执行(execute,X)。
3个主体和3个内容组合成9种授权,分别用9个st_mode位(bit0-bit8)来表示。对应位为1表示允许,为0表示拒绝。参考上表。
表面上权限问题很简单,但实际有点繁琐。因为unix世界所有东西都是文件,包括目录(windows下的文件夹)都是文件。文件除了文件的数据本身,还有一些为了管理而产生的一些附加属性,例如文件名、访问时间等等。最男人头疼的是目录的可执行权是什么?目录怎么也可以执行呢?
1、普通文件的权限:
(1)读权限:决定是否能成功调用open、read、pread、readlink函数,因为这些函数需以读权限打开文件并读取文件中的数据。
(2)写权限:决定是否能成功调用open、stat、fstat、lstat、write、pwrite、chmod、fchmod、utime等函数,因为这些函数需以写权限打开文件并向文件写入新数据或更改文件属性。
(3)执行权限:调用6个exec函数中的任何一个执行某个文件,都必须对该文件具有可行权限,并且该文件还必须是一个普通文件。
2、目录文件的权限:
(1)读权限:获取该目录中所有文件名的列表,例如调用readdir函数读取目录列表。
(2)写权限:在目录中创建一个新文件或者删除一个文件,必须对该目录具有写权限和执行权限(只是具有写权限还不够),对要创建或者删除的文件自身不需要有任何权限。目录的写权限涉及open(带O_CREAT标志)、creat、link、unlink、rename、symlink、mkdir、rmdir等函数,因为这些函数都可能影响到目录的目录项(可以把目录中的目录项对比作一个普通文件中的数据部分,所以必须有写权限)。
(3)执行权:在目录中创建一个新文件或者删除一个文件,必须具有的一个权限之一;此外,我们要在目录中查找一个指定文件(不是列出所有文件),还必须对该目录具有可执行权限。例如,我们要通过一个路径操作一个文件,还必须对沿途经过的目录文件具有可执行权。假设我们要打开文件/home/user1/direct1/file1进行读操作,除了要对文件file1具有读权限外,我们还必须对沿途经过的目录(/、/home、/home/user1、/home/user1/direct1)都具有可执行权限。
三、内核对文件访问权限的检查机制
所有文件操作都是有系统内核的文件管理子系统来完成的,各个库函数只是直接或者间接调用其中某个或者某些系统调用并以函数形式提供给上层代码调用。任何针对文件操作的库函数都不会包含文件权限的检查,只能返回调用成功还是失败。对文件操作权限的检查都是在内核中进行。(应该是这样)
内核对文件访问权限的检查机制大致可以描述为:当某进程调用内核对文件进行操作时,内核会将进程的有效用户ID、有效组ID以及附加组ID与要操作文件的所有者(stat.st_uid)、组用户(stat.st_gid)相关的权限进行对比,对比成功则允许,否则拒绝。检查步骤如下:
1、若进程的有效用户ID是0(即超级用户,例如root用户),则允许访问。
2、若进程的有效用户ID等于文件的所有者ID(stat.st_uid),那么继续检查具体请求,如果文件对应权限位设置则允许,否则拒绝。例如进程用户为user1,请求文件/home/user1/file1进行读操作,而文件/home/user1/file1的stat.st_mode中的S_IRUSR位为1,则请求的读操作成功,若S_IRUSR位为0,则请求失败。
3、若进程的有效组ID或者进程的附加组ID等于文件的组ID(stat.st_gid),若文件的对应权限位设置则允许,否则拒绝。
4、若文件的其他用户对应的权限位设置则允许,否则拒绝。
5、以上个步骤按上述所列顺序进行,从第2个步骤开始,如果ID对应,但权限被允许或者拒绝就不再进行下一步。如果ID不对应,则继续下一步的检查。
四、文件权限位定义
文件权限位常量定义在<sys/stat.h>和<bits.stat.h>文件中,其中<sys/stat.h>包含了<bits/stat.h>,所以在程序代码中只需要引用<sys/stat.h>即可。
1、<bits/stat.h>
#define __S_IREAD 0400 /* Read by owner. */
#define __S_IWRITE 0200 /* Write by owner. */
#define __S_IEXEC 0100 /* Execute by owner. */
2、<sys/stat.h>
#define S_IRUSR __S_IREAD /* Read by owner. */
#define S_IWUSR __S_IWRITE /* Write by owner. */
#define S_IXUSR __S_IEXEC /* Execute by owner. */
/* Read, write, and execute by owner. */
#define S_IRWXU (__S_IREAD|__S_IWRITE|__S_IEXEC)
#if defined __USE_MISC && defined __USE_BSD
# define S_IREAD S_IRUSR
# define S_IWRITE S_IWUSR
# define S_IEXEC S_IXUSR
#endif
#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
/* Read, write, and execute by group. */
#define S_IRWXG (S_IRWXU >> 3)
#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
/* Read, write, and execute by others. */
#define S_IRWXO (S_IRWXG >> 3)
这个定义过程有点意思,先定义__S_IREAD(可读)、__S_IWRITE(可写)、__S_IEXEC(可执行),然后通过重定义和右移3位运算来定义三种主体的权限。很美观!
五、文件访问权限位操作
1、权限位检查:用stat.st_mode与S_IXXXX位常量进行“按位与”运算。
stat.st_mode & S_IRUSR:用户-读
stat.st_mode & S_IWUSR:用户-写
stat.st_mode & S_IXUSR:用户-执行
stat.st_mode & S_IRGRP:组-读
stat.st_mode & S_IWGRP:组-写
stat.st_mode & S_IXGRP:组-执行
stat.st_mode & S_IROTH:其他-读
stat.st_mode & S_IWOTH:其他-写
stat.st_mode & S_IXOTH:其他-执行
2、权限位置位(置1,允许):用stat.st_mode与S_IXXXX位常量进行“按位或”运算。
stat.st_mode | S_IRUSR:用户-读
stat.st_mode | S_IWUSR:用户-写
stat.st_mode | S_IXUSR:用户-执行
stat.st_mode | S_IRGRP:组-读
stat.st_mode | S_IWGRP:组-写
stat.st_mode | S_IXGRP:组-执行
stat.st_mode | S_IROTH:其他-读
stat.st_mode | S_IWOTH:其他-写
stat.st_mode | S_IXOTH:其他-执行
3、权限位清除(置0,拒绝):用stat.st_mode与S_IXXXX位常量的反码进行“按位与”运算。
stat.st_mode & ~S_IRUSR:用户-读
stat.st_mode & ~S_IWUSR:用户-写
stat.st_mode & ~S_IXUSR:用户-执行
stat.st_mode & ~S_IRGRP:组-读
stat.st_mode & ~S_IWGRP:组-写
stat.st_mode & ~S_IXGRP:组-执行
stat.st_mode & ~S_IROTH:其他-读
stat.st_mode &~ S_IWOTH:其他-写
stat.st_mode & ~S_IXOTH:其他-执行
以上三种操作都可以用单个权限进行组合然后做对应的位运算,例如下面代码可以一次设置为文件所有者同时拥有可读、可写和可执行权限。
#define S_IURWX (S_IRUSR | S_IWUSR | S_IXUSR)
buf->st_mode | S_IURWX
以上各种操作只是改变了内存中stat结构的值,不会影响内核对文件实际权限的检查,也不会自动保存到到文件存储在物理磁盘上的对应值,要保存这些修改到物理磁盘,还必须以更改后的stat.st_mode作为参数调用chmod或fchmod函数。
实例 x.4.5.1.c
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*定义一些全权常量*/
#define S_IURWX (S_IRUSR | S_IWUSR | S_IXUSR)
#define S_IGRWX (S_IRGRP | S_IWGRP | S_IXGRP)
#define S_IORWX (S_IROTH | S_IWOTH | S_IXOTH)
#define S_IUGORWX (S_IURWX | S_IGRWX | S_IORWX)
void set_rwx(struct stat *buf, mode_t S_IXXXX);
void clr_rwx(struct stat *buf, mode_t S_IXXXX);
void get_rwx(const struct stat *buf, char *rwx);
int main(int argc, char *argv[])
{
struct stat buf;
char rwx[11] = "----------"; /*存放权限标识字符*/
if (argc < 3) {
printf("usage:./a.out -urwx filepath\n");
exit(1);
}
if (stat(argv[2], &buf) == -1) {
printf("stat error for %s\n", argv[2]);
exit(2);
}
if (strcmp(argv[1], "-urwx") == 0) {
clr_rwx(&buf, S_IUGORWX); /*清除所有权限*/
set_rwx(&buf, S_IURWX); /*设置为所有者可读、可写和可执行*/
}
get_rwx(&buf, rwx);
printf("%s\n", rwx);
exit(0);
}
/*设置权限位*/
void set_rwx(struct stat *buf, mode_t S_IXXXX)
{
buf->st_mode = buf->st_mode | S_IXXXX;
}
/*清除权限位*/
void clr_rwx(struct stat *buf, mode_t S_IXXXX)
{
buf->st_mode = buf->st_mode & ~S_IXXXX;
}
/*模拟ls -l 命令打印权限位*/
void get_rwx(const struct stat *buf, char *rwx)
{
/*rwx[0]留作文件类型标识,不在此例演示*/
if (buf->st_mode & S_IRUSR)
rwx[1] = 'r';
else
rwx[1] = '-';
if (buf->st_mode & S_IWUSR)
rwx[2] = 'w';
else
rwx[2] = '-';
if (buf->st_mode & S_IXUSR)
rwx[3] = 'x';
else
rwx[3] = '-';
if (buf->st_mode & S_IRGRP)
rwx[4] = 'r';
else
rwx[4] = '-';
if (buf->st_mode & S_IWGRP)
rwx[5] = 'w';
else
rwx[5] = '-';
if (buf->st_mode & S_IXGRP)
rwx[6] = 'x';
else
rwx[6] = '-';
if (buf->st_mode & S_IROTH)
rwx[7] = 'r';
else
rwx[7] = '-';
if (buf->st_mode & S_IWOTH)
rwx[8] = 'w';
else
rwx[8] = '-';
if (buf->st_mode & S_IXOTH)
rwx[9] = 'x';
else
rwx[9] = '-';
}
编译与执行:
[root@localhost unixc]# echo "This is my temporate file" > /tmp/myfile
[root@localhost unixc]# cc x.4.5.1.c
[root@localhost unixc]# ls -l /tmp/myfile
----r-S--T. 2 root root 26 Nov 4 03:44 /tmp/myfile
[root@localhost unixc]# ./a.out -urwx /tmp/myfile
-rwx------
[root@localhost unixc]#