在前面的两篇,我们了解了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)。

常见的几种文件类型包括:

  1. 常规文件(Regular File):包含某种组织形式的数据的文件,Linux并不关心数据是文本或是二进制(有一个例外,就是二进制可执行文件,内核必须知道该类型的格式才能执行),对数据的解析由应用程序负责。
  2. 目录文件(Directory):包含其他文件的名字,和指向这些文件信息的指针。进程可以读取目录文件的内容,但是只有内核才又写该类型文件的权限,进程通过调用本篇介绍的一些函数才操作修改目录文件,本质上仍然是内核修改。
  3. 块文件(Block Special File):如硬盘驱动,提供了固定大小缓存用于IO的文件类型。
  4. 字符文件(Charactor Special File):无缓存的对可变大小单元进行操作的文件类型。操作系统所有的设备都是块设备文件或字符设备文件。
  5. 先入先出队列(FIFO):进程间通信使用的文件类型。
  6. 套接字(Socket):网络或单机中的两个进程之间的通信使用的文件类型。
  7. 符号链接(Symbolic File):指向另一个文件的文件类型。

stat结构体中得st_mode制定了文件的类型。

Example:

#include "apue.h"

int
main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr; for (i = ; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < ) {
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();
}

运行截图:

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。

文件访问权限检测流程:

  1. 如果进程的effective user ID是0,即超级用户,则允许访问;
  2. 如果进程的effective user ID是文件所有者的ID,即该进程是该文件的所有者(owner),并且相应的访问权限标志位被设置,则允许访问;
  3. 如果进程的effective group ID或者supplementary group IDs之一是文件所有组的ID,并且相应的访问权限标志位被设置,则允许访问;
  4. 如果相应的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 != )
err_quit("usage: a.out <pathname>"); if (access(argv[], R_OK) < )
err_ret("access error for %s", argv[]);
else
printf("read access OK\n"); if (open(argv[], O_RDONLY) < )
err_ret("open error for %s", argv[]);
else
printf("open for reading OK\n"); exit();
}

测试:

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

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

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

小结

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

参考资料:

《Advanced Programming in the UNIX Envinronment 3rd》

UNIX高级环境编程(3)Files And Directories - stat函数,文件类型,和各种ID的更多相关文章

  1. UNIX高级环境编程(7)标准IO函数库 - 二进制文件IO,流定位,创建临时文件和内存流

    1 二进制IO(Binary IO) 在前一篇我们了解了逐字符读写和逐行读写函数. 如果我们在读写二进制文件,希望以此读写整个文件内容,这两个函数虽然可以实现,但是明显会很麻烦且多次循环明显效率很低. ...

  2. UNIX高级环境编程(6)标准IO函数库 - 流的概念和操作

    标准IO函数库隐藏了buffer大小和分配的细节,使得我们可以不用关心预分配的内存大小是否正确的问题. 虽然这使得这个函数库很容易用,但是如果我们对函数的原理不熟悉的话,也容易遇到很多问题.   1 ...

  3. UNIX高级环境编程1

    UNIX高级环境编程1 故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详. 首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表 ...

  4. UNIX高级环境编程(14)文件IO - O_DIRECT和O_SYNC详解 < 海棠花溪 >

    春天来了,除了工作学习,大家也要注意锻炼身体,多出去运动运动.  上周末在元大都遗址公园海棠花溪拍的海棠花.   进入正题. O_DIRECT和O_SYNC是系统调用open的flag参数.通过指定o ...

  5. UNIX高级环境编程(4)Files And Directories - umask、chmod、文件系统组织结构和链接

    本篇主要介绍文件和文件系统中常用的一些函数,文件系统的组织结构和硬链接.符号链接. 通过对这些知识的了解,可以对Linux文件系统有更为全面的了解.   1 umask函数 之前我们已经了解了每个文件 ...

  6. Unix高级环境编程

    [07] Unix进程环境==================================1. 进程终止    atexit()函数注册终止处理程序.    exit()或return语句:    ...

  7. UNIX高级环境编程(10)进程控制(Process Control)- 竞态条件,exec函数,解释器文件和system函数

    本篇主要介绍一下几个内容: 竞态条件(race condition) exec系函数 解释器文件    1 竞态条件(Race Condition) 竞态条件:当多个进程共同操作一个数据,并且结果依赖 ...

  8. UNIX高级环境编程(16)文件系统 < 雨后 >

    来点绿色放松一下眼睛吧 :) 文件系统是对文件和目录的组织集合. 一 设备文件 设备文件和系统的某个设备相对应. 设备驱动程序 处理设备的所有IO请求. 提供了一致的API接口,对应于系统调用的ope ...

  9. UNIX高级环境编程(12)进程关联(Process Relationships)- 终端登录过程 ,进程组,Session

    在前面的章节我们了解到,进程之间是有关联的: 每个进程都有一个父进程: 子进程退出时,父进程可以感知并且获取子进程的退出状态. 本章我们将了解: 进程组的更多细节: sessions的内容: logi ...

