UNIX高级环境编程学习笔记(3)

在前面的两篇,我们了解了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函数只返回该符号链接的相关信息,如果不设置该标志位,则返回该符号链接指向的文件的相关信息。

是一个关键的输出参数,上面的参数将文件属性填充值该结构体内。结构体如下图所示:

NewImage

结构体中得每个成员都代表文件的某一个属性,我们会逐个了解属性的具体含义。

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); } 

运行截图:

NewImage

3 设置用户Id(User ID)和组Id(Group ID)

每个进程6个甚至更多的ID和它关联。如下表所示:

NewImage

简要介绍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中权限位:

NewImage

这些标志位的使用需要注意的事项总结如下:

如果要打开某个文件,则该文件全路径上的所有文件夹都需要有执行权限(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,测试文件是否存在。

NewImage

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); } 

测试:

NewImage

由于本地mac环境搞不定,所以直接截书上的例子吧。

该例中,当切换到root用户,修改了该文件的所有者为root用户,并且设置了set-user-ID,这样当切换到别的用户时,仍可以以RDONLY方式打开文件,但是由于access是测试当前real user的读权限,当切换到其他用户时,access测试不通过,显示Permission denied。

好吧,直接忽略上面这个例子吧,我没能在自己的机器上重现,只是直接随便翻译书上某一段话。如果有某位高手看懂了,请指点。

小结

由于这一章内容比较多,所以打算用三篇来写,这是第一篇,主要介绍了stat函数,access函数,文件类型和各种令人糊涂的ID。

 
【内容导航】  
第1页:
File I/O   第2页:FIle I/O - 原子操作、共享文件描述符和I/O控制函数  
第3页:Files And Directories - stat函数,文件类型,和各种ID   第4页:Files And Directories - umask、chmod、文件系统组织结构和链接  
第5页:Files And Directories - 文件相关时间,目录文件相关操作   第6页:标准IO函数库 - 流的概念和操作  
第7页:标准IO函数库 - 二进制文件IO,流定位,创建临时文件和内存流   第8页:进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表  
第9页:进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid   第10页:进程控制(Process Control)- 竞态条件,exec函数,解释器文件和system函数  
第11页:进程控制(Process Control)- 进程快照,用户标识符,进程调度   第12页:进程关联(Process Relationships)- 终端登录过程 ,进程组,Session  
第13页:信号 - 概念、signal函数、可重入函数    

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/c828034bb8db0afeddd2a9b456fb7df9.html