一、本章引言
上一章通过学习文件io,知道了如何使用文件io来打开文件,对文件进行读写等操作,那么我们这一章
将换一个角度,专门围绕文件属性进行相关的讨论。
1.1 什么是文件属性?
我们通过ls查到就是文件属性,只不过ls只显示了部分文件属性。
1.2 本章讲什么
本章专门介绍操作文件属性的各种OS API,比如获取文件的读写权限、文件的大小、文件的
创建时间等。
1.3 本章的意义
本章操作文件属性的OS API,使用的不像文件io的OS API那样频繁,但是学习本章可
以让你深入的理解有关文件这个东西,所以非常希望大家能够认真对待本章内容。
1.4 本章涉及到的OS API有哪些?
stat、fstat、lstat
umask
chmod、fchmod
chown,fchown,lchown
link,unlink,remove,rename
symlink和readlink
chdir、和getcwd
看起来很多,不过每一行都是一组,只要理解其中一个,其它的都很好理解。
而且这些函数,都与我们常用的cd、ls、chmod、chown、pwd等命令息息相关,大家之前就已经
熟悉了这些命令,所以开解这些函数时,并不会觉得困难。
同样的,对于这些函数,不要去记,关键是理解,如果用到时,你能够查阅man手册后者资料,快
速的把它用起来,就说明你学明白了。
二、回顾文件7种类型
之前将Linux基础的时候说过,在Linux下一切皆是文件,从应用层(应用程序层)看待底层机制时,
皆以文件的方式来看待这些机制。但是不同的底层机制,毕竟是不同的,比如底层驱动程序,就分为字符
设备驱动和块设备驱动,根据底层机制的不同,文件被分为了7种类型。
2.1 文件的7种类型
我们在学习《Linux基础初级》时讲过,Linux一切皆文件,文件一共分为7类分别是- d c s p l b。
(1)普通文件(regular file:-)
1)普通文件根据存放的内容的不同,又分为如下两种
(a)文本文件
存放的都是文字编码,文本编辑器打开后,会将这些文字编码翻译为文字图形,以
供人识别。
(b)纯二进制文件(机器码)
比如经过编译后得到的可执行文件,里面放的是cpu执行的纯二进制机器码,
由于文编编辑器只认识文字编码,所以用文本编辑器打开后,显示的内容无法是错乱的,
无法辨识。
其实不管存放的是文字编码,还是机器码,在计算机中存储时,其实都是以二进制形式
存放的,只不过我们这里可刻意的把机器码这类非文字编码的数据,特意强调为了二进制数据。
2)以存储5为例
(a)以‘5’存储:在文件中存放的是'5'的ASCII码为53(??00110101?),在计算机中
实际存放的是?00110101?。
(b)以整数5存储:文件中实际存放是整数5(00000101),在计算机中实际存放的是000
00101。
对linux内核而言,这两种文件并无区别,至于文件中的数据如何解释,则由处理这些数据
的应用程序(比如文本编辑器)来决定。
不管是文字编码数据,还是纯二进制数据,应用程序调用read、write读写文件时,没有
任何区别。
(2)目录文件(director file:d)
目录是一种特殊的文件,专门用于管理其它文件。
(3)字符设备文件(character special file:c)
字符设备文件,就是字符设备驱动程序,在上层的表现形式。
当应用程序调用底层字符设备驱动程序,实现对某个字符设备进行读写时,上层就需要对接
底层的字符驱动程序,字符设备驱动在上层,会以“字符设备文件”的形式表现出来,我们通过
open、read、write去读写字符设备文件,就实现了和底层字符设备驱动程序的交互。
(4)块设备文件(block special file:b):对应块设备(如磁盘等)。
1)块设备文件,是块设备驱动程序在上层的表现形式。
2)字符设备与块设备有什么区别?
(a)字符设备
以字节为单位来操作数据。
比如:键盘、鼠标、显示器都等是字符设备。
字符设备的驱动程序,就称为“字符设备驱动程序”。
(b)块设备
块设备存储的数据量往往非常大,为了提高读写效率,都是以块(1024字节)为
单位来操作数据。
比如:电脑硬盘、移动硬盘、u盘等,凡是涉及大量数据存储的,都是以块为单位来
操作数据的,都是块设备。
块设备的驱动程序,就称为“块设备驱动程序”。
(5)FIFO(fifo:p)
管道文件,用于实现不同进程(程序)之间的通信,管道是OS提供的一种纯代码层面的通信机制。
数据 数据
A进程 ————————> 管道文件 ————————>B进程
后面讲进程间通信时,会具体讲到管道这个东西。
(6)套接字文件(socket:s)
专门用于网络通信的文件。
讲到网络编程时,再来具体介绍。
(7)符号连接(symbolic link:l):
我们在Linux基础中讲过了,其实就是一种快捷图标,背后指向了另外一个文件。
这7类文件,其中普通文件数量最多,其次是目录文件,然后才是其它类的文件。
2.2 如何判断文件的类型
(1)ls查看- d c l b s p符号来区分
(2)可以使用file命令来查看
1)查看普通文件
(a)如果查看的是文本文件
会提醒你,它是文字编码格式的文件。
(b)如果你查看的是纯二进制文件(机器码)
会提示你,这是一个可以运行的可执行文件。
a.out: ELF 64-bit LSB executable, x86-64, dynamically linked,
/lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not strippe
· ELF:Linux下可执行文件的格式,Windows下的可执行文件是PE格式
对应格式的可执行文件,只能在对应的OS下运行。
· 64-bit:文件里面的机器指令是64位的
· LSB:小端序,C语言里面有详细介绍大小端序
· executable:明确告诉你,该文件是一个可执行文件
· x86-64:运行的是intel的i386的、64位的cpu
· dynamically linked, /lib64/ld-linux-x86-64.so.2
程序使用的库是动态链接库,库名叫/lib64/ld-linux-x86-64.so.2
· for GNU/Linux 2.6.32:运行的系统是Linux系统(ubuntu),Linux 2.6.32
是ubuntu所用Linux内核的版本号
· not strippe:程序没有被瘦身,里面包含有各种用于调试用的信息,当这个程序
最终发布时,会使用strip命令为程序瘦身,去除里面的无用信息,让程序变的更小。
2.3 如何对这些文件进行IO操作
在我们整个课程中,这7类文件都会涉及到,对文件进行IO操作时,使用的都是第1章讲的各种文件io函数
,从这里也可以看出,所以第1章的内容很重要。
三、获取文件属性的函数,stat、lstat、fstat
这三个是兄弟函数,实现的功能相同,只是略微有区别,我们只要先把stat函数搞清楚了,lstat、
fstat非常容易理解。ls命令其实就是调用了这三个函数中的lstat来实现的,我们可以调用lstat函数来
自己实现一个ls命令。
为什么要讲stat、lstat、fstat函数?
讲这三个函数,并不是真的想让你自己去实现一个ls命令,而是通过这三个函数的学习,深刻的理解
文件各种属性,进而理解了、“文件”是个什么东西。
3.1 stat
3.1.1 函数理解
a) 函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
b) 函数功能
功能就是获取文件的属性信息。
每个文件的属性信息,都是存在块设备上、该文件自己的inode节点空间中的。
调用stat函数时,文件系统通过stat给的path,到块设备上索引到该文件的inode节点空
间,
然后将里面的文件属性信息,读到应用程序的缓存中,如此就得到了文件的属性信息。
文件属性数据中转的过程:
应用缓存 <————— stat函数提供的内核缓存 <—————— 驱动程序的缓存 <——————
块设备上的inode结点
c) 返回值
调用成功,返回0,失败返回-1,errno被设置。
d) 参数说明
int stat(const char *path, struct stat *buf);
(1)const char *path:文件路径名
(2)struct stat *buf:应用缓存,用于存放读到的文件属性信息
缓存的类型为struct stat,通过man stat,可以查看到struct stat结构体类型。
struct stat {
dev_t st_dev; /* 块设备号(ID) */
ino_t st_ino; /* inode结点号,文件属性信息所存inode节点的
编号 */
mode_t st_mode; /* 文件类型和文件权限*/ ls
nlink_t st_nlink; /* 链接数 */ ls
uid_t st_uid; /* 文件所属用户ID*/ ls
gid_t st_gid; /* 文件所属组ID */ ls
dev_t st_rdev; /* 字符设备ID */
off_t st_size; /* 文件大小 */
blksize_t st_blksize; /* 系统每次按块Io操作时,块的大小(一般是512或
1024) */
blkcnt_t st_blocks; /* 块的索引号 */
/* windows下,文件的时间,同样也分为这三种 */
time_t st_atime; /* 最后一次访问时间,read*/ ls
time_t st_mtime; /* 最后一次修改时间,write */
time_t st_ctime; /* 最后一次属性修改的时间,如权限被修改,文件所有者
(属主)被修改 */
};
其中标记有ls的,表示这些属性,是我们ls查看时,会显示的内容。
3.1.2 代码演示
实现一个自己的ls命令(my_ls),将制定文件的属性信息打印出来。
为什么打印出来的都是数字?
Linux都是以数字形式来管理属性信息的,我们需要自己把它翻译为更好理解英文字母。
我不知道使用什么格式来打印怎么办?
不要紧,都是数字,不是%d就是%u、或者%ld等,先写一种,当编译器提示警告或者错误时,再根据
提示的信息来改就行。
把程序改的更像ls命令:
(1)名字改为 my_ls
(2)ls可以跟参数,让我的程序也可以跟参数
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void print_error(char *str)
{
perror(str);
exit(-1);
}
int main(int argc, char **argv)
{
int ret = 0;
int i = 0;
struct stat sta = {0};
if(argc != 2)
{
printf("./a.out fileName\n\n");
exit(-1);
}
/* 获取文件属性 */
//ret = fstat(fd, &sta);
ret = lstat(argv[1], &sta);
//ret = stat(argv[1], &sta);
if(-1 == ret) print_error("stat fail");
/* 打印文件类型 */
char file_type = '0';
if(S_ISLNK(sta.st_mode)) file_type = 'l';
else if(S_ISREG(sta.st_mode)) file_type = '-';
else if(S_ISDIR(sta.st_mode)) file_type = 'd';
else if(S_ISCHR(sta.st_mode)) file_type = 'c';
else if(S_ISBLK(sta.st_mode)) file_type = 'b';
else if(S_ISFIFO(sta.st_mode)) file_type = 'p';
else if(S_ISSOCK(sta.st_mode)) file_type = 's';
printf("%c", file_type);
/* 打印文件权限 */
char buf[10] = {0};
char tmp_buf[] = "rwxrwxrwx";
for(i=0; i<9; i++)
{
if(sta.st_mode & (1<<(8-i))) buf[i] = tmp_buf[i];
else buf[i] = '-';
}
printf("%s", buf);
/* 打印文件属性 */
printf(" %lu %u %d %ld %ld %s", sta.st_nlink, \
sta.st_uid, sta.st_gid, sta.st_size, sta.st_atime, argv[1]);
if(file_type == 'l')
{
char sym_buf[30] = {0};
readlink(argv[1], sym_buf, sizeof(sym_buf));
printf(" -> %s\n", sym_buf);
}
else printf("\n");
return 0;
}
3.2 详细说一说struct stat结构体
struct stat {
dev_t st_dev; /* 块设备号 */
ino_t st_ino; /* inode结点号,文件属性信息所存inode节点的编号 */
mode_t st_mode; /* 文件类型和文件权限*/ ls
nlink_t st_nlink; /* 链接数 */ ls
uid_t st_uid; /* 文件所属用户ID*/ ls
gid_t st_gid; /* 文件所属组ID */ ls
dev_t st_rdev; /* 字符设备ID */
off_t st_size; /* 文件大小 */
blksize_t st_blksize; /* 系统每次按块Io操作时,块的大小(一般是512或1024) */
blkcnt_t st_blocks; /* 块的索引号 */
time_t st_atime; /* 最后一次访问时间,read*/ ls
time_t st_mtime; /* 最后一次修改时间,write */
time_t st_ctime; /* 最后一次属性修改的时间,如权限被修改,文件所有者(属主)被修改 */
};
3.2.1 7类文件都有的和部分所特有的属性
ino_t st_ino; /* inode结点号 */
mode_t st_mode; /* 文件类型和文件权限*/
nlink_t st_nlink; /* 链接数 */
uid_t st_uid; /* 文件所属用户ID*/
gid_t st_gid; /* 文件所属组ID */
off_t st_size; /* 文件大小 */
time_t st_atime; /* 最后一次访问时间,read*/
time_t st_mtime; /* 最后一次修改时间,write */
time_t st_ctime; /* 最后一次属性修改的时间,如权限被修改,文件所有者(属主)被
修改 */
以上是7类文件都有的属性。
2)dev_t st_dev; /* 块设备号 */
blksize_t st_blksize; /* 系统每次按块Io操作时,块的大小(一般是512或1024) */
blkcnt_t st_blocks; /* 块的索引号 */
专门给块设备文件用的。
3)dev_t st_rdev; /* 字符设备号(ID) */
而这个是专门给字符设备用的。
3.2.2 详细的说一说ls的信息
st_mode st_link st_uid st_gid st_size st_atime 文件名
-rwxrwxr-x 1 zxf zxf 8944 Apr 2 20:05 a.out
其中不好理解的是st_mode、st_uid和st_gid,
(1)st_uid 和 st_gid
1)st_uid
(a)st_uid是什么
用户id(用户编号),编号是唯一的,例子中的编号1000代表的就是zxf这个用户。
在Linux下,是以编号形式来管理用户的,这有点像人名和身份证的关系。
(c)文件属性中,为什么有st_uid
表示文件的所属用户,好比现实生活中,文件上会标记该文件属于某某公司,
道理其实是一样的。
(b)怎么把用户id变成zxf名字输出
这个问题我们留到第4章《获取系统信息》时再来解决。
2)st_gid
多个用户可以在一起组成一个组,其中的某个用户会担任组长,该用户的用户id,就是整个组
的组id。
这就好比张三、李四、王五组成了一个组,张三当组长,张三的身份证号也被作为了整个组的
组id,张三的名字也被作为了整个组的组名。
对于普通用户而言,自己一个人就是一组,组员和组长都是自己,所以一般情况下,ls显示
文件的所属组时,就是所属用户亲自担任组长的那个组,而且组员就自己一人。
怎么把组id变成zxf名字输出
这个问题我们留到第4章《获取系统信息》时再来解决。
(2)详解st_mode
1)为什么要详解st_mode?
介绍st_mode的目的是,希望通过st_mode的学习,深入理解文件权限这个东西。
有关st_mode,关键是理解,而不是记忆,如果你把文件权限理解清楚了,以后凡是涉及与
文件权限相关的知识点,对你来说都是so seay。
2)ls显示的st_mode(比如-rwxrwxr-x)
st_mode st_link st_uid st_gid st_size st_atime 文件名
-rwxrwxr-x 1 zxf zxf 8944 Apr 2 20:05 a.out
(a)- :文件类型
(b)rwxrwxr-x:文件权限
· 每三个为一组(rwx rwx r-x)
- 每组第一个:如果是-,表示不能读,如果是r,表示可以读
- 每组第二个:如果是-,表示不能写,如果是w,表示可以写
- 每组第三个:如果是-,表示不可以被cpu执行,如果是x,表示可以被执行
如果是编译得到的可执行文件(里面所放的内容是机器指令),这个文件是可以被
cpu执行的
· 看看每一组
- 第一组:代表的是文件所属用户,对该文件的操作权限
- 第二组:代表的是文件所属组里面,其它的组员用户,对该文件的操作权限
- 第三组:除了所属用户、所属组以外的,其它用户对该文件的操作权限。
· 三组权限的大小关系
正常情况下,所属用户的操作权限 >= 组员用户的操作权限 >= 其它不相干用户
的操作权限
这就好比:
你自己的私人物品:
- 所属用户:你,你拥有最高操作权限
- 所属组:你和你的家人就是一个组,你的家人作为组员,拥有仅次于你的
操作权限
- 外人:你家人以外的人,显然拥有最低操作权限,低到甚至是没有操作权限
· 为什么区分三组不同的操作权限
因为早期计算机并不是特别发达,所以当时可能公司里面很多人(比如一个开发组)网线
连接同一台linux系统,每个人创建一个自己的用户,可能一些文件是自己独自用的,所以就
有了所属用户权限;此外这些人肯定有一些文件是需要共享的,所以会有一个所属组权限;
但是又不可能把这些共享文件放开给所有人用,所以就有了其他用户的权限。
3)将数字形式的st_mode,打印为-rwxrwxr-x形式
(a)st_mode的本质
st_mode的本质就是一个数字,我们看到的rwx的形式,只是形态转换的结果。
(b)st_mode的组成
我们以二进制来分析st_mode,以二进制表示时,st_mode有16位,包含三部分信息:
文件权限
(比如:rwx rwx r-x)
__________/\_________
文件类型 设置位 | |
* * * * * * * * * * * * * * * *
1 1 1 1
0 0 0 0
比如:例子中的st_mode为33200,对应的二进制为:?1000 000 110 110 000?
(c)文件类型
· 12~15 bit用于表示文件类型
* * * * * * * * * * * * * * * *
· 如何表示文件类型
八进制 二进制
- 0100000(1000 000000000000):代表普通文件
- 0040000(0100 000000000000):代表目录文件
- 0060000(0110 000000000000):代表块设备文件
- 0020000(0011 000000000000):代表字符设备文件
- 0010000(0001 000000000000):代表管道文件
- 0140000(1100 000000000000):代表套接字文件
- 0120000(1010 000000000000):代表符号链接文件
为了方便使用,在Linux系统提供的stat.h头文件中,给以上数字定义了宏名。
我怎么知道是定义在stat.h中的?
man stat 查到的。
#define S_IFREG 0100000
#define S_IFDIR 0040000
#define S_IFBLK 0060000
#define S_IFCHR 0020000
#define S_IFIFO 0010000
#define S_IFSOCK 0140000
#define S_IFLNK 0120000
我们可以直接使用宏名。
· 如何取出12~15位的值,然后用于判断文件的类型
- 使用c语言中的 & 操作即可实现
我们使用屏蔽字0170000(1111000000000000)&st_mode,将0~13清零
(屏蔽),留下的12~15就是我们要的文件类型。系统给这个屏蔽字定义了一个
宏名,即#define S_IFMT 0170000
- 比如:例子中的st_mode为:33200
33200是十进制,转成八进制为?100660?。
0?100660?(?1000 000 110 110 000?)
&
0170000(1111 000 000 000 000)屏蔽字
0100000(1000 000 000 000 000)文件类型
经过比对,0100000显然是属于普通文件。
· 如何快速判断文件类型
为了方便我们操作,系统在stat.h中定义了相应的带参宏,可以让我们快速的判断文件
的类型。
#define S_ISLNK(st_mode) (((st_mode) & S_IFMT) == S_IFLNK)
#define S_ISREG(st_mode) (((st_mode) & S_IFMT) == S_IFREG)
#define S_ISDIR(st_mode) (((st_mode) & S_IFMT) == S_IFDIR)
#define S_ISCHR(st_mode) (((st_mode) & S_IFMT) == S_IFCHR)
#define S_ISBLK(st_mode) (((st_mode) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(st_mode) (((st_mode) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(st_mode) (((st_mode) & S_IFMT) == S_IFSOCK)
每一个带参宏,用于判断一种文件类型,判断时会把st_mode&S_IFMT,然后与对应的
类型比对,如果比对结果为真,就表示是这种类型的文件,否则就不是。
(d)文件权限
文件权限
(比如:rwx rwx r-x)
__________/\_________
文件类型 设置位 | |
* * * * * * * * * * * * * * * *
1 1 1 1
0 0 0 0
· 0~8bit:文件权限
user group other
* * * * * * * * * * * * * * * *
1 1 1 1 1 1 1 1 1
r w x r w x r w x
0 0 0 0 0 0 0 0 0
- - - - - - - - -
· 从st_mode中提取文件权限
- 提取用户对应的权限
在stat.h中,定义了对应的屏蔽字。
文 件 权 限
**** *** *** *** ***
#define S_IRUSR 00400:对应的是0000 000 100 000 000,提取用户读权限
#define S_IWUSR 00200:对应的是0000 000 010 000 000,提取用户写权限
#define S_IXUSR 00100:对应的是0000 000 001 000 000,提取用户可执行权限
比如提取例子中的st_mode值(33200)中的用户权限,
1000 000 110 110 000
&
0000 000 100 000 000 S_IRUSR
1000 000 110 110 000
&
0000 000 010 000 000 S_IWUSR
1000 000 110 110 000
&
0000 000 001 000 000 S_IXUSR
- 提取组对应的权限
1000 000 110 110 000 st_mode
#define S_IRGRP 00040 (0000 000 000 100 000)
#define S_IWGRP 00020 (0000 000 000 010 000)
#define S_IXGRP 00010 (0000 000 000 001 000)
- 提取其他用户对应的权限
1000 000 110 110 000 st_mode
#define S_IROTH 00004 (0000 000 000 000 100)
#define S_IWOTH 00002 (0000 000 000 000 010)
#define S_IXOTH 00001 (0000 000 000 000 001)
· 代码演示
将数字表示的读写权限,表示为rwx所表示的权限。上面代码有
4)如何使用chmod命令修改文件权限(rwx)
(a)方法1:直接使用数字
· 例1:chmod 0777 file.txt
这个0777,在命令行时,代表八进制的0可以省略。
- 第一个7:user对应的权限,111
user group other
**** *** *** *** ***
111
- 第二个7:group对应的权限,111
user group other
**** *** *** *** ***
111
- 第三个7:other对应的权限,111
user group other
**** *** *** *** ***
111
修改后,文件的权限变为了:rwxrwxrwx。
· 例2:chmod 0664 file.txt
0664的二进制为110 110 100
user group other
**** *** *** *** ***
110 110 100
修改后,文件的权限变为了:rw-rw-r--。
对于存放文字编码的普通文件来说,不需要任何的执行权限。
· open创建文件时所指定的原始权限
fd = open("./new_file.txt", O_RDWR|O_CREAT, 0664);
使用open系统API创建新文件时,需要制定一个原始的权限,比如指定的是
0664的话,就表示,创建出的文件的原始权限为rw-rw-r--。
(a)方法2:直接使用rwx来设置
user group other
**** *** *** *** ***
· 例子1:修改所有权限
- chmod a=rw- file.txt
表示所有的权限全部指定为rw-
· 例子2:只修改某一组的权限
user group other
**** *** *** *** ***
- chmod u=rw- file.txt
只将文件所属用户对应的权限设置为rw-
- chmod g=r-- file.txt
只将文件所属组对应的权限设置为r--
- chmod o=r-- file.txt
只将其它用户对应的权限设置为r--
- chmod u=rwx,g=rw-,o=r-- file.txt
一次性的对三个权限都做设置。
· 例子3:只修改组里面某一位的权限
user group other
**** *** *** *** ***
- chmod u+x file.txt
chmod u-x file.txt
chmod u+x,u+r file.txt
- chmod g+w file.txt
-
- chmod o+r file.txt
-
- chmod u+r,g+w,o+x file.txt
-
5)st_mode中的设置位
文件权限
(比如:rwx rwx r-x)
__________/\_________
文件类型 设置位 | |
* * * * * * * * * * * * * * * *
1 1 1 1
0 0 0 0
设置位用的很少,因此这里就不再介绍具体介绍。
3.3 lstat
功能:
与stat几乎完全一样,都是从块设备上的inode节点空间中读取文件的属性信息。
但是与stat唯一不同的是,lstat会区分链接文件。
(1)stat:当操作对象是链接文件时,stat获取的文件属性是链接文件背后所指向的文件,
而不是链接文件的。
(2)lstat:当操作对象是链接文件时,lstat直接显示的是链接文件本身的文件属性。
3.4 fstat
a) 函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *buf);
b) 功能
和stat一样,也是用于获取文件属性。
不过与stat不同的是,fstat是使用文件描述符来操作的,当你不知道文件路径名,但是你知道
指向这个文件的描述符时,就可以使用fstat来操作。
3.5 ls命令调用的是stat、lstat、fstat中哪一个函数
调用的是lstat,因为当ls的是链接文件时,能够单独的讲链接文件自己的属性显示出来,而不是背后所
指向文件的属性。
3.6 r w x的含义
(1)r:表示文件可以被读
(2)w:表示文件可以被写
(3)x:表示文件可以被执行
1)x对于普通文件来说
如果普通文件存放的只是文字编码,因为文字编码无法被cpu执行,所以普通文件的x没有太大
意义,所以一般的普通文件的x权限一般都是-。
如果普通文件里面存放的是机器指令,机器指令是可以被cpu执行的,存放机器指令的文件就
必须要有x权限,如果没有是无法执行的。比如gcc编译得到的可执行文件,里面放的就是机器指
令,该文件默认就有x,所以才能被执行,否者文件是无法执行的。
2)x对于目录的意义?
我们发现目录都有x,显然目录里面放的并不是机器指令,是不能被执行的,那么x对于目录
的意义何在呢?
对于目录的x来说,也被称为通过权限,也就是说,如果你的目录没有x权限,你是无法通过
这个目录的。
open("/dev/input/sda.txt", ...);
如果/dev/input/sda.txt路径中的某个目录没有x,就无法通过这个目录,最终找不到
sda.txt文件,这个路径就是一个无用路径。
不过正常情况下,除非你自己刻意把目录的x设为-,否则创建的目录默认都有x,不会出现无法通过
的情况。
3)x对于其它文件来说,意义不大
(4)chown命令
1)功能:用于修改文件的属主
2)修改所属用户
chown 新的所属用户 文件
3)修改所属组
chown :新的组 文件
4)同时修改
chown 新的所属用户:新的组 文件
四、umask函数
4.1 open函数创建新文件时的一个问题
open函数创建新的文件时,如果指定的是0777满级权限的话,实际创建文件权限为0775(rwxrwxr-x)
(1)为什么不是满级权限
因为被文件权限掩码做了限制。
111 111 111 (0777)
&
~000 000 010 (0002)文件权限掩码
(111 111 101)
111 111 101 (0775:rwxrwxr-x)
限制其它用户的写权限,为什么限制?
防止其它用户修改你的文件。
如果确实有创建满级权限的需求的话,就必须将文件权限掩码002修改为0
(2)怎么将文件权限掩码改为0
使用umask即可。
4.2 函数原型
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
(1)功能:修改文件权限掩码
(2)参数
mask:新的文件权限掩码
(3)返回值
umask不会调用失败。
返回值是旧的文件权限掩码。
4.3 函数举例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd = 0;
mode_t ret = 0;
ret = umask(0);
printf("oldUmask = %d\n", ret);
fd = open("./new_file.txt", O_RDWR|O_CREAT, 0777);
if(-1 == fd)
{
printf("open fail\n");
return 0;
}
ret = umask(ret);
printf("ret = %d\n", ret);
return 0;
}
4.4 每一个进程都有一个文件权限掩码
我的程序,修改只是当前进程的文件权限掩码,对其它进程的文件权限掩码无影响。
后面讲守护进程的时候就会用到umask函数。
五、文件长度st_size
我们前面学习struct stat结构体时,这个结构体中的st_size被用来存放文件长度,但是这一项只对
普通文件、目录、以及符号连接文件有意义。
因为只有普通文件、目录、以及符号链接文件才有实际的数据,有数据才有文件长度。
其它的文件在块设备上只存储了文件属性,它们只是挂了一个文件名,以文件的形式进行管理而已,没有
实际的数据,所以对于这些文件来说,文件大小是没有意义的。
关于符号链接文件的文件大小
符号链接文件就是一个快捷键,背后指向了某个文件。
符号链接文件的数据,就是所指向文件的文件名,所以它的文件大小指的就是这个名字的字符个数。
pfile -> file.txt
六、文件截断函数truncate、 ftruncate
我们学习open时,可以指定了O_TRUNC后,文件里面有数据的话,会将打开的文件截短(清空)为0,
这一小节我们将要学习一个文件截短的函数truncate,它不仅能够将文件截为0,还可以把文件截短为任意
长度。
6.1 函数理解
a) 函数原型和所需头文件
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
b) 函数功能
将文件长度截短为length所指定长度。
truncate利用文件路径名操作,ftruncate利用文件描述符操作。
c) 函数返回值:成功返回0,失败返回-1,errno被设置
6.2 测试用例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd = 0;
mode_t ret = 0;
//truncate("./new_file.txt", 10);
fd = open("./new_file.txt", O_RDWR|O_CREAT, 0777);
if(-1 == fd)
{
printf("open fail\n");
return 0;
}
//ftruncate(fd, 5);
//如果20 > 文件长度,多余的部分就是空洞
ftruncate(fd, 20);
return 0;
}
七、空洞文件
7.1 什么是空洞文件
在一般文件的情况下,对于普通文件来说,文件数据的理论大小 == 在块设备上实际占用的空间大小。
但是空洞文件却不是这样的,对于空洞文件来说,文件数据的理论大小 > 在块设备上实际占用的空间大小。
7.2 空洞文件的意义
a) 打个比方
我承诺给你一亩地,但是你又不是马上就要用满这一亩地,是一点一点来占用的,如果我现在一下子就把
一亩地全部给你,但是你要花费很久时间才会把地全用上,在你占满之前,一直有相当部分的空间被闲置不用
,显然非常浪费空间资源。
解决办法是,我先承诺说给你一亩地,但是这一亩地先不全部给你,你搬一部分东西过来时,我给你一部
分空间,按照这样的方式,直到把1亩地的空间全部给你,在你没有用满一亩地之前,其它的空间我就可以用
作它用。
b) 迅雷等下载文件
比如下载一个1M大小的文件,文件肯定是要花费相当长的时间才能下载完成,如果我直接就开辟一个实际
占用1M空间的普通文件来放数据的话,在实际下载完数据之前,未装满数据的空间都被闲置,会很浪费空间,
怎么办呢?
解决办法就是开辟一个1M大小的空洞文件,空洞文件的理论大小是1M,但是并没有在块设备上实际给你
分配1M的物理空间,而是在下载过程中,每下载一部分数据,再实际开辟一部分空间给你,直到整个文件下完
位置。
7.3 在Linux下如何制作空洞文件
7.3.1 truncate、ftrucate制作
文件截短长度 > 文件长度时,多余的部分就是空洞。
du命令:查看文件在块设备上,实际占用的物理空间。
ls查看到的只是文件的理论大小,但是空洞部分并不占用实际物理存储空间。
7.3.2 使用lseek制作
将文件读写位置调整到文件尾部之后,然后写点数据,中间空出的部分就是空洞。
八、文件系统是如何管理文件的
8.1 文件系统是什么
文件系统就是一个软件代码,属于OS的一部分。文件系统使用树形结构来管理文件的,凡是涉及管理的,
都是以树形结构来管理的,比如班级学生组织结构,政府、公司的人员组织结构,都是以树形结构来管理的。
8.2 文件在块设备上是如何存储的
(1)超级区
负责“块设备”空间的分配和回收。
(2)inode节点区
1)被划分为了一个个相连的,空间大小相同的inode节点空间。
2)每个节点空间被用于存放某个文件的属性信息,每个节点空间大小是固定的
比如一般的是512字节,512够用吗?
够,因为文件属性并没有多少数据量。
3)每个节点都有一个节点编号,通过节点编号就可以索引找到inode节点空间。
使用stat读取文件属性时,struct stat结构体中的st_dev成员,就是用来存储inode
节点编号的。
(3)数据区
数据区专门用于存放文件的数据。
当然我们前面就说过,不是所有的文件都有数据,只有普通文件、目录、链接文件有数据,其
它的文件只有属性,没有数据。
存储数据时,实际上并不是数据有多少个字节,就分配对应多少的字节空间给你,为了便于物
理空间高效管理,往往都是按块分配空间的,一块往往为4k字节(4*1024)。
文件中的数据小于一块时,还是给你分配一块,当数据超过一块时,会给你再分配一块,当这
一块又满了时,再给你分配一块。
这就好比你去酒店住房时,人家也是按整块空间给你分配的,这个房间满了,人家另外再开一
个房间给你。但是人家开的肯定整块的房间,人家不可能半个半个出租,这样不便于管理,道理
其实都是一样的。
不同的数据块之间不一定是连续的,块之间使用地址进行相互链接,也即是说每一块都有存放
前后块的地址,通过地址就可以找到前后块空间。
1)普通文件
(a)如果是文本文件,数据就是文字编码。
(b)如果是纯二进制文件,数据就是机器指令之类。
2)目录文件
(a)目录文件的数据量小,所以一般就只有一块数据。
(b)目录文件里面放的内容,并不是目录所包含文件的数据,放的只是所包含文件的基本信
息,其中两个信息最重要。
文件名
文件的inode节点号
这个两个有什么有,后面会详细介绍。
3)链接文件
存放的数据很简单,就是所指向文件的文件名。
(3)文件系统是如何通过“文件路径名”索引找到文件的
索引查找的过程,肯定是通过代码来实现的。
1)索引找到普通文件
比如:
(a)fd = open("/new/xxx.txt", O_RDWR);
文件系统如何利用/new/xxx.txt,找到xxx.txt文件的?
图:
找到数据存放空间的起始地址后,read、write调用驱动读写数据时,“块设备驱
动程序”通过这个地址,就能够实现数据的读写。
(b)stat("/new/xxx.txt", ...);
这个函数获取文件属性时,也是按照相同的原理来索引的,找到文件的inode节点
空间后,就可以将inode节点中的文件属性读取出来。
2)索引找到目录文件
(a)cd /new/,如何进入new目录的
先在文件存储区中找到/目录的inode节点,然后根据inode节点中的地址从文件
数据存储区中找到文件名为new的inode节点编号,最终就可以进入到new目录中。
(b)在/目录下,cd . 是怎么回事?
.目录就在/目录下边,然后去/目录的数据空间找有没有文件名为.的数据项,根据
这个数据项找到inode节点编号发现就是/目录本身的节点编号,所以最终在文件属性
存储区里面找到的还是/目录的文件属性。
(c)在new目录下,cd . 和cd .. 各自是怎么回事?
同理。
九、link,unlink,remove,rename函数
这几个函数与ln、rm、mv命令息息相关,因为这几个命令就是调用这几个函数来实现的,学习这几个
函数的其中一个重要目的,就是理解ln、rm、mv命令的实现原理。
9.1 link、unlink
这两个函数涉及到硬链接,在讲《Linux基础初级》时,就介绍过什么是硬链接,这里还是来回顾下什么
是硬链接。
9.1.1 回顾硬链接
(1)ln命令创建硬链接
ln xxx.txt xxx1.txt
(2)创建硬链接,创建的是什么
创建硬链接,就是再为文件创建一个名字。
1)每创建一个硬链接,文件就多一个文件名,硬件链接数+1
多个文件名指向了同一个文件,操作文件时,使用任何一个名字都可以。
2)为什么说多个文件名,指向的是同一个文件
看文件系统的管理图:
从图中看出,创建硬链接后所得到的多个文件名,指向的同一个inode节点,只有inode
节点代表了文件的真实存在,inode节点只有一个,因此多个文件名指向的是同一个文件,不管
使用的是哪一个文件名,都能操作这个文件。
这就好比你有好多外号,但是代表你这个人真实存在的身体只有一个,多个名字代表的都是
同一个人。
3)硬链接数
记录了有多少个文件名指向了inode节点,通过创建硬链接,每增加一个文件名,就多一个硬
链接数。
同理,每删除一个硬链接,也就是删除一个文件名,就少一个硬链接数,当硬链接数被减为0
时,也就代表着文件名被删除完了,这个文件也就被删除了。
不过这里需要注意一点,当硬链接数被减为了0时,如果还有进程在操作这个文件的话,这个
文件还会一直存在,直到进程结束后,这个文件才被删除。
删除文件后,文件数据还在吗?
还在,因为删除文件时,只是将文件的inode节点空间释放了,如果这个文件有数据的话,那
么这个文件的数据仍然还在,在这种情况下,只要将文件的inode节点空间恢复,即可还原该文件。
4)有关目录的硬链接数
(a)为什么新建的目录一开始的文件链接数就是2
因为新创建的目录,一开始就有两个名字指向了目录的inode节点,分别是目录的本名new和.
(b)为什么在该目录下,每多创建一个目录,当前目录就会多一个硬链数
· 原因
新创建目录的..名字,也指向了当前目录new。
(c)能不能使用ln命令,自己给目录创建硬链接
答:不能,Linux不允许用户自己给目录创建硬链接,只能由Linux系统自己给目录创
建硬链接。用户只能给目录以外的,其它类型的文件创建硬链接。
9.1.2 link函数
函数原型
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
1)功能:为非目录文件建立一个新的硬连接。
ln命令就是调用这个系统函数来实现的。
2)返回值:调用成功返回0,失败返回-1,errno被设置
3)参数
· oldpath:原有路径名
· newpath:新的路径名
从参数看出,与ln命令的用法其实是一样的。
ln 原有路径名 新的路径名
9.1.3 unlink函数
函数理解:
函数原型和所需头文件
#include <unistd.h>
int unlink(const char *pathname);
(1)功能:删除一个硬连接,其实就是删除一个名字。
unlink,只能用于删除非目录文件的硬链接,不能删除目录的硬链接,Linux系统
不允许用户修改目录的硬件链接。
(2)返回值:调用成功返回0,失败返回-1,errno被设置
(3)参数
pathname:要删除路径名。
当硬链接数减为0时,文件即被删除了。
使用unlink创建临时文件:
所谓临时文件就是,只在程序运行过程中有效,程序运行结束后就自动删除,这就是临时文件,使用unlink
就可以实现一个临时文件。
如何使用unlink创建临时文件?
open创建一个文件后(新文件的硬链接数都是1),然后立即调用unlink将文件硬链接数减为0,将其
删除。
虽然文件的硬链接数变成了0,但是在进程没有结束之前,这个文件仍然可以被使用,直到进程结束后,
文件才被删除,这样一来就实现了临时文件。
代码演示:
在这里插入代码片#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
//link("./file.txt", "../file1.txt");
//unlink("./file1.txt");
/* 使用unlink实现临时文件 */
int fd = open("./file.txt", O_RDWR|O_CREAT, 0664);
if(-1 == fd)
{
perror("open fail");
exit(-1);
}
unlink("./file.txt");
write(fd, "hello world\n", 12);
lseek(fd, 0, SEEK_SET);
char buf[30] = {0};
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
9.2 remove函数
9.2.1 函数原型
#include <stdio.h>
int remove(const char *pathname);
(1)功能:可以用于删除任何文件(既可以删除目录文件,也可以删除非目录文件)。
删除非目录文件时,功能与unlink一样。
(2)返回值:调用成功返回0,失败返回-1,errno被设置
(3)代码演示
把unlink换成remove。
9.2.2 为什么这个函数既能用于删除一般文件,也能用于删除目录
(1)remove是一个库函数
它封装了unlink和rmdir这两个系统函数。
remove
/ \
/ \
/ \
unlink rmdir(后面讲)
| |
| |
非目录 目录
remove会自动检测文件类型,如果是目录就调用rmdir删除,如果其它文件,就调用unlink删除。
(2)为什么会封装remove库函数?
主要想把unlink和rmdir统一起来,方便使用。
(3)rm命令
这个命令既能用于删除目录,也能用于删除其它所有的文件,可以认为就是调用remove实现。
rm
|
|
remove
/ \
/ \
/ \
unlink rmdir(后面讲)
| |
| |
非目录 目录
9.3 rename函数
修改文件的路径名,mv命令就是调用这个函数实现的。
mv命令回顾
(1)改名
(2)移动
(3)移动+改名
mv就是调用rename函数实现的,看起来功能有三个,其实功能就一个,修改文件的路径名。
函数原型和所需头文件
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
(1)功能:修改文件路径名,将旧的路径名oldpath,改为新的路径名newpath。
(2)返回值:调用成功返回0,失败返回-1,errno被设置
代码演示
1)修改路径名情况1:当只改路径,不改文件名字
这种情况其实就是移动。
(a)比如:
· 普通文件:./new_file.txt 改为 ../new_file.txt
· 目录:./kk 改为 ../kk
· 其它文件:一样的
(b)代码演示
· 如果文件移动起始位置和目标位置,在同一个分区里面的话
移动文件时,不会移动文件的数据,只是把文件的基本信息(名字、inode编号),
从这个目录记录到另一个目录下。
· 如果移动的起始位置和目标位置,不在同一个分区。
既要移动数据,也要移动文件基本信息,比如:
- windows:从u盘拷贝文件
- Linux:从U盘拷贝文件
在Linux下,分区是以目录的形式存在的,而在windows分区是以c:等盘符
形式存在的。
不管是在windows下还是在linux,从u盘拷贝文件,都是从u盘这个移动分
区,将文件拷贝到电脑硬盘的分区,拷贝时,既要拷贝数据,也要拷贝文件
的基本信息。
2)修改路径名情况2:不改路径,只改文件名
这种情况就是一般意义上的改名。
(a)普通文件:./new_file.txt 改为 ./new_file1.txt
(b)目录:./kk 改为 ./yy
(c)其它文件:一样的
3)修改路径名情况3:既改路径,也改文件名
(a)普通文件:./new_file.txt 改为 ../new_file1.txt
(b)目录:./kk 改为 ../yy
(c)其它文件:一样的
十、symlink、readlink
这两个函数与符号链接文件有关,讲《Linux基础初级》时,我们有详细的介绍符号链接文件,不过这里还是
要先回顾一下符号链接这个东西。
10.1 链接文件
符号链接文件也被称为软链接文件。
使用ln -s就可以创建符号链接文件
演示:ln -s xxx.txt pxxx
什么是符号链接文件
符号链接文件就是一个快捷图标,它指向了另一个文件。
11.1.3 符号链接 与 硬链接的对比
(1)创建硬连接
同一个文件有多个不同的名字,它们指向是同一个inode节点。
(2)创建符号链接文件
符号链接文件与它所指向的文件,是两个完全不同的独立的文件,拥有自己独立的inode节点。
符号链接文件的数据就是指向文件的文件名,文件大小就是名字的字符个数。
(3)不能给目录创建硬链接,但是可以给目录创建符号链接文
只要你有需要,可以给任何文件创建符号链接文件。
(4)可以给符号链接文件,创建硬链接吗
可以
10.2 symlink
函数原型
#include <unistd.h>
int symlink(const char *oldpath, const char *newpath);
(1)功能:为oldpath,创建符号连接文件newpath。
使用ln创建硬链接时,调用的是link函数。
使用ln -s创建符号链接时,调用的是symlink,sym就是符号的意思。
(2)函数返回值:调用成功返回0,失败返回-1,errno被设置
10.3 readlink
函数原型
#include <unistd.h>
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
(1)功能:读符号链接文件的数据(指向文件的名字)。
1)const char *path:符号连接文件的路径名。
2)char *buf:存放名字缓存的地址。
3)size_t bufsiz:缓存的大小
readlink命令,就是调用这个函数实现的。
(2)返回值:调用成功,返回读到的字节数,失败返回-1,errno被设置
10.4 符号跟随函数 与 符号不跟随函数
(1) 符号跟随函数
调用某个函数操作文件,当指定的路径名是符号链接文件时,如果函数最后操作的是符号链接
文件所指向的文件,而不是“符号链接文件”本身,这个函数就是符号跟随函数,因为它跟到符号
链接文件所指向的背后去了。
比如stat、open就是符号跟随函数,因为这些操作的都是符号链接文件所指向的文件。
(2)符号不跟随函数
当路径名是符号链接文件时,函数操作的就是符号链接文件本身,不会跟随。
比如lstat就是符号不跟随函数,因为获取文件属性时,如果操作的是符号链接文件的
话,那么获取的是符号链接文本身的属性。
(3)需要记住哪些是符号链接文件,哪些不是吗?
1)不要记,有规律。
凡是需要指定“文件路径名”函数,只要函数名字是l打头的,比如像lstat,基本
都是符号不跟随函数。
如果没有l,比如stat、open、truncate这种的,就是符号跟随函数。
疑问:lseek是符号跟随函数吗?
lseek不需要指定路径名,而是通过文件描述符(fd)操作的,不存在符号跟
随与不跟随的问题。
2)你要是实在拿不准
你就自己去操作一个符号链接文件测试下,看看这个函数是跟随的还是不跟随的就
知道了,然后你就知道了。
有关符号跟随问题,理解即可,在后续学习中,涉及到的不多。
十一、getcwd、chdir、mkdir、rmdir
11.1 getcwd
函数原型
#include <unistd.h>
char *getcwd(char *buf, size_t size);
这是一个库函数,执行pwd命令就是调用这个函数实现的。
(1)功能:获取进程的当前工作目录
什么是进程的工作路径呢?
后面再说。
(2)参数
1)buf:存放获取到的当前路径的缓存
2)size:缓存的大小
(3)返回值:成功返回缓存buf的地址,失败返回空,errno被设置。
pwd这个命令获取的是,当前终端这个进程的工作路径。
我自己的进程调用getcwd函数,获取的是我自己进程的当前工作路径,默认是你运行这个程序时所在的路径。
演示:
当前工作路径有什么用?
讲到后面你就知道了。
11.2 chdir
函数原型
#include <unistd.h>
int chdir(const char *path);
(1)功能:切换进程当前工作目录到path。
(2)返回值:成功返回0,失败返回-1,errno被设置。
我自己的进程调用chdir函数时,切换的是我自己进程的工作路径,切换后,你调用getcwd
获取当前路径后,你会发现当前路径变成了切换后的路径。
cd命令也是调用chdir实现的,使用cd这个命令时,cd会调用getcwd函数来切换当前终端
这个进程的工作路径。
11.3 mkdir函数
mkdir命令调用的就是这个函数。
函数原型
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
(1)功能:创建新目录。
1)pathname:需创建目录的路径名
2)mode:指定目录的原始权限,一般给0775
给目录指定原始权限时,一定要有x权限,否者无法进入这个目录,有关这个问题,
我们在前面的课程中已经讲得非常清楚了。
(2)返回值:调用成功返回0,失败返回-1,errno被设置
11.4 rmdir函数
rmdir和rm命令删除目录时,调用的都是rmdir这个函数。
rmdir命令:只能删除空目录,rmdir命令用的很少
rm:不管目录空不空,都能删除,rm用的最多
rm
|
remove
| |
| |
/ \
/ \
unlink rmdir
函数原型
#include <unistd.h>
int rmdir(const char *pathname);
(1)函数功能:删除路径名为pathname的这个目录。
不管是我们讲那个函数,在指定路径名时,可以是相对路径,也可以是绝对路径。
删除时,Linux系统会调用相关函数,将目录硬链数全部减位0,然后目录就被删除了。
(2)函数返回值:调用成功返回0,失败返回-1,errno被设置
· 如果目录不为空,必须递归调用rmdir函数,实现递归删除。
· 什么是递归删除?
当目录不为空时,先调用chdir函数进入目中,然后调用rmdir、unlink把里面的内容删除完毕后,再回到上一级,将空目录删除。
为了方便操作,可以调用remove函数来间接调用unlink和rmdir函数。
如果目录很深,需要重复相同的过程。
图:
我这里就不写递归删除非空目录的代码了,请大家自己下去后,根据我的提示来实现。
rm命令删除非空目录时,最后还是调用rmdir函数来递归删除实现的。
十二、opendir、readdir
opendir:打开目录,以便调用readdir读取目录项
readdir:读取目录里面的目录项
什么是目录项?
目录里面的数据,其实就是一条一条的目录项,每个目录项就是一个文件的基本信息,包含两个最重要
的基本信息,即:
文件名
inode节点号
12.1 opendir
函数原型
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
(1)功能:打开目录
不能使用open函数打开目录,只能使用opendir打开。
(2)参数:name:需打开目录的路径名
(3)返回值
· 调用成功:返回一个DIR *的指针,这个指针指向了被打开的目录,readdir通过这个指针就
可以读取目录的目录项。
· 调用失败:返回NULL,errno被设置。
12.2 readdir
函数原型
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
(1)功能:读取目录里的目录项。
每调用一次,就读取出一条目录项。
(2)参数dirp:opendir打开目录时,得到的指针。
(3)返回值:调用成功,返回指针指向struct dirent结构体的指针。
man readdir能查看到这个结构体,这个结构体其实是定义在了<dirent.h>头文件中。
这个结构体就是用来存放一条目录项的,调用readdir读取到目录项后,会自动开辟一个
struct dirent变量来存放目录项,然后将变量的指针返回,应用程序通过这个指针,就可以
访问结构体中的目录项信息(文件基本信息)。
struct dirent
{
ino_t d_ino; /* i节点编号 */ 重点
off_t d_off; /* 地址偏移 */
unsigned short d_reclen; /* 本条目录项的大小 */
unsigned char d_type; /* 文件类型,不是所有系统都支持 */
char d_name[256]; /* 文件名字 */ 重点
};
返回NULL的话有如下两种情况:
1)读到目录的末尾时,返回NULL。
2)函数调用失败时,也返回NULL,不过errno被设置。
怎么判断函数是否调用失败了呢,如果ernno==0,表示没有设置错误号,返回NULL是因为
读到了文件的末尾。如果errno!=0,表示是因为函数调用出错而返回的NULL。
代码演示
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <dirent.h>
#include <errno.h>
int main(void)
{
DIR *dirp = NULL;
dirp = opendir(".");
if(NULL == dirp)
{
perror("opendir fail");
exit(-1);
}
while(1)
{
struct dirent * direntp = NULL;
direntp = readdir(dirp);
if(direntp == NULL && errno!=0)
{
perror("readdir fail");
exit(-1);
}
if(direntp == NULL && errno == 0) break;
//printf("inodeID=%lu, fname=%s\n", direntp->d_ino, direntp->d_name);
printf("%s\n", direntp->d_name);
}
return 0;
}
十三、chmode、fchmod
函数原型
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
(1)功能:修改文件权限,比如将权限指定为0664等。
chmod命令就是调用这两个函数来实现的。
chmod:使用路径名操作
fchmod:使用文件描述符操作
(2)返回值:成功返回0,失败返回-1,errno被设置、