随机推荐

  1. 布局管理器之CardLayout(卡片布局管理器)

    对于选项卡这个概念大家可能不会陌生,就是在一个窗口中可以切换显示多页不同的内容,但同一时间只能是其中的某一页可见的,这样的一个个的页面就是选项卡. CardLayout就是类似的这样一个布局管理器,它 ...

  2. [转] Hadoop 2.0 详细安装过程

    1. 准备 创建用户 useradd hadoop passwd hadoop 创建相关的目录 定义代码及工具存放的路径 mkdir -p /home/hadoop/source mkdir -p / ...

  3. 解决升级Nodepad++都会让插件失效

    主要原因是Plugin Manager失效导致的,需要重新导入 导入一下PluginManager就可以了地址:https://github.com/bruderstein/nppPluginMana ...

  4. 工程中添加工程依赖 Xcode iOS

    有时我们需要在一个主工程中添加其他的子工程,用来对子工程进行编写修改或者是利用子工程中的库文件等等操作,这时候我们需要用到工程的嵌套.   步骤:(看图说话)   1.新建主工程,名为TestTTTT ...

  5. MySQL5:触发器

    什么是触发器 MySQL的触发器(trigger)和存储过程一样,都是嵌入到MySQL中的 一段程序.触发器是由事件来触发某个操作,这些事件包括INSERT.UPDATE和DELETE语句.如果定义了 ...

  6. 说说HTML5中label标签的可访问性问题——张鑫旭

    一.开篇叨叨 一般稍微有些经验的页面制作人员都知道label标签可以优雅地扩大表单控件元素的点击区域,例如,单纯的单选框点击区域就鼻屎那么大的地方,经常会点不到位置.因此,label标签的使用对于提高 ...

  7. PHP生成缩略图(1)--简单缩略图

    原理:就是将大图缩小并另存为小图 以此图为例,使其生成缩略图! 首先要使用到以下函数 imagecopyresampled() 重采样拷贝部分图像并调整大小 bool imagecopyresampl ...

  8. navicat 批量插入 测试数据

    1. 前言 遇到线上大sql执行较慢, 10s+, 做优化改进时,首先想到的是在本地造出一个类似的库环境,先本地实验. 然后往表中创建大量数据... 2. 方案 利用mysql函数来插入大量数据 代码 ...

  9. 在vue中子组件修改props引发的对js深拷贝和浅拷贝的思考

    不管是react还是vue,父级组件与子组件的通信都是通过props来实现的,在vue中父组件的props遵循的是单向数据流,用官方的话说就是,父级的props的更新会向下流动到子组件中,反之则不行. ...

  10. react知识点汇总

    ①uncontrolComponent & controlComponent If your form is incredibly simple in terms of UI feedback ...