UNIX环境高级编程 第4章 文件和目录
第三章说明了关于文件I/O的基本函数,主要是针对普通regular类型文件。本章描述文件的属性,除了regular文件还有其他类型的文件。
函数stat、fstat、fstatat和lstat
stat系列函数用于返回文件的属性信息,比如文件类型、大小、所有者、访问修改时间等。其头文件及函数原型如下:
- #include <sys/stat.h>
- int stat (const char *restrict file, struct stat *restrict buf);
- int fstat (int fd, struct stat *buf);
- int lstat (const char *restrict file, struct stat *restrict buf);
- int fstatat (int fd, const char *restrict file, struct stat *restrict buf, int flag);
上面的函数,成功时返回0,失败返回-1。其中的stat结构体各个实现可能存在差异,但它们都至少具备下列的信息:
- struct stat {
- mode_t st_mode; //文件类型
- ino_t st_ino; //指向数据块的节点的编号
- dev_t st_dev; dev_t st_rdev; nlink_t st_nlink; //硬链接数
- uid_t st_uid; //文件用户ID
- gid_t st_gid; //文件用户组ID
- off_t st_size; struct timespec st_atim; //文件数据访问时间
- struct timespec st_mtim; //文件数据修改时间
- struct timespec st_ctim; //文件属性修改时间
- blksize_t st_blksize; blkcnt_t st_blocks; };
文件类型
UNIX系统中的文件大多数是普通文件和目录,但也存在其他类型的文件,其分类如下:
- 普通文件(regular file):包含数据的常规文件,数据可以是文本类型的,也可以是二进制的。
- 目录文件(directory file):如字面所言,它是一个目录,保存目录相关的信息。
- 块设备文件(block special file):通常是硬件设备,被抽象成文件,比如硬盘。带有缓冲功能。
- 字符设备文件(regular file):通常是硬件设备,被抽象成文件,比如键盘。不支持缓冲。
- FIFO:进程间通信的一种文件。
- 套接字文件(regular file):网络间通信的一种文件。
- 符号连接(regular file):类似Windows系统的快捷方式。
以上文件类型的信息存储在前面说明的stat结构体中st_mode成员中的。st_mode成员的读取是利用系统提供的宏函数进行的。
设置用户ID和设置组ID
在UNIX系统中,一个进程至少有3大类6个以上的ID信息,这些信息主要是用来判定进程身份和权限的。当一个用户登入到UNIX系统之后,可以通过当前的交互shell来执行他自己的程序,此时执行起来的进程必须带有身份信息以便判断进程是否有权限访问文件。
进程首先得是一个可执行文件,该可执行文件被加载到内存执行起来之后才是一个进程。因为文件一定是属于某个用户的,也即属于某个ID的,所以执行后的进程也就是属于这个ID,这个ID叫做实际用户ID。但实际中,UNIX并非直接用这个实际用户ID,而是复制一份这个实际用户ID作为有效用户ID。文件的访问权限等判断是根据这个复制的有效用户ID来进行的。
在UNIX中,提供了一个额外的功能,那就是Set UID和Set GID,通过使用该功能可以将用户的身份切换到文件的所有者身份。举例来说,假设一个用户当前以admin账号的身份登录到系统上,那么该用户执行程序时,程序的实际身份和有效身份是admin,如果一个程序文件设置了Set UID权限为root,那么该程序被加载到内存中成为进程后,进程的有效身份不是admin,而是root,但实际身份保持admin不变。如下图所示,利用chmod命令手动对一个可执行文件增加Set UID权限,然后使用普通用户执行,其有效身份在执行过程中临时切换到了root:
文件访问权限
stat结构体中st_mode成员除了包含文件类型还包含了文件的访问权限,所有的文件都有权限控制,而不仅仅是普通文件有。为了打开任意类型的一个文件,则需要对该文件所在的父级以及父级的父级等目录具有执行权限。删除一个文件不需要对该文件有任何权限,只需要对被删除文件的父级目录具有写和执行权限即可。
进程每次打开、创建或删除一个文件时,内核就对该文件进行访问权限测试,通常的步骤是:
- 先判断进程是否是超级用户,即ID是0,是则允许访问,否则执行第二步;
- 再判断进程的有效用户ID是否等于文件的所有者ID,如果是并且被访问文件设定了适当的读写权限,则允许访问;否则执行第三步;
- 然后判断进程有效组ID或者附加组ID是否等于文件的组ID,如果是并且被访问文件设定了适当的读写权限,则允许访问;否则执行第四步;
- 最后查看文件的其他用户是否有适当权限访问文件,有则允许,否则判断结束、访问失败。
新文件和目录的所有权
当进程创建一个文件时,该文件的权限是进程的有效用户ID。而文件的组ID则有两种可能,POSIX规定可以是任意下面两种之一:
1.进程的有效组ID
2.新文件所在父级目录的组ID
函数access和faccessat
在使用open函数打开一个文件时,内核会进行文件访问权限的判定测试。如前所述,对权限的比较是通过进程的有效用户ID来进行的,但有时该进程的有效身份可能因为程序文件被设置了Set UID导致有效身份并非用户的实际身份,而用户想要该进程用自己的实际身份去测试文件是否可访问,而不是用有效身份去测试,此时可以使用access和faccessat函数来测试。access和faccessat函数会使得内核使用进程的实际用户ID去进行访问权限的判定,其头文件及函数原型如下:
- #include <unistd.h>
- int access (const char *name, int type);
- int faccessat (int fd, const char *file, int type, int flag);
上面的函数,成功时返回0,失败返回-1。
函数umask
函数umask用来辅助控制程序所创建的文件的权限。其头文件及函数原型如下:
- #include <sys/stat.h>
- mode_t umask (mode_t mask);
该函数没有出错返回,它返回之前的umask值。
函数chmod、fchmod和fchmodat
chmod、fchmod和fchmodat这三个函数用于更改一个文件的访问权限。其头文件及函数原型如下:
- #include <sys/stat.h>
- int chmod (const char *file, mode_t mode);
- int fchmod (int fd, mode_t mode);
- int fchmodat (int fd, const char *file, mode_t mode, int flag);
粘着位
粘着位原本用来阻止常用程序在结束后彻底退出内存,设置粘着位可以在系统重启前将常用的程序在使用完毕后不退出,而是把程序正文代码部分保存在内存交换区,以便下次快速载入启动,现今该技术已被淘汰,因为有了虚拟存储系统和更快更大的内存以及快速的文件系统。现在的系统扩展了粘着位的使用范围,允许对目录而不是程序设置粘着位。如果对一个目录设置了粘着位,那么对该目录具有写权限的用户具备下列三个条件之一,才能删除或重命名该目录下的文件:
- 拥有这个欲操作的文件,也即是文件的所有者;
- 拥有这个目录,也即是目录的所有者;
- 是超级用户。
目录/tmp是典型的粘着位的使用例子,所有的用户都有权限对其进行读写和执行,这个目录是一个缓存目录,所有用户的临时文件都能存放在这里,它会在每次重启系统或者定时被清空。由于所有人的临时文件都可以放在这里,并且所有人都能读写执行这个目录,因此就有必要阻止一个用户删除了其他用户的文件,粘着位就派上用场了。/tmp目录设置了粘着位,那么除非满足上面三个条件之一,否则无法删除文件。
函数chown、fchown、fchownat和lchown
chown系列函数用来改变文件的所属用户和所属组。其头文件及函数原型如下:
- #include <sys/stat.h>
- int chmod (const char *file, mode_t mode);
- int lchmod (const char *file, mode_t mode);
- int fchmod (int fd, mode_t mode);
- int fchmodat (int fd, const char *file, mode_t mode, int flag);
上面的函数,成功时返回0,失败返回-1。
文件长度
stat结构体中st_size成员的值表示以字节为单位的文件长度,该字段只对普通文件、目录文件和符号连接有意义,其他类型文件无意义。对于普通文件,如果其st_size值为0,则文件长度是0,读取该文件时,会立即返回eof文件结束标记。对于符号连接,文件长度是符号连接文件中保存的字符串的总长度。
文件中的空洞
在第三章中学习过关于文件偏移量的内容,当设置一个偏移量值大于文件的实际长度并在偏移量之后写入了数据,那么介于实际文件长度之后和偏移量之前的那段就形成了空洞,对于空洞,read函数读作0,。如果用copy命令复制一个带空洞的文件,那么复制后的目标文件的空洞会被真实填充为0。
文件截断
truncate函数用于将一个文件截断为指定长度。其头文件及函数原型如下:
- #include <unistd.h>
- int truncate (const char *file, off_t length);
- int ftruncate (int fd, off_t length);
上面的函数,成功时返回0,失败返回-1。
函数link、linkat、linkat、unlink和remove
link系列函数用来添加或解除对一个文件或目录的连接。其头文件及函数原型如下:
- #include <unistd.h>
- int link (const char *from, const char *to);
- int linkat (int fromfd, const char *from, int tofd, const char *to, int flags);
- int unlink (const char *name);
- int unlinkat (int fd, const char *name, int flag);
- #include <stdio.h>
- int remove (const char *filename);
上面的函数,成功时返回0,失败返回-1。
函数rename和renameat
函数rename和renameat用于对文件或者目录重命名。其头文件及函数原型如下:
- #include <stdio.h>
- int rename (const char *old, const char *new);
- int renameat (int oldfd, const char *old, int newfd, const char *new);
上面的函数,成功时返回0,失败返回-1。
符号链接
符号连接相当于Windows系统上的快捷方式,它与硬链接不同,硬链接具有下面的两条限制:
- 硬链接必须和所链接文件处于同一文件系统。
- 只有超级用户才能创建执行目录的硬链接。
而符号链接则没有上述限制。
创建和读取符号链接
函数symblink和symblinkat函数用于创建一个符号链接。其头文件及函数原型如下:
- #include <unistd.h>
- int symlink (const char *from, const char *to);
- int symlinkat (const char *from, int tofd, const char *to);
上面的函数,成功时返回0,失败返回-1。
函数readlink和readlinkat用于读取一个符号链接。其头文件及函数原型如下:
- #include <unistd.h>
- ssize_t readlink (const char *restrict path, char *restrict buf, size_t len);
- ssize_t readlinkat (int fd, const char *restrict path, char *restrict buf, size_t len);
上面的函数,成功时返回读取的字节数,失败返回-1。
文件的时间
UNIX系统中会对每个文件维护3个时间类型:
- st_atim:文件数据部分的访问时间
- st_mtim:文件数据部分的修改时间
- st_ctim:文件属性部分的修改时间
对于文件属性来说,其没有访问时间。
函数futimens、utimensat和utimes
文件的访问和修改时间可以利用这几个函数来进行更改。其头文件及函数原型如下:
- #include <sys/stat.h>
- int futimens (int fd, const struct timespec times[]);
- int utimensat (int fd, const char *path, const struct timespec times[], int flags);
从这两个函数的名字可以看出,它们的精确度是ns纳秒级别的。成功时返回0,失败返回-1。
- #include <sys/time.h>
- int utimes (const char *file, const struct timeval tvp[]);
utimes函数和前两个函数不同,它是通过路径名来操作文件。成功时返回0,失败返回-1。
函数mkdir、mkdirat和rmdir
mkdir和mkdirat函数用来创建目录,rmdir函数用来删除空目录。其头文件及函数原型如下:
- #include <sys/stat.h>
- int mkdir (const char *path, mode_t mode);
- int mkdirat (int fd, const char *path, mode_t mode);
- #include <unistd.h>
- int rmdir (const char *path);
上面的函数,成功时返回0,失败返回-1。
读目录
对于一个目录具有访问权限的用户都能读取该目录,只有内核才能写目录。一个目录的写和执行权限决定了能否在该目录中建立或者删除文件,但不表示这是写目录本身。关于目录的操作有一系列总计7个函数。其头文件及函数原型如下:
- #include <dirent.h>
- DIR *opendir (const char *name); //成功返回指针,失败返回NULL
- DIR *fdopendir (int fd); //成功返回指针,失败返回NULL
- struct dirent *readdir (DIR *dirp); //成功返回指针,失败返回NULL
- void rewinddir (DIR *dirp);
- int closedir (DIR *dirp); //成功时返回0,失败返回-1
- long int telldir (DIR *dirp); //成功时返回与dp关联的目录中的当前位置,失败返回-1
- void seekdir (DIR *dirp, long int pos);
函数chdir、fchdir和getcwd
每个进程都要一个当前工作目录,此目录是搜索所有相对路径的起点。每个登入到操作系统的用户都有一个默认工作目录,该目录在配置文件/etc/passwd中每个用户相关信息的第6个字段中指明。进程可以使用chdir和fchdir来更改当前工作目录。其头文件及函数原型如下:
- #include <unistd.h>
- int chdir (const char *path);
- int fchdir (int fd);
成功时返回0,失败返回-1。
有时进程也需要获取当前的工作路径,可以通过getcwd函数来获得。其头文件及函数原型如下:
- #include <unistd.h>
- char *getcwd (char *buf, size_t size);
成功是返回buf,失败返回NULL。
UNIX环境高级编程 第4章 文件和目录的更多相关文章
- 《UNIX环境高级编程》笔记——4.文件和目录
一.引言 本章描述文件系统的其他特征和文件的性质.有些背景知识需要注意,例如用户ID与文件权限.文件系统等. 二.函数stat.fstat.fstatat和lstat #include <sys ...
- UNIX系统高级编程——第四章-文件和目录-总结
文件系统: 以UNIX系统V文件系统为例: 磁盘分为区,每个分区都有自己的文件系统: i节点是固定长度的记录项,包含了文件的相关信息.目录项包含文件名和i节点号.stat结构中除文件名和i节点编号 ...
- UNIX环境高级编程 第3章 文件I/O
前面两章说明了UNIX系统体系和标准及其实现,本章具体讨论UNIX系统I/O实现,包括打开文件.读文件.写文件等. UNIX系统中的大多数文件I/O只需要用到5个函数:open.read.write. ...
- UNIX环境高级编程 第5章 标准I/O库
本章是关于C语言标准I/O库的,之所以在UNIX类系统的编程中会介绍C语言标准库,主要是因为UNIX和C之间具有密不可分的关系.由于UNIX系统存在很多实现,而每个实现都有自己的标准I/O库,为了统一 ...
- UNIX环境高级编程 第1章 UNIX基础知识
所有操作系统都为运行在它之上的程序提供各种服务,典型的服务包括:执行新程序.打开文件.读写文件.分配存储空间.提供时间等. UNIX体系结构 严格来说,操作系统是一种软件,它控制计算机硬件资源,提供程 ...
- unix环境高级编程第四章笔记
文件和目录 start fstart lstart函数 一旦给出pathname, start函数就返回了与此命名文件有关的信息结构 #include <sys/start> int st ...
- UNIX环境高级编程 第6章 系统数据文件和信息
UNIX系统的正常运作需要用到大量与系统有关的数据文件,例如系统用户账号.用户密码.用户组等文件.出于历史原因,这些数据文件都是ASCII文本文件,并且使用标准I/O库函数来读取. 口令文件 /etc ...
- UNIX环境高级编程 第8章 进程控制
本章是UNIX系统中进程控制原语,包括进程创建.执行新程序.进程终止,另外还会对进程的属性加以说明,包括进程ID.实际/有效用户ID. 进程标识 每个进程某一时刻在系统中都是独一无二的,它们之间是用一 ...
- UNIX环境高级编程 第7章 进程环境
本章涉及C/C++程序中main函数是如何被调用的.命令行参数如何传递给main函数.程序的内存空间布局.程序如何使用环境变量.程序如何终止退出. main函数 C程序或C++程序总是从main函数开 ...
随机推荐
- jquery 半透明遮罩效果 小结
最近偏离学术的道路越来越远了!! 今天要小结的是实现一个半透明遮罩效果.点击页面上的一个按钮,立即在屏幕的正中央显示某个部件,并且在这个部件之外的区域像是蒙上了一层半透明的遮罩.点击遮罩区域,该正中央 ...
- php三种方法从控制结构或脚本中跳出
PHP中,如果希望停止一段代码,根据需要达到的效果不同,可以有三种方法实现: 1. break: 如果在循环中使用了break语句,脚本就会从循环体后面的第一条语句开始执行: 2. continue: ...
- [洛谷P5216]DLS 采花
题目大意:有$n$个数,任意排列,排列后第$i$个数会产生贡献当且仅当$1\sim i-1$中的数不是它的因子,问所有排列的贡献和 题解:发现一个数要产生贡献要求所有它的因子在它的右边,设有$cnt_ ...
- Java之线程池和Lambda表达式
线程池和lambda表达式 学习线程池和lambda表达式的理解 补充一个知识点(单例设计模式) 在多线程中,我们只需要一个任务类,为了防止创建多个任务类,这个时候就需要用到单例模式,单例模式有两种设 ...
- HDU 2087 剪花布条(字符串匹配,KMP)
HDU 2087 剪花布条(字符串匹配,KMP) Description 一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案.对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出 ...
- 3:JavaBean,EJB,POJO
JavaBeanJavaBean是公共Java类,但是为了编辑工具识别,需要满足至少三个条件: 有一个public默认构造器(例如无参构造器,) 属性使用public 的get,set方法访问,也就是 ...
- struts2的MVC模式
MVC是一种架构型模式,它本身并不引入新的功能,只是用来指导我们改善应用程序的架构,使得应用的模型和视图相分离,从而得到更好的开发和维护效率. 在MVC模式中,应用程序被划分成了模型(Model).视 ...
- python 常用模块之os
1.权限判断 bool: os.access('/python/test.py',os.F_OK) #是否存在 bool: os.access('/python/test.py',os.R_OK) # ...
- K8S调度之节点亲和性
Node Affinity Affinity 翻译成中文是"亲和性",它对应的是 Anti-Affinity,我们翻译成"互斥".这两个词比较形象,可以把 po ...
- P4644 [Usaco2005 Dec]Cleaning Shifts 清理牛棚
P4644 [Usaco2005 Dec]Cleaning Shifts 清理牛棚 你有一段区间需要被覆盖(长度 <= 86,399) 现有 \(n \leq 10000\) 段小线段, 每段可 ...