注:前言、目录见 https://blog.csdn.net/qq_44220418/article/details/108428971
一、文件系统简介
1、索引节点
Linux系统采用按名存取的方式访问文件
除了文件名以外,文件的主要属性信息都存放在inode节点中
目录文件中保存着文件名和索引节点的对应关系【可用命令ls -i
查看文件/目录的inode号】
Linux系统中文件包括两部分
- 索引节点inode
\qquad 记录文件属性信息(除了文件名)【可用命令ls -l
查看】
\;
\qquad 文件属性包括 { 文 件 类 型 访 问 权 限 文 件 主 人 组 长 度 访 问 日 期 … … \begin{cases} 文件类型 \\ 访问权限 \\ 文件主人 \\ 组 \\ 长度 \\ 访问日期 \\ …… \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧文件类型访问权限文件主人组长度访问日期……
\;
\qquad 索引节点的结构如下图所示:
\qquad\qquad - 数据块
\qquad 存放文件具体内容
注:硬链接文件的inode与源文件的inode相同
更多关于inode节点、软链接与硬链接的区别相关的知识可以参考博文 Linux文件索引节点inode
2、文件系统组成
一个简单的Unix文件系统组成
\qquad
\qquad
引导块:用于引导该分区内操作系统的引导程序
\qquad
超级块:UFS的重要参数,如文件系统的
{
块
索
引
节
点
总
数
空
闲
块
空
闲
索
引
节
点
数
\begin{cases} 块 \\ 索引节点总数 \\ 空闲块 \\ 空闲索引节点数 \\ \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧块索引节点总数空闲块空闲索引节点数
\qquad
索引节点表(i节点):文件系统中索引节点的集合
\qquad
数据块:存储文件数据
Linux文件系统——Ext2
\qquad
\qquad
第一个是引导块,其他空间分成各个块组
\qquad
每个块组中
{
超
级
块
块
组
描
述
符
,
数
据
块
位
图
,
索
引
节
点
位
图
,
索
引
节
点
,
数
据
块
\begin{cases} 超级块 \\ 块组描述符,数据块位图,索引节点位图,索引节点,数据块 \end{cases}
{超级块块组描述符,数据块位图,索引节点位图,索引节点,数据块
3、文件
(1).类型
文件类型 { 普 通 文 件 ( rugular file ) 目 录 文 件 ( directory ) 字 符 设 备 文 件 ( character device ) 块 设 备 文 件 ( block device ) FIFO 文 件 ( fifo ) 符 号 链 接 文 件 ( symbolic link ) socket 套 接 字 文 件 ( socket ) \begin{cases} 普通文件 & (\text{rugular file})\\ 目录文件 & (\text{directory})\\ 字符设备文件 & (\text{character device})\\ 块设备文件 & (\text{block device})\\ \text{FIFO}文件 & (\text{fifo})\\ 符号链接文件 & (\text{symbolic link})\\ \text{socket}套接字文件 & (\text{socket})\\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧普通文件目录文件字符设备文件块设备文件FIFO文件符号链接文件socket套接字文件(rugular file)(directory)(character device)(block device)(fifo)(symbolic link)(socket)
(2).文件权限
[1].文件权限
Linux中的用户分为三类 { user/owner 文 件 拥 有 者 group 用 户 组 other 其 他 用 户 \begin{cases} \text{user/owner} & 文件拥有者 \\ \text{group} & 用户组 \\ \text{other} & 其他用户 \\ \end{cases} ⎩⎪⎨⎪⎧user/ownergroupother文件拥有者用户组其他用户
Linux文件对三类用户都有三种访问权限 { r 可 读 w 可 写 x 可 执 行 \begin{cases} \text{r} & 可读 \\ \text{w} & 可写 \\ \text{x} & 可执行 \\ \end{cases} ⎩⎪⎨⎪⎧rwx可读可写可执行
另外,Linux文件还有三种特殊的权限
{
setuid
setgid
sticky
\begin{cases} \text{setuid} \\ \text{setgid} \\ \text{sticky} \\ \end{cases}
⎩⎪⎨⎪⎧setuidsetgidsticky
这里就不对这三种特殊权限做具体介绍了,想知道更详细的可以去看博文 Linux文件特殊权限——SetUID、SetGID、Sticky BIT
Linux文件的索引节点里面有一个st_mode
字段,记录文件类型及权限
该字段共
16
16
16 位
{
高
4
位
文
件
类
型
低
12
位
文
件
权
限
\begin{cases} 高4位 & 文件类型 \\ 低12位 & 文件权限 \\ \end{cases}
{高4位低12位文件类型文件权限
12位文件权限 { 特 殊 权 限 { setuid setgid sticky 文 件 拥 有 者 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 用 户 组 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 其 他 用 户 { r 可 读 权 限 w 可 写 权 限 x 可 执 行 权 限 \begin{cases} 特殊权限 & \begin{cases} \text{setuid} \\ \text{setgid} \\ \text{sticky} \\ \end{cases} \\ \\ 文件拥有者 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \\ 用户组 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \\ 其他用户 & \begin{cases} \text{r} & 可读权限 \\ \text{w} & 可写权限 \\ \text{x} & 可执行权限 \\ \end{cases} \\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧特殊权限文件拥有者用户组其他用户⎩⎪⎨⎪⎧setuidsetgidsticky⎩⎪⎨⎪⎧rwx可读权限可写权限可执行权限⎩⎪⎨⎪⎧rwx可读权限可写权限可执行权限⎩⎪⎨⎪⎧rwx可读权限可写权限可执行权限
如下图所示:
[2].表示方法
文件权限的表示方法
- 字母
如:字母 权限 r
可读 w
可写 x
可执行 -
无 rwxr-xr-x
就表示了 { 拥 有 者 对 文 件 可 读 、 可 写 、 可 执 行 用 户 组 对 文 件 可 读 、 可 执 行 其 他 用 户 对 文 件 可 读 、 可 执 行 \begin{cases} 拥有者对文件可读、可写、可执行 \\ 用户组对文件可读、可执行 \\ 其他用户对文件可读、可执行 \\ \end{cases} ⎩⎪⎨⎪⎧拥有者对文件可读、可写、可执行用户组对文件可读、可执行其他用户对文件可读、可执行 - 数字(9位二进制 / 3位八进制)
和上面的字母类似,对应位的二进制 { 1 有 权 限 0 无 权 限 \begin{cases} 1 & 有权限 \\ 0 & 无权限 \\ \end{cases} {10有权限无权限
如:111 101 101
就相当于rwxr-xr-x
,755
就相当于rwxr-xr-x
- 宏(S_IPwww模式)
S_I
固定,P
可为R
或W
或X
,www
可为USR
或GRP
或OTH
,多种权限可用或运算连接
如:S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
就相当于rwxr-xr-x
注:编程中作为参数时,只能使用数字/宏
(3).访问文件
每打开一次文件
- 系统增加一个
file
表项 - 进程增加一个
fd
表项(以文件描述符为下标),通过指针指向file
表项 - 用户通过文件描述符和系统调用访问该文件
二、文件基本I/O操作
1、文件描述符
用户空间中任何打开的文件都被分配一个唯一的非负整数,标识该打开文件,即文件描述符
进程默认打开的三个文件描述符
- 标准输入
0
- 标准输出
1
- 标准错误输出
2
会选用文件描述符中未使用的最小值
2、常用系统调用
常用的系统调用如下
系统调用 | 说明 |
---|---|
open | 打开/新建文件 |
close | 关闭文件 |
creat | 新建文件 |
read | 读文件 |
write | 写文件 |
lseek | 定位文件偏移量 |
(1).open
定义在<fcntl.h>
中
函数原型如下:
int open( const char *path, int flags);
int open( const char *path, int flags, mode_t perms);
-
功能
-
打开一个已经存在的文件时,参数
perms
可以省略 -
创建并打开一个不存在的文件时,参数
perms
必填
参数
-
参数 说明 path
文件路径 flags
文件打开方式(可用或运算 |
连接多种打开方式)
关于flags
可选的宏参数可参考博客 https://blog.csdn.net/ArchyLi/article/details/78937937perms
文件权限(4位八进制,仅创建时需要)
返回值
- 成功:返回最小可用的文件描述符(当前文件所用的文件描述符)
-
失败:返回
-1
(置errno
)
(2).close
定义在<unistd.h>
中
函数原型如下:
int close(int fd );
-
功能
- 关闭文件,释放文件描述符,使之可再利用 参数
-
参数 说明 fd
文件描述符
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
注:当一个进程终止时,内核会自动检查并回收该进程所有的文件描述符
(3).write
定义在<unistd.h>
中
函数原型如下:
ssize_t write(int fd, const void *buf, size_t nbytes);
-
功能
-
将
buf
所指缓冲区中的nbytes
个字节写入文件描述符fd
所指示的已打开文件中
注意点
- 写操作从当前文件偏移量处开始写数据,写完后,文件偏移量也将后移成功写入的字节数(因此不移动读写指针读不到刚刚写入的内容)
-
若调用
open
打开文件时,读写指针初始指向文件开头
\qquad 若flags
包含O_APPEND
,则每次写入前,读写指针都将移动到文件末尾 -
由于物理介质空间不足等原因将会使得
write
的返回值小于计划要写入文件的字节个数
参数
-
参数 说明 fd
文件描述符 buf
要写入的数据 nbytes
要写入数据的字节数
返回值
- 成功:返回已写入的字节数
-
失败:返回
-1
(置errno
)
(4).read
定义在<unistd.h>
中
函数原型如下:
ssize_t read(int fd, void *buf, size_t nbytes);
-
功能
-
从一文件描述符
fd
对应的已打开文件中,读取nbytes
字节的数据放入buf
中
注意点
- 读操作从当前文件偏移量处开始读数据,读完后,当前文件偏移也将改变成功读出的字节个数
-
若调用
open
打开文件时,读写指针初始指向文件开头 -
有时读到的数据可能比希望读的字节少,但系统不会置
errno
,因为这不是错误(如文件已经到结尾),需要用户自己去推测问题所在
参数
-
参数 说明 fd
文件描述符 buf
保存读取数据的缓冲区 nbytes
要读取数据的字节数
返回值
- 成功:返回已读取的字节数
-
失败:返回
-1
(置errno
)
(5).lseek
定义在<unistd.h>
中
函数原型如下:
off_t lseek(int fd, off_t pos, int whence);
-
文件偏移量
- 文件偏移量是一个非负整数,它用于标识下一次读或写文件的位置
-
除了
O_APPEND
模式打开文件外,读/写操作都从当前文件偏移量处开始 -
每
open
一次文件,就能得到一个新的打开文件表项,因此每次open
都可以得到一个独立的文件偏移量
功能
- 移动文件读写指针(文件偏移量) 参数
-
参数 说明 fd
文件描述符 pos
相对于 whence
的位置的偏移量whence
SEEK_SET
:代表文件开始,即设文件偏移量为pos
SEEK_CUR
:代表当前位置,即设文件偏移量为当前值+pos
SEEK_END
:代表文件结尾,即设文件偏移量为文件长度+pos
返回值
- 成功:返回当前文件偏移量
-
失败:返回
-1
(置errno
)
(6).使用示例
示例1:创建文件权限为rw-r--r--
的文件test.txt
(初始时不存在),在其末尾写入内容
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
// 创建并打开文件test.txt
int fd = open("test.txt", O_RDWR | O_APPEND | O_CREAT, 0644);
if (fd == -1)
{
perror("open");
exit(1);
}
// 写入文件
char str1[] = "123456789012345678901234567890\n";
if (write(fd, str1, strlen(str1)) != -1)
{
printf("写入成功\n");
}
// 关闭文件
close(fd);
return 0;
}
程序输出的内容如下
写入成功
生成的test.txt
文件内容如下
123456789012345678901234567890
示例2:从刚刚 示例1 生成的test.txt
中读取至多
99
99
99 字节的数据
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
// 打开文件
int fd = open("test.txt", O_RDWR);
if (fd == -1)
{
perror("open");
exit(1);
}
// 读取文件
char buf[100];
read(fd, buf, sizeof(buf) - 1);
printf("读取到的字符串是\n%s\n", buf);
// 关闭文件
close(fd);
return 0;
}
示例3:创建文件权限为rw-r--r--
的文件test3.txt
(初始时不存在),利用lseek
函数定位,写入、读取文件内容
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
// 创建
int fd3 = open("test3.txt", O_RDWR | O_CREAT, 0644);
char str2[] = "newnewnewnewnewnewnewnew\n";
char str3[] = "test\n";
char buf[100];
// 向文件末尾写入数据
lseek(fd3, 0, SEEK_END);
write(fd3, str2, strlen(str2));
// 从文件开头读取数据
lseek(fd3, 0, SEEK_SET);
read(fd3, buf, sizeof(buf) - 1);
printf("读取到的字符串3是\n%s\n", buf);
// 向文件开头写入数据
lseek(fd3, 0, SEEK_SET);
write(fd3, str3, strlen(str3));
// 从文件开头读取数据
lseek(fd3, 0, SEEK_SET);
read(fd3, buf, sizeof(buf) - 1);
printf("读取到的字符串4是\n%s\n", buf);
close(fd3);
return 0;
}
程序输出的内容如下
读取到的字符串3是
newnewnewnewnewnewnewnew
读取到的字符串4是
test
wnewnewnewnewnewnew
生成的test3.txt
文件内容如下
test
wnewnewnewnewnewnew
显然这个最终结果是将test3.txt
之前的前5个字符"newne"
修改成了"test\n"
(7).探索与练习
探索1:对同一文件的不同fd
的操作是否会造成相互影响?
已存在test2.txt
,内容如下:
1234321222222
编写了如下C源程序对其进行探究:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
char str1[] = "123456789012345678901234567890\n";
char buf[100];
int fd1 = open("test2.txt", O_RDWR | O_APPEND);
int fd2 = open("test2.txt", O_RDONLY);
write(fd1, str1, strlen(str1));
write(fd1, str1, strlen(str1));
write(fd1, str1, strlen(str1));
read(fd2, buf, sizeof(buf) - 1);
printf("读取到的字符串2是\n%s\n", buf);
close(fd1);
close(fd2);
return 0;
}
程序输出的内容如下
读取到的字符串2是
1234321222222
123456789012345678901234567890
123456789012345678901234567890
1234567890123456789012
修改后的test2.txt
文件内容如下
1234321222222
123456789012345678901234567890
123456789012345678901234567890
123456789012345678901234567890
显然,fd1
和fd2
都是已打开文件test2.txt
的文件描述符
fd1
的写操作改变了fd1
的文件偏移量,而没有改变fd2
的文件偏移量(从读操作正常可见)
这也就验证了 每次open
都可以得到一个独立的文件偏移量
练习1:将键盘输入的一批字符串(以quit
结束)写入到文件test.txt
中,然后再读出并显示到屏幕上
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char str[999];
char buf[100];
// 打开文件
int fd = open("test.txt", O_CREAT | O_RDWR, 0644);
// 循环读入并写入
scanf("%s", &str);
while (strcmp(str, "quit"))
{
// 写入
write(fd, str, strlen(str));
scanf("%s", &str);
}
// 将文件偏移量定位到文件开头
lseek(fd, 0, SEEK_SET);
// 循环读取文件内容
printf("读取到的写入内容如下:\n");
while (read(fd, buf, sizeof(buf) - 1))
{
printf("%s", buf);
}
printf("\n");
// 关闭文件
close(fd);
return 0;
}
程序输入的内容如下
1234
abc
quit
程序输出的内容如下
读取到的写入内容如下:
1234abc
创建的test.txt
文件内容如下
1234abc
三、目录操作
1、目录文件
Linux系统中,目录也是以文件的方式存储,在目录文件中存储了该目录下的文件的信息,包括 { 文 件 名 文 件 i n o d e 编 号 文 件 类 型 ⋯ \begin{cases} 文件名 \\ 文件inode编号 \\ 文件类型 \\ \cdots \\ \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧文件名文件inode编号文件类型⋯,姑且把这些信息称为一个文件的索引信息
如果想要找到某个文件,首先需要找到这个文件所在目录的目录文件
2、常用系统调用
常用的系统调用如下
系统调用 | 说明 |
---|---|
opendir | 打开目录 |
readdir | 读取目录 |
telldir | 返回当前打开的目录的下次读取位置 |
seekdir | 设置目录流的读取位置为指定位置 |
rewinddir | 重置目录流的读取位置为起始位置 |
closedir | 关闭目录 |
(1).opendir
定义在<dirent.h>
中
函数原型如下:
DIR * opendir(const char *dir_path);
-
功能
-
打开一个目录,返回一个
DIR
指针,从而创建一个到目录的连接
参数
-
参数 说明 dir_path
目录路径
返回值
- 成功:返回指向参数目录的指针
-
失败:返回
NULL
(置errno
)
(2).readdir
定义在<dirent.h>
中
函数原型如下:
struct dirent * readdir(DIR *dir);
-
功能
-
从
DIR
指针中读取一个目录项(文件/目录)的信息,并后移指针对应目录的读取位置
参数
-
参数 说明 dir
已打开目录对应的 DIR
指针
返回值
- 成功:返回相应目录项的结构体指针
-
失败:返回
NULL
(目录项读取完成)
(3).closedir
定义在<dirent.h>
中
函数原型如下:
int closedir(DIR * dir);
-
功能
- 关闭目录 参数
-
参数 说明 dir
已打开目录对应的 DIR
指针
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(4).rewinddir
定义在<dirent.h>
中
函数原型如下:
void rewinddir(DIR * dir);
-
功能
- 重置目录指针读取位置到起始位置 参数
-
参数 说明 dir
已打开目录对应的 DIR
指针
(5).seekdir
定义在<dirent.h>
中
函数原型如下:
void seekdir(DIR * dir, off_t offset);
-
功能
- 设置目录指针读取位置到指定位置 参数
-
参数 说明 dir
已打开目录对应的 DIR
指针
(6).telldir
定义在<dirent.h>
中
函数原型如下:
off_t telldir(DIR * dir);
-
功能
-
返回目录指针下次读取的位置(可以理解为一个从
0
开始的索引号)
参数
-
参数 说明 dir
已打开目录对应的 DIR
指针
返回值
- 成功:返回当前目录指针下次读取的位置
-
失败:返回
-1
(置errno
)
(7).mkdir
定义在<sys/stat.h>
中
函数原型如下:
int mkdir(char * pathname, mode_t mode);
-
功能
- 创建目录,并指定其权限模式 参数
-
参数 说明 pathname
创建目录的路径 mode
创建目录的权限模式
Tips:关于mode
的选项,可以参考博客 mkdir()函数、mode_t参数
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(8).rmdir
定义在<unistd.h>
中
函数原型如下:
int rmdir(char * pathname);
-
功能
- 删除目录 参数
-
参数 说明 pathname
要删除目录的路径
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(9).getcwd
定义在<unistd.h>
中
函数原型如下:
char* getcwd(char *buf, size_t size);
-
功能
- 获取进程当前所处的工作目录的绝对路径,存入参数字符数组中 参数
-
参数 说明 buf
存储路径的字符数组 size
buf
所指向字符数组的字节数
返回值
- 成功:返回存储空间的首地址
-
失败:返回
NULL
(置errno
)
(10).chdir
定义在<unistd.h>
中
函数原型如下:
int chdir(const char *pathname);
-
功能
- 切换当前进程所处的工作目录 注意点
- 对其他进程、调用该进程的进程的当前工作目录均没有影响 参数
-
参数 说明 pathname
要切换到的工作目录路径
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(11).rename
定义在<unistd.h>
中
函数原型如下:
int rename(const char *from, const char *to);
-
功能
- 移动并重命名文件 注意点
-
rename
并不真正复制文件中的数据,它只是将文件的链接从一个目录移动到另一个目录中,这个过程数据本身并没有移动
参数
-
参数 说明 from
源文件路径 to
新文件路径
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(12).使用示例
示例4:尝试使用上述若干系统调用读取、创建、删除、更改、查看目录
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
// 打开目录testdir
DIR * dir_ptr = opendir("testdir");
struct dirent * dir;
// 获取下次读取位置
if (dir_ptr != NULL)
printf("下次读取的位置是:%ld\n\n", telldir(dir_ptr));
// 获取目录下所有文件的名称
printf("第一次打印:\n");
if (dir_ptr != NULL)
while ((dir = readdir(dir_ptr)) != NULL)
printf("%s\n", dir->d_name);
printf("\n");
// 重定位,重新读取
printf("第二次打印:\n");
if (dir_ptr != NULL)
{
rewinddir(dir_ptr);
while ((dir = readdir(dir_ptr)) != NULL)
printf("%s\n", dir->d_name);
}
printf("\n");
// 重定位,重新读取
printf("第三次打印:\n");
if (dir_ptr != NULL)
{
seekdir(dir_ptr, 3);
while ((dir = readdir(dir_ptr)) != NULL)
printf("%s\n", dir->d_name);
}
printf("\n");
// 关闭目录
closedir(dir_ptr);
// 创建目录
int res = mkdir("./testmk", 0777);
if (!res)
printf("创建目录成功\n\n");
// 删除目录
res = rmdir("./testmk");
if (!res)
printf("删除目录成功\n\n");
// 获取当前工作目录
char str[100];
getcwd(str, sizeof(str) - 1);
printf("当前工作目录:\n%s\n\n", str);
// 改变并重新获取当前工作目录
res = chdir("./testdir/");
if (!res)
{
getcwd(str, sizeof(str) - 1);
printf("改变后的当前工作目录:\n%s\n\n", str);
}
// 移动并重命名目录/文件
// (注:因为已经改变了当前工作目录,所以现在已经跳转到了testdir目录下,再用"./testdir"就不管用了)
int res2 = rename("./subdir/", "./newname/");
int res3 = rename("./a.txt", "./b.txt");
if (!res2 && !res3)
{
printf("重命名成功\n");
dir_ptr = opendir("./");
printf("重命名后的打印:\n");
if (dir_ptr != NULL)
while ((dir = readdir(dir_ptr)) != NULL)
printf("%s\n", dir->d_name);
printf("\n");
}
return 0;
}
程序输出的内容如下
下次读取的位置是:0
第一次打印:
.
..
12.txt
subdir
a.txt
第二次打印:
.
..
12.txt
subdir
a.txt
第三次打印:
12.txt
subdir
a.txt
创建目录成功
删除目录成功
当前工作目录:
/home/excious/工程/目录操作
改变后的当前工作目录:
/home/excious/工程/目录操作/testdir
重命名成功
重命名后的打印:
.
..
12.txt
newname
b.txt
四、文件/目录的属性
文件与目录的属性存储与inode节点中,可通过系统调用stat
获取文件或者目录的属性,存入struct stat
结构中
结构体stat
至少包含了以下文件信息:
struct stat
{
dev_t st_dev, // 包含该文件的设备ID号
ino_t st_ino, // 文件的inode号
mode_t st_mode, // 文件类型及权限模式
nlink_t st_nlink, // 文件的链接数
uid_t st_uid, // 文件所有者的用户ID
gid_t st_gid, // 文件的组ID
dev_t st_rdev, // 如果文件为字符或块设备时的设备ID
off_t st_size, // 如果文件为普通文件时的文件字节数
time_t st_atime, // 最近的访问时间
time_t st_mtime, // 最近的数据修改时间
time_t st_ctime, // 最近的文件状态改变时间
blksize_t st_blksize, // 该对象文件系统相关的最佳I/O块大小
blkcnt_t st_blocks // 系统为此文件所分配的数据块数
}
1、常用函数
(1).stat
定义在<sys/stat.h>
中
函数原型如下:
int stat(char *pathname, struct stat *buf);
-
功能
- 获取文件的详细信息 注意点
- 路径中的文件若是符号链接文件,则会获取其所链接到的文件的详细信息 参数
-
参数 说明 pathname
要切换到的工作目录路径 buf
要存入文件详细信息的结构指针
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(2).lstat
定义在<sys/stat.h>
中
函数原型如下:
int lstat(char *pathname, struct stat *buf);
-
功能
- 获取文件的详细信息 注意点
- 路径中的文件若是符号链接文件,则会获取该文件本身的详细信息 参数
-
参数 说明 pathname
要切换到的工作目录路径 buf
要存入文件详细信息的结构指针
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
(3).fstat
定义在<sys/stat.h>
中
函数原型如下:
int fstat(int fd, struct stat *buf);
-
功能
- 获取已打开文件的详细信息 参数
-
参数 说明 fd
已打开文件的文件描述符 buf
要存入文件详细信息的结构指针
返回值
-
成功:返回
0
-
失败:返回
-1
(置errno
)
2、常用宏
(1).文件类型宏
定义在<usr/include/bits/stat.h>
、<sys/stat.h>
中
文件信息中的模式st_mode
是一个
16
16
16位的二进制数,由高位(第
15
15
15位)到低位(第
0
0
0位)分别是
4
4
4为文件类型、
3
3
3位特殊权限、
3
3
3位用户权限、
3
3
3位组权限、
3
3
3位其他用户权限
C语言中已经定义好的文件类型宏如下表所示
最高四位二进制数 | 文件类型常量(八进制) | 文件类型 |
---|---|---|
0100 | S_IFDIR 0040000 | 目录文件 |
0010 | S_IFCHR 0020000 | 字符设备文件 |
0110 | S_IFBLK 0060000 | 块设备文件 |
1000 | S_IFREG 0100000 | 普通文件 |
1010 | S_IFLNK 0120000 | 符号链接文件 |
1100 | S_IFSOCK 0140000 | Socket文件 |
0001 | S_IFIFO 0010000 | 命名管道文件 |
可以把st_mode
和相应的mask
进行按位与,与上述相应的宏进行比较,判断文件类型
-
关于掩码
-
要判断文件类型,因为
16
16
16位文件模式中高
4
4
4位是文件类型,因此应取
mask
为1111000000000000
,即mask = 0170000
,该常量
mask
:
C语言中也同样预定义好了用于判断文件类型的函数宏如下,直接将st_mode
传入即可判断:
#define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
#define S_ISCHR(mode) __S_ISTYPE((mode), __S_IFCHR)
#define S_ISBLK(mode) __S_ISTYPE((mode), __S_IFBLK)
#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
#ifdef __S_IFIFO
# define S_ISFIFO(mode) __S_ISTYPE((mode), __S_IFIFO)
#endif
#ifdef __S_IFLNK
# define S_ISLNK(mode) __S_ISTYPE((mode), __S_IFLNK)
#endif
(2).文件权限宏
定义在<usr/include/bits/stat.h>
、<sys/stat.h>
中
文件信息中的模式st_mode
低
9
9
9位表示文件权限
C语言中已经定义好的文件权限宏如下
#define S_IRUSR 0000400 // 文件所有者读权限
#define S_IWUSR 0000200 // 文件所有者写权限
#define S_IXUSR 0000100 // 文件所有者执行权限
#define S_IRWXU (__S_IREAD | __S_IWRITE | __S_IEXEC) // 文件所有者读写执行权限
#define S_IRGRP (S_IRUSR >> 3) // 组用户读权限
#define S_IWGRP (S_IWUSR >> 3) // 组用户写权限
#define S_IXGRP (S_IXUSR >> 3) // 组用户执行权限
#define S_IRWXG (S_IRWXI >> 3) // 组用户读写执行权限
#define S_IROTH (S_IRGRP >> 3) // 其他用户读权限
#define S_IWOTH (S_IWGRP >> 3) // 其他用户写权限
#define S_IXOTH (S_IXGRP >> 3) // 其他用户执行权限
#define S_IRWXO (S_IRWXO >> 3) // 其他用户读写执行权限
3、使用示例
示例5:尝试使用上述若干系统调用获取文件信息、判断文件类型和文件权限
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
struct stat buf;
int res = stat("./test.txt", &buf);
if (res != -1)
{
printf("文件的\t【设备ID号】\t\t\t为\t【%ld】\n", buf.st_dev);
printf("文件的\t【inode号】\t\t\t为\t【%ld】\n", buf.st_ino);
printf("文件的\t【类型及权限模式】\t\t为\t【%d】\n", buf.st_mode);
printf("文件的\t【链接数】\t\t\t为\t【%ld】\n", buf.st_nlink);
printf("文件的\t【所有者用户ID】\t\t为\t【%d】\n", buf.st_uid);
printf("文件的\t【组ID】\t\t\t为\t【%d】\n", buf.st_gid);
printf("文件的\t【字节数】\t\t\t为\t【%ld】\n", buf.st_size);
printf("文件的\t【最近访问时间】\t\t为\t【%ld】\n", buf.st_atime);
printf("文件的\t【最近数据修改时间】\t\t为\t【%ld】\n", buf.st_mtime);
printf("文件的\t【最近状态改变时间】\t\t为\t【%ld】\n", buf.st_ctime);
printf("文件的\t【系统相关最佳I/O块大小】\t为\t【%ld】\n", buf.st_blksize);
printf("文件的\t【被系统分配到的数据块数】\t为\t【%ld】\n", buf.st_blocks);
}
printf("==============================================================\n");
int fd = open("test.txt", O_RDONLY);
struct stat buf2;
int res2 = fstat(fd, &buf2);
if (res2 != -1)
{
printf("文件的\t【设备ID号】\t\t\t为\t【%ld】\n", buf2.st_dev);
printf("文件的\t【inode号】\t\t\t为\t【%ld】\n", buf2.st_ino);
printf("文件的\t【类型及权限模式】\t\t为\t【%d】\n", buf2.st_mode);
printf("文件的\t【链接数】\t\t\t为\t【%ld】\n", buf2.st_nlink);
printf("文件的\t【所有者用户ID】\t\t为\t【%d】\n", buf2.st_uid);
printf("文件的\t【组ID】\t\t\t为\t【%d】\n", buf2.st_gid);
printf("文件的\t【字节数】\t\t\t为\t【%ld】\n", buf2.st_size);
printf("文件的\t【最近访问时间】\t\t为\t【%ld】\n", buf2.st_atime);
printf("文件的\t【最近数据修改时间】\t\t为\t【%ld】\n", buf2.st_mtime);
printf("文件的\t【最近状态改变时间】\t\t为\t【%ld】\n", buf2.st_ctime);
printf("文件的\t【系统相关最佳I/O块大小】\t为\t【%ld】\n", buf2.st_blksize);
printf("文件的\t【被系统分配到的数据块数】\t为\t【%ld】\n", buf2.st_blocks);
}
close(fd);
printf("==============================================================\n");
struct stat buf3;
int res3 = stat("./test.txt", &buf3);
if (res3 != -1)
{
if ((buf3.st_mode & S_IFMT) == S_IFREG)
printf("文件test.txt是一个普通文件\n");
if (S_ISREG(buf3.st_mode))
printf("文件test.txt是一个普通文件\n");
}
printf("==============================================================\n");
struct stat buf4;
int res4 = stat("./test.txt", &buf4);
if (res4 != -1)
{
int mode = buf4.st_mode;
char str_res[] = "----------";
// 判断文件类型
if (S_ISDIR(mode)) str_res[0] = 'd';
if (S_ISCHR(mode)) str_res[0] = 'c';
if (S_ISBLK(mode)) str_res[0] = 'b';
// 判断用户权限
if (mode & S_IRUSR) str_res[1] = 'r';
if (mode & S_IWUSR) str_res[2] = 'w';
if (mode & S_IXUSR) str_res[3] = 'x';
// 判断组权限
if (mode & S_IRGRP) str_res[4] = 'r';
if (mode & S_IWGRP) str_res[5] = 'w';
if (mode & S_IXGRP) str_res[6] = 'x';
// 判断其他用户权限
if (mode & S_IROTH) str_res[7] = 'r';
if (mode & S_IWOTH) str_res[8] = 'w';
if (mode & S_IXOTH) str_res[9] = 'x';
// 输出文件类型和权限
printf("文件类型和权限为%s\n", str_res);
}
return 0;
}
程序输出的内容如下
文件的 【设备ID号】 为 【33】
文件的 【inode号】 为 【33875】
文件的 【类型及权限模式】 为 【33188】
文件的 【链接数】 为 【1】
文件的 【所有者用户ID】 为 【1000】
文件的 【组ID】 为 【1000】
文件的 【字节数】 为 【7】
文件的 【最近访问时间】 为 【1618807263】
文件的 【最近数据修改时间】 为 【1618807248】
文件的 【最近状态改变时间】 为 【1618807260】
文件的 【系统相关最佳I/O块大小】 为 【4096】
文件的 【被系统分配到的数据块数】 为 【8】
==============================================================
文件的 【设备ID号】 为 【33】
文件的 【inode号】 为 【33875】
文件的 【类型及权限模式】 为 【33188】
文件的 【链接数】 为 【1】
文件的 【所有者用户ID】 为 【1000】
文件的 【组ID】 为 【1000】
文件的 【字节数】 为 【7】
文件的 【最近访问时间】 为 【1618807263】
文件的 【最近数据修改时间】 为 【1618807248】
文件的 【最近状态改变时间】 为 【1618807260】
文件的 【系统相关最佳I/O块大小】 为 【4096】
文件的 【被系统分配到的数据块数】 为 【8】
==============================================================
文件test.txt是一个普通文件
文件test.txt是一个普通文件
==============================================================
文件类型和权限为-rw-r--r--