思考一个问题(如果你很清楚的知道答案,那么下面的内容你就不必看了):
假设有一个用户A,他创建了一个可执行文件a.out(rwxrwx–x)和一个文本文件a.txt(rwxrwx—),在a.out中,会对文件进行读写。假设还有另一个用户B,和A不是一个属组的,他现在执行a.out,假设叫进程B,这时候
- 请问可以成功对文件a.txt进行读写吗?什么情况下可以?
- 进程B的实际用户ID是?有效用户ID是?进程B的设置用户ID有什么用?
- 说明:
- B当然不是root用户
- a.out的文件模式字(st_mode)没有说明因为这是题目讨论的一部分
- 想确切的知道答案的话最好看一下《unix环境下高级编程》的第四章
1、stat()、fstat()、fstatat()、lstat()
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstatat(int dirfd, const char *pathname, struct stat *buf,
int flags);
-
stat函数返回与命名文件有关的信息结构体
-
lstat类似,但当文件是一个符号链接时,lstat返回符号链接的有关信息,而非它链接的文件的信息
-
fstatat为一个相对于当前打开目录(fd)的路径名返回文件统计信息
- flags为AT_SYMLINK_NOFOLLOW时不会跟随符号链接
- fd为AT_FDCWD时为当前目录(path不是绝对路径)
-
stat结构如下:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
2、文件类型
- -dlbcps 分别表示普通文件、目录文件、链接文件、块设备文件,比如磁盘、串行端口设备文件(字符设备文件)、管道文件、套接字
3、设置用户ID和设置组ID
- 与一个进程相关的有7个ID
- 1.实际用户ID
- 2.实际组ID
- 3.有效用户ID
- 4.有效组ID
- 5.附属组ID
- 6.保存的设置用户ID
- 7.保存的设置组ID
- 可通过sysconf(_SC_SAVED_IDS)检查是否支持
- 345决定了文件访问权限
- 67在执行程序时包含了12的副本
- 通常3=1,4=2
- 通过设置文件模式字67可以:执行此文件时,将进程的3设置文件所有者的用户ID,将4设置为文件的组所有者ID
- 67可用S_ISUID(st_mode)、S_ISGID(st_mode)测试
4、文件访问权限
- 所有文件都有文件访问权限
- st_mode值包含了文件的访问权限位,即
S_I[RWX]USR|S_I[RWX]GRP|S_I[RWX]OTH
- 思考:目录的读权限和可执行权限有什么区别:
- 打开任意文件时,对该文件包含的每一个目录都应具有执行权限
- 读权限决定能否打开进行读
- 写权限决定了能否打开进行写
- O_TRUNK标志必须具有写权限
- 要在目录中创建文件必须具有写权限和执行权限
- rm必须具有WX权限
- exec执行的文件必须具有R权限,且必须是普通文件
- 每次打开、创建或删除一个文件时,内核进行下面的测试:
- 若进程的有效用户ID是0,那么允许访问
- 若有效用户ID等于文件所有者ID(进程拥有此文件),适当权限位被设置时允许访问
- 有效组ID或附属组ID等于文件组ID时,同上
- 若其他用户适当权限被设置,同上
5、新文件和目录的所有权
- 新文件的用户ID设置为进程的有效用户ID
- 组ID可以是下面的(linux下先2再考虑1):
- 1.进程的有效组ID
- 2.所在目录的组ID
6、access()和faccessat()
int access(const char *pathname, int mode);
int faccessat(int dirfd, const char *pathname, int mode, int flags);
- open()打开一个文件时,内核用有效用户ID和有效组ID为基础执行访问权限测试。
- 有时用户希望按照实际用户ID和实际组ID来测试访问权限(即用户可能已经通过设置用户以root权限运行,但还想验证实际用户能否访问一个给定的文件)
- mode可以下面常数的按位与
- R_OK、W_OK、X_OK
- 当pathname为绝对路径或fd取值为AT_FDCWD时,faccessat()与access()相同
- flag为AT_EACCESS时,检查的就是有效用户ID而非实际用户ID
7、umask()
mode_t umask(mode_t mask);
- 为进程设置文件模式创建屏蔽字,并返回之前的值–不影响父进程
- 想确保任何用户都能读文件,就应该设置umask为0
umask -S
查看许可权限而非拒绝权限
8、chmod()、fchmod()、fchmodat()
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
- 上述函数改变现有文件的读写权限
- 在pathname为绝对路径或dirfd为AT_FDCWD时fchmod和fchmodat相同
- 当flags为AT_SYMLINK_NOFOLLOW标志时,fchmodat不会跟随符号链接
- mode参数除了前面提到的
S_I[RWX]USR|S_I[RWX]GRP|S_I[RWX]OTH
:- S_ISUID:执行时设置用户ID
- S_ISGID:执行时设置组ID
- S_ISVTX:保存正文(沾着位)
9、沾着位
- UNIX版本称它为保存正文位,配置了虚拟存储系统以及快速文件系统后不再需要这种技术了
- 现在扩展了沾着位的用法,如果一个目录设置了沾着位,对该目录具有写权限的用户在下列条件满足时才能删除或重命名目录下的文件(/tmp、/var/tmp):
- 拥有此文件
- 拥有此目录
- 是超级用户
10、chown()、fchown()、fchownat()、lchown()
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
int fchownat(int dirfd, const char *pathname,
uid_t owner, gid_t group, int flags);
- lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW)更改符号链接本身的所有者而非指向的文件
- 可通过_POSIX_CHOWN_RESTRICTED常量、pathconf()或fpathconf()常量查询是否支持任意用户更改文件的所有者,生效时:
- 超级用户可以改变文件的用户ID
- 进程拥有此文件,owner=-1或文件用户ID,且group等于进程的有效组ID或附属ID,那么非root用户可以更改文件的组ID
11、文件长度
- stat结构的st_size表示字节为单位的文件长度(对普通文件、目录文件和符号链接有意义)
- 对目录,长度通常是一个数(16/512)的整数倍
- 对于符号链接,长度是文件名的实际字节数(不包含null)
- st_blksize和st_blocks分别表示对文件IO较适合的块长度和分配的实际512字节块数
11.1 文件空洞
- 有空洞的文件使用ls -l与du命令查看可以得到不同的结果
- cat命令复制后会填满所有空洞
12、文件截断
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
13、文件系统
本节讨论UFS:以berkeley快速文件系统为基础的
- 我们可以把一个磁盘分成一个或多个区,每个分区可以包含一个文件系统。i节点是固定长度的记录项
[外链图片转存失败(img-ezwsY9xz-1567246141238)(./images/文件系统.jpg)] - 删除一个目录项被称为unlink而非delete?
- st_nlink(硬链接数):每个i节点有一个链接计数,减少到0时才可删除。
- 符号链接:文件内容(数据块中)包含了该符号链接指向的文件的名字,i节点中存储为S_IFLNK
- i节点包含了文件的所有信息:类型权限长度数据块指针等,除了目录项中的文件名和i节点编号
- ln为什么不能跨文件系统
- 目录项中的i节点编号指向同一文件系统中相应的i节点
- 重命名文件时,只需构造一个指向现有i节点的新目录项并删除老的目录项
- 对于目录,叶链接计数总是2,父目录是3
14、link()、linkat()、unlink()、unlinkat()、remove()
int link(const char *oldpath, const char *newpath);
int linkat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, int flags);
- 函数创建新的目录项,通过引用现有文件(最好在一个文件系统下)
- 就算允许对目录创建硬链接,也仅限于root用户(因为可能造成循环)
int unlink(const char *pathname);
int unlinkat(int dirfd, const char *pathname, int flags);
- 函数删除目录项,链接数减1
int remove(const char *pathname);
- 函数解除对一个文件或目录的链接
15、rename()或renameat()
int rename(const char *oldpath, const char *newpath);
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
- 对文件或目录重命名
16、符号链接
- 符号链接一般用于将一个文件或目录结构移动到系统中的另一个位置
17、创建和读取符号链接
int symlink(const char *oldpath, const char *newpath);
int symlinkat(const char *oldpath, int newdirfd, const char *newpath);
- 因为open函数跟随符号链接,所有有下面的函数:
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
int readlinkat(int dirfd, const char *pathname,
char *buf, size_t bufsiz);
18、文件时间
- 对每个文件系统维持三个时间段
- st_atim:文件数据最后访问时间 ls -u
- st_mtim:最后修改时间 ls
- st_ctim:i节点的最后更改时间 ls -c
19、futimens()、utimensat()、utimes()
#include <sys/stst.h>
int futimes(int fd, const struct timespec times[2]);
int utimesat(int fd, const char *path, const struct timespec times[2], int flag);
- times[0]为访问时间,times[1]为修改时间,值是日历时间
- 时间戳可以按下列4种方式之一进行绑定:
- times为空时,访问时间和修改时间都设置为当前时间
- times指向两个timespec结构数组,且任一元素为UTIME_NOW,则相应的时间设置为当前时间,忽略tv_sec字段
- times指向两个timespec结构数组,且任一元素为UTIME_OMIT,相应时间戳保持不变
- times指向两个timespec结构数组,且不是UTIME_NOW|UTIME_OMIT,设置为指定的时间戳
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
- 该函数对路径名尽心操作
20、mkdir()、mkdirat()、rmdir()
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int dirfd, const char *pathname, mode_t mode);
int rmdir(const char *pathname);
- 函数创建一个新的空目录
- 注意至少要设置一个执行权限位,以允许访问目录中的文件名
21、读目录
- 有权限的任一用户可以读目录,但只有内核能写目录
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
int readdir(unsigned int fd, struct old_linux_dirent *dirp,
unsigned int count);
void rewinddir(DIR *dirp);
int closedir(DIR *dirp);
long telldir(DIR *dirp);
void seekdir(DIR *dirp, long offset);
22、chdir()、fchdir()、getcwd()
int chdir(const char *path);
int fchdir(int fd);
- 函数更改当前的工作目录
#include <unistd.h>
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);
- 函数获取当前路径名
23、设备特殊文件
- 每个文件系统所在的存储设备都由其主、次设备号表示(dev_t)。
- 主设备号标识设备驱动程序,有时编码为与其通信的外设备板
- 次设备号标识特定的子设备
- (同一磁盘驱动器上的文件系统通常具有相同的主设备号,但次设备号却不同)
- 通常可以使用两个宏:major、minor来访问主、次设备号
- 系统中与每个文件名关联的st_dev值是文件系统的设备号
- 只有特殊文件和块特殊文件才有st_dev值(实际设备的设备号)