第一节:umask权限屏蔽位
文件的类型:
| 符号 | 类型 |
|---|---|
| - | 普通文件 |
| d | 目录directory |
| l | 软链接link |
| c | 字符设备(终端/dev/tty) |
| b | 块设备(磁盘/dev/sda) |
| p | 管道fifo |
| s | socket |
| umask权限屏蔽器 | |
| umask的本质是一个要减掉的位 | |
| umask函数: |
mode_t umask(mode_t mask);
- 功能:设置新的屏蔽位
- 返回值:返回之前的屏蔽位
- 这个函数总是成功,没有失败的情况
核心公式:
文件的最终权限 = ~umask & 你传给 open 的 mode
用大白话讲就是umask是要扣掉的分,取反就是能保留的分,和mode做按位与,就是结果
为什么要有umask这个奇怪的东西?
为了批量省事
场景:你写了一个程序要创建1000个临时文件,写mode太麻烦,系统给你一个默认值,你只要mode = 0666,最终就是0664,省事又安全
umask的返回值为什么重要
mode_t old = umask(0777);
为什么要接住这个返回值?因为umask是全局设置,一旦改了就影响后面所有创建文件的操作
如果你想临时改、临时用完就还原,就必须记住旧值:
mode_t old = umask(0777);
umask(old);
第二节:查文件的属性
access---试一下这个文件我能不能动
int access(const char *pathname, int mode);
功能:检查文件的权限
四个mode参数:
| mode | 含义 |
|---|---|
| F_OK | File Okay,只测试文件是否存在 |
| R_OK | Read,只测试可读 |
| W_OK | Write,只测试可写 |
| X_OK | eXecute,只测试可执行 |
| 返回值: |
- 0:检查通过(文件存在、权限够)
- -1:不通过,设置errno
🔴注意:access检查的是真实权限,不是有效权限
- 真实权限:ls -l 显示的那个
- 有效权限:你当前身份实际能用的权限
stat---拿到文件的所有档案
int stat(const char *pathname, struct stat *statbuf);
- 功能:把文件的全部属性填进statbuf指向的结构体
- 成功:返回0
- 失败:返回-1并设置errno
struct stat全字段解读
struct stat {
dev_t st_dev; //设备的编号(文件在哪个磁盘分区上)
ino_t st_ino; //inode号---文件的身份证
nlink_t st_nlink; //硬链接个数
uid_t st_uid; //所有者uid
gid_t st_gid; //所属者gid
off_t st_size; //文件大小(字节)
blksize_t st_blksize; //文件系统的块大小
blkcnt_t st_blocks; //占用多少个块
struct timespec st_atim; //最后访问时间
struct timespec st_mtim; //最后修改内容时间
struct timespec st_ctim; //最后改元数据时间(权限、大小变了)
}
三个时间戳的区别:
| 时间 | 名字 | 什么时候变 |
|---|---|---|
| atime | access time | 你读了这个文件 |
| mtime | modify time | 你改了这个内容 |
| ctime | change time | 你改了元数据 |
| 举例: |
- 你cat一个文件:只有atime变
- 你vi改内容保存:mtime变
- 你chmod777:只有ctime变,mtime不变
inode:文件的身份证号
- 文件名=人的名字,可以改,可重名(同一目录下不行,但不同目录可以)
- inode号=人的身份证号,唯一,不可改
getpwuid/getgrgid---把数字翻译成名字
stat出来的uid/gid是数字,但是你想看的是名字,这就需要查表:
struct passwd *getpwuid(uid_t uid);
struct group *getgrgid(gid_t gid);
fstat---已经打开的文件怎么查属性
int fstat(int fd, struct stat *statbuf);
区别只有一个:参数从路径变成了fd
| 函数 | 参数 | 什么时候用 |
|---|---|---|
| stat | stat("a.txt",&st) | 你知道文件名 |
| fstat | fstat(fd,&st) | 文件已经打开,你手上有fd |
| 为什么需要fstat? |
- 省掉路径拼接---你已经open过了,直接查fd就行
- 防止竟态---如果你先用stat("a.txt")查到大小,再open("a.txt"),中间文件被别人改了你就错,用fstat就锁定的是这个fd,不受影响
- 处理特殊情况---有些fd不是文件,没有路径只能用fstat
第三节:目录操作
目录和文件有什么不同?
文件你有open打开,目录呢?Linux里目录也是一个文件,但是它的内容不是普通字节,而是一张表
所以有一套专门的函数:
| 操作 | 文件用 | 目录用 |
|---|---|---|
| 打开 | open | opendir |
| 读 | read | readdir |
| 关闭 | close | closedir |
| 得到一个流 | FILE*/fd | DIR* |
| DIR* 是目录专用的句柄类型,和FILE*、fd一样,是"打开后拿到的那个号码牌" |
opendir打开目录
DIR *opendir(const char *name);
- 成功:返回DIR*
- 失败:返回NULL并设置errno
readdir读目录
struct dirent *readdir(DIR *dirp);
一次读一个目录项,也就是目录里的一个名字(可能是文件,也可能是子目录)
返回值有三种情况:
| 返回值 | 含义 | errno状态 |
|---|---|---|
| 非NULL指针 | 成功,读到一个目录项 | 不变 |
| NULL | 读完了 | 不变(保持原值) |
| NULL | 出错了 | 被设置 |
| 🔴区分读完了和出错了就看errno有没有被设置 |
closedir关闭目录
int closedir(DIR *dirp);
- 成功:返回0
- 失败:返回-1并设置errno
readdir的正确用法
因为读完了也返回NULL,所以必须先清空errno
struct dirent结构体
struct dirent {
ino_t d_ino; //inode号
off_t d_off;
unsigned short d_reclen; //这条记录有多长
unsigned char d_type; //文件类型
char d_name[256]; //文件名/目录名
}
🔴只需要关心两个字段
d_name256---里面的名字,这可能是文件名,可能是子目录名,还可能是./...
d_type---文件类型
| d_type | 含义 |
|---|---|
| DT_REG | 普通文件regular |
| DT_DIR | 目录Directory |
| DT_LNK | 软链接Link |
| DT_CHR | 字符设备 |
| DT_BLK | 块设备 |
| DT_FIFO | 管道 |
| 一个重要提醒:不是所有文件系统都支持d_type,有些文件系统的值是DT_UNKNOWN,这时候只能靠stat去查 |
必须认识的三个特殊名字
| 名字 | 是什么 |
|---|---|
| . | 当前目录 |
| ... | 上一级目录 |
其他三个目录函数
mkdir创建目录
int mkdir(const char *pathname, mode_t mode);
- 成功:返回0
- 失败:返回-1并设置errno
- mode权限和open的第三个参数一样是八进制
🔴mkdir的mode同样受umask影响,最终权限=~umask & mode
rmdir删除目录
int rmdir(const char *pathname);
- 成功:返回0
- 失败:返回-1
关键限制:rmdir只能删除空目录,如果目录里面还有文件,会失败返回-1
🔴为什么?因为你让用户删除一个目录,他以为是删除这文件夹,结果文件全没了,那就太危险,所以强制要求先清空
chdir切换目录
int chdir(const char *path);
- 成功:返回0
- 失败:返回-1