UNIX高级环境编程(3)Files And Directories - stat函数,文件类型,和各种ID
在前面的两篇,我们了解了IO操作的一些基本操作函数,包括open、read和write。
在本篇我们来学习一下文件系统的其他特性和一个文件的属性,涉及的函数功能包括:
- 查看文件的所有属性;
- 改变文件所有者;
- 改变文件权限;
- 操作文件夹。
我们还会了解一些文件系统相关的数据结构和符号链接(symbolic link)。
1 函数stat、fstat、fstatat、lsat函数
#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); //All four return: 0 if OK, −1 on error
函数功能:
stat返回pathname指定文件的信息;
fstat获取在文件描述符fd上打开的文件信息;
lstat的功能和stat函数类似,只是有一种情况有区别,当pathname指定的文件是一个符号链接的时候,lstat函数获取的文件信息是该符号链接的信息,而不是符号链接所指向文件的信息,在后面我们会了解更多关于符号链接的相关内容;
fstatat:文件描述符fd表示一个父目录,pathame指定父目录下得一个子目录,fstatat函数返回该子目录下的文件统计数。flag用来设置是否查询符号链接所指向的文件,如果flag设置为AT_SYMLINK_NOFOLLOW,则fstatat函数只返回该符号链接的相关信息,如果不设置该标志位,则返回该符号链接指向的文件的相关信息。
是一个关键的输出参数,上面的参数将文件属性填充值该结构体内。结构体如下图所示:
结构体中得每个成员都代表文件的某一个属性,我们会逐个了解属性的具体含义。
2 文件类型(File Type)
目前我们了解了两种文件:常规文件(regular files)和文件夹(directory)。
常见的几种文件类型包括:
- 常规文件(Regular File):包含某种组织形式的数据的文件,Linux并不关心数据是文本或是二进制(有一个例外,就是二进制可执行文件,内核必须知道该类型的格式才能执行),对数据的解析由应用程序负责。
- 目录文件(Directory):包含其他文件的名字,和指向这些文件信息的指针。进程可以读取目录文件的内容,但是只有内核才又写该类型文件的权限,进程通过调用本篇介绍的一些函数才操作修改目录文件,本质上仍然是内核修改。
- 块文件(Block Special File):如硬盘驱动,提供了固定大小缓存用于IO的文件类型。
- 字符文件(Charactor Special File):无缓存的对可变大小单元进行操作的文件类型。操作系统所有的设备都是块设备文件或字符设备文件。
- 先入先出队列(FIFO):进程间通信使用的文件类型。
- 套接字(Socket):网络或单机中的两个进程之间的通信使用的文件类型。
- 符号链接(Symbolic File):指向另一个文件的文件类型。
stat结构体中得st_mode制定了文件的类型。
Example:
#include "apue.h" int main(int argc, char *argv[]) { int i; struct stat buf; char *ptr; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < 0) { err_ret("lstat error"); continue; } if (S_ISREG(buf.st_mode)) ptr = "regular"; else if (S_ISDIR(buf.st_mode)) ptr = "directory"; else if (S_ISCHR(buf.st_mode)) ptr = "character special"; else if (S_ISBLK(buf.st_mode)) ptr = "block special"; else if (S_ISFIFO(buf.st_mode)) ptr = "fifo"; else if (S_ISLNK(buf.st_mode)) ptr = "symbolic link"; else if (S_ISSOCK(buf.st_mode)) ptr = "socket"; else ptr = "** unknown mode **"; printf("%s\n", ptr); } exit(0); }
运行截图:
3 设置用户Id(User ID)和组Id(Group ID)
每个进程6个甚至更多的ID和它关联。如下表所示:
简要介绍ID的区别:
- real user ID和real group ID:当前登陆用户的ID和该用户所属的组ID;
- effective user ID、effective group ID和supplementary group IDs:文件的访问权限。
- saved set-user-ID和saved set-group-ID:当一个程序运行时,会拷贝effective user ID和effective group ID到这两个变量中,这两个ID和函数setuid相关。
一般情况下,effective user ID = real user ID,effective group ID = real group ID.
每个文件都由一个所有者,和一个组所有者,分别对应stat数据结构中得字段:st_uid和st_gid。
如果执行程序时,希望effective user(group) ID != real user(group) ID,即希望改变进程的访问资源的权限为文件所有者的访问权限,而不是真实用户的访问权限,可以通过设置mode中两个bit来实现将effective user(group) ID设置为文件所有者(组),这两个bit叫做:set-user-ID位和set-group-ID位。这两个位包含在stat数据结构中得st_mode字段中,可以通过函数S_ISUID和S_ISGID来测试。
4 文件访问权限(File Access Permissions)
stat数据结构中的st_mode字段中同样包含文件访问权限位。
每个文件有9中权限位:
这些标志位的使用需要注意的事项总结如下:
- 如果要打开某个文件,则该文件全路径上的所有文件夹都需要有执行权限(execute permission),该文件则需要对应的操作权限。需要可执行权限的原因是可以通过(pass through)它找到下一级目录或者文件;
- 读权限,允许打开某个已存在文件,标志位:O_RDONLY和O_RDWR;
- 写权限,允许向某个已存在文件写入数据,标志位:O_WRONLY和O_RDWR;
- 调用open函数时,如果要指定标志位O_TRUNC,需要写权限;
- 我们无法在某个文件夹下创建文件,除非我们对该文件夹有写权限(write permission)和执行权限(execute permission);
- 删除某个文件,需要该文件所在文件夹的写权限和执行权限,而不需要对该文件本身有写权限或读权限;
- 如果我们希望使用exec类函数执行某个文件,则必须有可执行权限,并且该文件必须为regular file。
文件访问权限检测流程:
- 如果进程的effective user ID是0,即超级用户,则允许访问;
- 如果进程的effective user ID是文件所有者的ID,即该进程是该文件的所有者(owner),并且相应的访问权限标志位被设置,则允许访问;
- 如果进程的effective group ID或者supplementary group IDs之一是文件所有组的ID,并且相应的访问权限标志位被设置,则允许访问;
- 如果相应的other访问权限标志位被设置,则允许访问。
简单来说,如果进程是该文件的拥有者,则访问是否允许取决于用户访问权限标志位,忽略组权限标志位;如果进程不是该文件的拥有者,但是该进程属于某个有访问权限的组,访问是否允许取决于组权限标志位的设置,忽略other访问权限标志位。
5 新文件和目录的所有权(ownership)
新文件的real user ID为创建该文件的进程的effective user ID。
新文件的real group ID的取值取决于:
- 新文件的real group ID可以是常见该文件的进程的effective group ID;
- 新文件的real group ID可以是该文件所在目录的real group ID。(必须保证该目录下面的所有文件的real group ID都是该目录的real group ID)。
6 函数access和faccessat
access和faccessat函数用于测试当前用户(real user,当前登录用户)对某一文件是否有某种操作权限。
函数声明:
#include <unistd.h> int access(const char* pathname, int mode); int faccessat(int fd, const char* pathname, int mode, int flag);
mode的可取值,当为F_OK,测试文件是否存在。
faccessat函数的相关细节:
- 当pathname为绝对路径,或者fd取值为AT_FDCWD并且pathname为相对路径时,功能和access相同。
- 否则,faccessat函数指定的工作目录为pathname指定的相对目录加上fd描述符指定的父目录。
- 当flag的值为AT_EACCESS时,测试时使用进程的effective user和group而不是当前real user ID和group。
Example:
#include "apue.h" #include <fcntl.h> int main(int argc, char *argv[]) { if (argc != 2) err_quit("usage: a.out <pathname>"); if (access(argv[1], R_OK) < 0) err_ret("access error for %s", argv[1]); else printf("read access OK\n"); if (open(argv[1], O_RDONLY) < 0) err_ret("open error for %s", argv[1]); else printf("open for reading OK\n"); exit(0); }
测试:
由于本地mac环境搞不定,所以直接截书上的例子吧。
该例中,当切换到root用户,修改了该文件的所有者为root用户,并且设置了set-user-ID,这样当切换到别的用户时,仍可以以RDONLY方式打开文件,但是由于access是测试当前real user的读权限,当切换到其他用户时,access测试不通过,显示Permission denied。
好吧,直接忽略上面这个例子吧,我没能在自己的机器上重现,只是直接随便翻译书上某一段话。如果有某位高手看懂了,请指点。
小结
由于这一章内容比较多,所以打算用三篇来写,这是第一篇,主要介绍了stat函数,access函数,文件类型和各种令人糊涂的ID。
参考资料:
《Advanced Programming in the UNIX Envinronment 3rd》