[ APUE ] 第四章 文件和目录
1. stat,fstat,fstatat,lstat函数
#include
#include
#include
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
#include /* Definition of AT_* constants */
#include
int fstatat(int dirfd, const char *pathname, struct stat *statbuf,int flags);
获得 文件有关的信息结构.
fstat获得已在fd上打开的文件的信息.
lstat类似于stat,但是遇到符号链接的时候返回符号链接的有关信息,而不是像stat一样返回符号链接引用的文件的信息.
statbuf指向一个结构体变量,里面有文件的各种属性.
timespec类型按照秒和纳秒定义时间.
time_t tv_sec;
long tv_nsec;
为了兼容以前版本(只有秒),所以有了上面的#define
2. 文件类型
- 普通文件
- 目录文件
- 块特殊文件:提供对设备的带缓冲访问
- 字符特殊文件:提供对设备不带缓冲的访问,每次访问长度可变。
- FIFO。进程间通信
- socket。进程见网络通信
- 符号链接。
可以使用宏判断文件类型 (取出文件stat,再用宏处理st_mode成员)
判断文件类型的小程序:
#include"apue.h"
int main(int argc,char *argv[]){
struct stat buf;
const char *ptr;
for(int i=1;i
运行结果:
3.用户ID和组ID
与一个进程相关联的ID如下所示:
ID | 解释 |
---|---|
实际用户ID、实际组ID | 我们实际是谁 |
有效用户ID、有效组ID、附属组ID | 用于文件访问权限检查(当前进程具有谁的权限) |
保存的设置用户ID、保存的设置组ID | 由exec函数保存 |
- 实际用户ID和实际组ID。这两个字段在登录时取自口令文件,通常在一个session中不变,但超级用户进程可以改变它。
- 有效用户ID、有效组ID、附属组ID。决定我们的文件访问权限。
- 保存的设置用户ID、保存的设置组ID。在执行一个程序的时候包含了有效用户ID和有效组ID的副本。
通常情况下,有效组ID等于实际组ID,有效用户ID等于实际用户ID。
每个文件有一个所有者和组所有者,分别由stat中的st_uid, st_gid指定。
有效用户ID一般是实际用户ID,有效组ID亦然。但是我们可以在st_mode中设置标志,使“当执行该文件时,将进程的有效用户ID设置为文件所有者的用户ID”,有效组ID也有相应标志。这两个标志被称为设置用户ID(set-user-ID)位和设置组ID(set-group-id)位。
这种操作有什么用吗?
例如,当某文件的所有者为超级用户,而且设置了set-user-ID位,那么当该文件执行时,该进程具有超级用户权限,无论当前用户的ID是什么。例如passwd命令的程序,它允许任何一个用户修改自己的密码,将修改后得密码写入口令文件中/etc/passwd或/etc/shadow。事实上只有超级用户对口令文件具有写权限,所以这个文件需要被设置set-user-ID位,当程序被执行的时候产生的进程会拥有额外权限,可以修改口令文件。
(chmod可以打开设置用户ID位。 u+s
4.文件访问权限
st_mode也包含了对文件的访问权限位。所有类型的文件都有访问权限。
USR:文件所有者
GRP:同组用户
OTH:其他用户
chmod 命令可以进行修改,分别用u/g/o表示三种用户。
文件访问权限有以下几种常用的规则:
-
当我们打开文件时,我们对路径名中包含的所有文件夹都应该拥有执行权限,包括“.”这样的隐藏的当前工作目录。这也是为什么目录文件的执行位也被称为搜索位。举个例子,比如当你打开a.txt文件时,和打开./a.txt的效果是一样的。
* 注意目录的执行权限和读权限有所不同
执行权限可根据文件名检索目录(但不获取列表)并执行文件/打开子目录,读权限只是读取目录中所有文件名的列表,但不能检索。比较容易理解的一个例子是:当只有读权限时,ls可以执行,cd不行;当只有执行(搜索)权限时,cd命令可以执行,ls不行。
-
对文件的读权限—— O_RDONLY , O_RDWR
-
对文件的写权限——O_WRONLY, O_RDWR
-
O_TRUNC选项需要写权限
-
在目录中新建文件需要对目录的写权限和执行权限。
-
删除文件必须对包含文件的目录拥有写权限和执行权限,文件本身不需要读写权限。
-
exec要执行的文件必须有执行权限,而且这个文件还得是个普通文件
进程每次打开/创建/删除一个文件时,内核就会进行文件访问权限测试,主要涉及到文件所有者(属于”死“的文件,st_uid, st_gid), 和进程的有效id(有效用户id和有效组id)和附属组id("活"的进程)。
具体测试过程如下:
- 若有效用户ID为0,则代表超级用户,肯定允许访问。
- 若进程的有效用户ID等于文件所有者ID,那么就检查文件访问权限位的用户 (u)那几个位。
- 若进程的有效组ID或附属组ID之一等于文件的组ID,那么就检查文件访问权限位的组(g)那几个位。
- 都不是那就检查其他用户(o)的各位。
注意测试过程有短路效应,若任一步符合,接下来的测试过程都不会进行。
5.新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID,组ID设置有两种选择
- 新文件的组ID可以是进程的有效组ID
- 新文件的组ID可以是所属目录的组ID
可以使用mount命令设置。
6. access和faccessat函数
有的时候我们的进程已经以设置用户ID的权限运行,但同时我们又想对用户ID和实际组ID进行访问权限测试。测试过程和上面写的“进程每次打开/创建/删除一个文件时”的测试过程一致,只是将有效换为实际。
#include
int access(const char *pathname, int mode);
#include /* Definition of AT_* constants */
#include
int faccessat(int dirfd, const char *pathname, int mode, int flags);
成功返回0,出错返回-1.
以下是mode的各种常量
mode | 说明 |
---|---|
F_OK | 测试文件是否存在 |
R_OK | 测试读权限 |
W_OK | 测试写权限 |
X_OK | 测试执行权限 |
access与faccessat在两种情况下相同。
- pathname是绝对路径
- fd参数取值AT_FDCWD(表示相对于当前路径),pathname为相对路径。
否则根据打开目录fd和相对路径pathname计算路径。
flag参数可改变faccessat的行为,如果flag被设置为AT_EACCESS, 访问检查用的是有效ID而非实际ID。(这有锤子用。。。?)
7. umask函数
为当前进程设置文件的mode创建屏蔽字,并返回之前的屏蔽字。
这里”屏蔽字“的意思是,出现在屏蔽字中的位,该进程无法赋予文件相应位权限。
#include
#include
mode_t umask(mode_t mask);
子进程屏蔽字不会影响父进程的屏蔽字,shell自己还有个umask命令查看shell进程屏蔽字。
8.chmod, fchmod和 fchmodat
#include
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
#include /* Definition of AT_* constants */
#include
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
fchmod是对打开文件进行操作。
fchmodat和fchmod在以下两种情况下相同:
- pathname参数为绝对路径
- dirfd参数取值为AT_FDCWD(表示当前路径),pathname为相对路径。
否则fchmodat以dirfd为当前目录计算路径。
flag可以改变fchmodat的行为。当设置了AT_SYMLINK_NOFOLLOW标志时, fchmodat并不会跟随符号链接。
改变文件的权限位有效用户ID必须为文件所有者ID,或者是超级用户。
mode取值如下,通过或运算达到并的效果。
chmod在下列情况下会自动清除两个权限位。
- 在Solaris和FreeBSD中会阻止非超级用户对普通文件设置粘着位。Linux和Mac不会,因为linux下对普通文件设置粘着位无意义。(FreeBSD也是无意义但还是阻止了
- 新创建文件的组ID可能是父目录所属组ID,而非进程有效组ID或附属组ID。如果是这样的话(且没有超级权限),设置组ID位会被自动关闭。防止该文件被非该用户所属组“拥有”。
9.粘着位
早年Unix还没有使用分页技术,所以使用了粘着位来实现对要放入交换区程序的标记。被标记程序的text部分机器指令的副本被放入交换区,以供下次使用的时候快速载入内存,然而现在一般并无卵用。
有的系统改变了他的作用:如linux!
一般来说,只有目录内文件的所有者或超级用户才可以删除或移动目录内文件。如果不为目录设置粘着位,则任何对该目录具有写和执行权限的用户都可以对其中文件进行删除和移动操作。在Linux系统中,我们一般为/tmp目录设置粘着位,从而防止普通用户删除或移动其他用户的文件。
对目录设置了粘着位后,只有对该目录具有写和执行权限且
拥有此目录 或 拥有此文件 或 是超级用户
才能删除或者重命名这个文件。
10.chown, fchown, fchownat和lchown
#include
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
#include /* Definition of AT_* constants */
#include
int fchownat(int dirfd, const char *pathname,
uid_t owner, gid_t group, int flags);
chown函数用于修改文件的用户ID和组ID。如果参数中的owner或group为-1,则表示不修改。
lchown和fchownat(mode设置AT_SYMLINK_NOFOLLOW标志)可以修改符号链接的所有者而非符号链接指向文件的所有者。
fchown修改已打开文件的所有者。
(似乎f开头的那些与文件相关的函数都和已打开的文件有关?特性也一致
*** fd为打开目录描述符,pathname为相对路径。。。**
*** fd为AT_FDCWD, pathname为相对路径。。或pathname绝对路径。。等于不带f)**
11.文件长度
stat成员st_size表示以字节为单位的文件的长度。该字段只对普通文件、目录文件和符号链接有意义。(某些系统下 管道也可以,代表可读取字节数
文件长度通常是一个数的整倍数(如16或512.
符号链接的文件长度代表实际指向文件长度。
12.文件截断
截断文件可以在打开的时候使用O_TRUNC选项。也可使用以下函数
#include
#include
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
将一个文件长度截断为length。文件长度小于length则文件长度拓展,可能会创建一个空洞,中间数据读作0。
13.文件系统
我们可以把一个磁盘分成一个或多个分区,每个分区可以包含一个文件系统。
i节点是固定长度的记录项,它包含有关文件的大部分信息。
-
在图中有两个目录项指向同一个i节点,每个i节点都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少到0时,才可以删除该文件。(即 释放该文件占用的数据块)。这就是删除一个目录项的函数为unlink而不叫delete的原因。
在stat结构中,链接计数包含在st_nlink成员当中,其基本数据类型为 nlink_t ,这种链接类型也被称为硬链接。LINK_MAX指定了文件链接数的最大值。
-
符号链接(symbolic link)。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。例如:
目录项中的文件名是3个字符的字符串lib,而lib文件中包含了7个字节的数据“usr/lib”.
-
i节点包含了与文件有关的所有信息:文件类型,文件访问权限位,文件长度和指向文件数据块的指针等等。stat结构中大多数信息都取自i节点!只有两项重要数据存放在目录项中:文件名和i节点编号(数据类型ino_t)。
-
因为目录项的i节点编号指向同一文件系统的相应i节点,而不能指向另一个文件系统的i节点。这就是为什么ln命令(构造一个指向一个现有文件的新目录项)不能跨文件系统的原因。
-
在不改变文件系统的时候修改文件名,文件的实际内容不移动,只需要构造一个指向现有i节点的新目录项,删除老目录项,链接计数不变。
目录文件的链接计数字段是怎样的?
一个叶目录(不包含目录的目录)的链接计数总是2,分别来自目录中的 . 项和该目录名对应的目录项(如2549)。父目录增加一个子目录,父目录的链接计数都会增加1(如1267)。
14.link,linkat,unlink,unlinkat和remove函数
#include
int link(const char *oldpath, const char *newpath);
#include /* Definition of AT_* constants */
#include
int linkat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, int flags);
成功返回0,失败返回-1.
创建一个指向现有文件的链接。
都是创建一个新目录项newpath,引用现有文件项oldpath。若newpath已存在则返回出错。只创建newpath的最后一个分量(最后一个斜杠后面的),其他都应该已存在。
linkat就不多说了1.普通情况 fd计算 相对路径 2.AT_FDCWD,当前路径计算相对路径 3.绝对路径忽略fd。
当现有文件为符号链接,通过flags参数来选择是否跟随符号(AT_SYMLINK_FOLLOW),即创建符号链接指向文件的链接,还是创建符号链接的链接。
创建目录项和链接计数应该是原子的!
许多文件系统一般不允许创建指向目录的硬链接,因为可能会导致循环(即使可以也仅限超级用户可以做?),而很多处理文件系统的程序都无法应对这种情况,所以一般不允许这种操作。
#include
int unlink(const char *pathname);
#include /* Definition of AT_* constants */
#include
int unlinkat(int dirfd, const char *pathname, int flags);
unlink删除现有目录项(文件,删不了目录). 成功返回0,出错返回-1并不对文件进行任何修改. (unlink删目录会报 "Is a directory")
pathname链接计数-1.
为了解除链接,必须对包含该目录项的目录具有写和执行权限。对于设置了粘着位的目录,见9处。
只有链接计数为0才能删除文件内容。同时若一个进程打开了该文件,也会阻止删除文件。删除一个文件时,内核首先检查打开该文件的进程个数,如果计数为0,则内核再去检查链接计数;如果链接计数也是0,则删除文件内容。
unlinkat可以通过flags改变行为。AT_REMOVEDIR可以和rmdir一样删除目录。尝试一下
(结果:rmdir只可以删除空文件夹,unlinkat加上AT_REMOVEDIR也一样)
(* 突然发现自己删自己是不行的,会检测参数,报"Invalid argument".
注意unlink解除链接时,如果文件已被打开,则不会立即解除,会等到打开该文件的进程主动关闭该文件 或者进程关闭(此时系统内核会关闭该进程打开的全部文件)。
所以他可以用来及时删除进程产生的临时文件。
#include
int remove(const char *pathname);
remove是c标准的,它很牛逼。
对于文件,他和unlink相同;对于目录,他和rmdir相同.
15.rename和renameat函数
#include
int rename(const char *oldpath, const char *newpath);
#include /* Definition of AT_* constants */
#include
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
成功返回0,失败返回-1.
根据oldname是文件、目录还是符号链接,有以下情况需要注意:
-
如果oldname是文件而非目录,则为该文件或符号链接重命名。在这种情况下,如果newname已经存在,则看他是不是目录,如果是目录就不行;如果不是目录,则删除原newname对应目录项,然后将oldname重命名为newname。对包含oldname和newname的目录,调用进程必须具有写权限,因为要更改这两个目录,
具体原因:
如果newname已存在,则调用进程需对其有写权限(要删除),同时调用进程要删除oldname目录项,可能会创建newname目录项,所以他需要对包含oldname以及包含newname的目录具有写权限(改写目录项)和执行权限(检索目录项)。
-
如果oldname是目录,则为该目录重命名。如果newname已存在,则newname对应目录应当是空目录(空目录指目录中只有 . 项和 .. 项)。如果newname存在且为空目录,则先删除,然后将oldname对应文件重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀,因为这将导致无法删除oldname。
-
若oldname或newname为符号链接,则处理符号链接指向的文件。
-
newname和oldname中, . 和 .. 都不能出现在最后部分。
-
如果oldname和newname指向同一个文件,则啥也不做直接成功返回。
16.符号链接
符号链接是对一个文件的间接指针,它与硬链接有所不同,硬链接直接指向文件的i节点。符号链接出现的目的是为了避开硬链接的一些限制:
-
硬链接通常要求链接和文件在同一个文件系统。
-
只有超级用户才能创建指向目录的硬链接(在文件系统支持的情况下)。
对符号链接指向何种对象并无文件系统限制,任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置。
当使用有路径名作为参数的函数时,应当了解函数是否会跟随符号链接。
上图有一个对open函数有一个例外:当同时使用O_CREAT和O_EXCL两者调用open函数,在此情况下,若路径名引用的是符号链接,open将出错返回,errno设置为EEXIST。这是为了堵塞安全性漏洞,防止具有特权的进程被诱骗写错误的文件(具体是怎样的呢?不清楚,也没查到靠谱的解释)。
O_CREAT和O_EXCL共用是为了将测试和创建合并为一个原子操作。(测试:文件存在则报错返回,创建:文件不存在则创建),但是单个的O_CREAT如果文件存在既不操作也不报错。
- 发现O_CREAT和O_RDONLY是不兼容的,因为O_CREAT同时需要写权限。
17.创建、读取符号链接
#include
int symlink(const char *target, const char *linkpath);
#include /* Definition of AT_* constants */
#include
int symlinkat(const char *target, int newdirfd, const char *linkpath);
创建符号链接,成功返回0,失败返回-1.
在linkpath创建指向target的符号链接。
#include
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
#include /* Definition of AT_* constants */
#include
ssize_t readlinkat(int dirfd, const char *pathname,
char *buf, size_t bufsiz);
读取符号链接文件的实际内容,成功返回读取字节数,失败返回-1.
18.文件的时间
修改时间(st_mtim)和状态更改时间(st_ctim):修改时间是文件内容最后一次被修改的时间。状态更改时间是该文件的i节点最后一次被修改的世界。i节点的修改操作有很多,比如更改文件访问权限、更改用户ID、更改链接数等等。。。这些修改并不影响文件的实际内容。
注意系统不维护i节点的最后一次访问时间,所以access和stat函数并不更改这三个时间的任意一个。
ls 命令 按时间排序显示。ls -l按文件修改时间先后排序、-u按访问时间顺序排序显示、-c按状态更改时间排序显示。
对于一个目录,增加、删除、修改目录项会影响到目录的三个时间,但读写目录中的文件只会影响该文件的i节点,而对目录则无影响。
19.futimens、utimensat和utimes
#include /* Definition of AT_* constants */
#include
int utimensat(int dirfd, const char *pathname,
const struct timespec times[2], int flags);
int futimens(int fd, const struct timespec times[2]);
struct timespec {
time_t tv_sec;
long tv_nsec;
};
两个函数返回值:若成功,返回0;若出错,返回-1.
修改”文件的访问和修改时间“,futimens和utimensat可以指定纳秒级别的精度的时间戳。
times[2]: 第一个元素是访问时间,第二个元素是修改时间,这两个时间是自1970.01.01 00:00:00 以来的秒数。
times参数的设置:
- 如果times参数是一个空指针,则访问时间和修改时间都设置为当前时间。
- 如果times是指向两个timespec结构的数组, 任意数组元素的tv_nsec字段的值为UTIME_NOW,则时间戳设置为当前时间。忽略tv_sec字段。
- 如果times是指向两个timespec结构的数组,任意数组元素的tv_nsec字段的值为UTIME_OMIT,则相应时间戳不变,忽略tv_sec字段。
- 如果times是指向两个timespec结构的数组,且数组元素的tv_nsec字段不为UTIME_NOW或UTIME_OMIT,则时间戳设置为tv_sec和tv_nsec
不同times参数需要的条件不同:
- 如果times是一个空指针,或者任意数组元素的tv_nsec字段的值为UTIME_NOW,则进程的有效用户ID必须等于该文件的所有者ID;进程对该文件必须具有写权限,或者进程是一个超级用户进程。
- 如果times数组元素的tv_nsec字段不为UTIME_NOW或UTIME_OMIT,则进程的有效用户ID必须等于文件所有者ID,或者进程必须是一个超级用户进程,只有写权限是不够的。
- 如果times是非空指针,且两个tv_nsec都是UTIME_OMIT(表示时间戳不变),则不进行权限检查。
futimens需要打开文件来更改时间,utimensat可使用文件名更改时间(flags可以设置为AT_FDCWD, 从调用进程的当前目录计算相对路径;flags也可以设置为AT_SYMLINK_NOFOLLOW表示不跟随符号链接;pathname也可以设置为绝对路径;)。
似乎st_ctim状态更改时间是无法指示值的,因为调用相关更改时间的函数时,它本身就会修改i节点里的值,进而会自动更新st_ctim。
20.mkdir,mkdirat和rmdir函数
#include
#include
int mkdir(const char *pathname, mode_t mode);
#include /* Definition of AT_* constants */
#include
int mkdirat(int dirfd, const char *pathname, mode_t mode);
成功返回0,出错返回-1.
创建一个空目录,其中 . 和 .. 项会自动创建,文件访问权限由mode和进程屏蔽字mask决定。
对于目录通常至少要设置一个执行权限位,从而可以进行目录检索进而访问目录中文件。
#include
int rmdir(const char *pathname);
成功返回0,出错返回-1.
删除空目录。
-
如果rmdir使目录链接计数为0,且没有其他进程打开该目录,则释放此目录空间;
-
如果rmdir使目录链接计数为0,且有其他进程打开该目录,则在此函数返回前删除最后一个链接和 . 和 .. 项,同时不能在此目录创建新文件,但是在最后一个进程关闭该文件之前不释放目录。(即使其他进程打开了这个目录也不能执行其他操作,这是为了保证该目录为空)。
21.读目录
对于某个目录具有访问权限的任一用户都可以读该目录,但是只有内核才能写目录。
一个目录的写权限位和执行权限决定了在目录中能否创建新文件以及删除文件,但是并不表示能否写目录本身。
#include
#include
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
成功返回指针,出错返回NULL。
#include
struct dirent *readdir(DIR *dirp);
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* 在目录流的当前位置 */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
成功返回指针,若在目录尾或出错,返回NULL
#include
#include
int closedir(DIR *dirp);
关闭文件夹。成功返回0,失败返回-1.
#include
long telldir(DIR *dirp);
返回参数dirp 目录流目前的读取位置(偏移量)
#include
void seekdir(DIR *dirp, long loc);
设置置参数dirp目录流目前的读取位置, 在调用readdir()时便从此新位置开始读取. 参数offset 代表距离目录文件开头的偏移量。
实例:递归目录,统计各类型文件数量与百分比。
#include "apue.h"
#include
#include
typedef int Myfunc(const char *, const struct stat*, int);
static Myfunc *pfunc;
static int myfunc(const char *pathname, const struct stat *statptr, int type);
static int myftw(char *,Myfunc *);
static int dopath(Myfunc* );
static long nreg,ndir,nblk,nchr,nfifo,nslink,nsock,ntot;
int main(int argc,char* argv[]){
int ret;
if(argc != 2){
err_quit("usage: ftw ");
}
pfunc = &myfunc;
ret = myftw(argv[1], pfunc);
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
if( ntot == 0 ){
ntot = 1;
}
printf("regular files = %7ld, %5.2f %%\n", nreg, nreg*100.0/ntot);
printf("directories = %7ld, %5.2f %%\n", ndir, ndir*100.0/ntot);
printf("block special = %7ld, %5.2f %%\n", nblk, nblk*100.0/ntot);
printf("char special = %7ld, %5.2f %%\n", nchr, nchr*100.0/ntot);
printf("Fifos = %7ld, %5.2f %%\n", nfifo, nfifo*100.0/ntot);
printf("symbolic links= %7ld, %5.2f %%\n", nslink, nslink*100.0/ntot);
printf("sockets = %7ld, %5.2f %%\n", nsock, nsock*100.0/ntot);
exit(ret);
}
#define FTW_F 1
#define FTW_D 2
#define FTW_DNR 3
#define FTW_NS 4
static char *fullpath;
static size_t pathlen;
static int myftw(char *pathname, Myfunc *func){
fullpath = path_alloc(&pathlen);
if(pathlen <= strlen(pathname)){
pathlen = strlen(pathname) * 2;
if((fullpath = realloc(fullpath,pathlen)) == NULL){
err_sys("realloc failed");
}
}
strcpy(fullpath,pathname);
return (dopath(func));
}
static int dopath(Myfunc *func){
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret,n;
if(lstat(fullpath,&statbuf)<0){
return (func(fullpath, &statbuf, FTW_NS));
}
if(S_ISDIR(statbuf.st_mode)==0){
return (func(fullpath, &statbuf, FTW_F));
}
if((ret = func(fullpath,&statbuf, FTW_D))!=0){
return ret;
}
n = strlen(fullpath);
if(n + NAME_MAX + 2 > pathlen){
pathlen *= 2;
if((fullpath = realloc(fullpath,pathlen))==NULL){
err_sys("realloc failed");
}
}
fullpath[n++] = '/';
fullpath[n]=0;
if((dp = opendir(fullpath)) == NULL){
return (func(fullpath, &statbuf, FTW_DNR));
}
while((dirp = readdir(dp))!=NULL){
if(strcmp(dirp->d_name,".")==0||strcmp(dirp->d_name,"..")==0){
continue;
}
strcpy(&fullpath[n], dirp->d_name);
if((ret = dopath(func))!=0){
break;
}
}
fullpath[n-1] = 0;
if(closedir(dp)<0){
err_ret("can't close directory %s",fullpath);
}
return ret;
}
static int myfunc(const char *pathname, const struct stat *statptr, int type){
switch(type){
case FTW_F:
switch(statptr->st_mode & S_IFMT){
case S_IFREG: nreg++; break;
case S_IFBLK: nblk++; break;
case S_IFCHR: nchr++; break;
case S_IFIFO: nfifo++;break;
case S_IFLNK: nslink++; break;
case S_IFSOCK: nsock++; break;
case S_IFDIR:
err_dump("for S_IFDIR for %s",pathname);
}
break;
case FTW_D:
ndir++; break;
case FTW_DNR:
err_ret("can't read directory %s",pathname);
break;
case FTW_NS:
err_ret("stat error for %s",pathname);
default:
err_dump("unknown type %d for pathname %s",type, pathname);
}
return 0;
}
22.chdir,fchdir和getcwd
#include
int chdir(const char *path);
int fchdir(int fd);
更改当前进程的当前工作目录。
成功,返回0;出错,返回-1.
#include
char *getcwd(char *buf, size_t size);
获取进程当前工作目录的绝对路径。buf是缓冲区,size是buf的大小。
23.文件访问权限位
习题明天更T_T