本节简要介绍一下文件和目录操作常用的系统函数,常用的文件操作命令如ls、cp、mv等也是基于这些函数实现的。本节的侧重点在于讲解这些函数的工作原理,而不是如何使用它们,理解了实现原理之后再看这些函数的用法就很简单了,请读者自己查阅Man Page了解其用法。
- stat(2) 函数读取文件的 inode,然后把 inode 中的各种文件属性填入一个 struct stat 结构体传出给调用者。stat(1) 命令是基于 stat 函数实现的。stat 需要根据传入的文件路径找到 inode,假设一个路径是 /opt/file,则查找的顺序是:
· 读出 inode 表中第 2 项,也就是根目录的 inode,从中找出根目录数据块的位置
· 从根目录的数据块中找出文件名为 opt 的记录,从记录中读出它的 inode 号
· 读出 opt 目录的 inode,从中找出它的数据块的位置
· 从 opt 目录的数据块中找出文件名为 file 的记录,从记录中读出它的 inode 号
· 读出 file 文件的 inode
还有另外两个类似 stat 的函数:
- fstat(2) 函数传入一个已打开的文件描述符,传出 inode 信息。
- lstat(2) 函数也是传入路径传出 inode 信息,但是和 stat 函数有一点不同,当文件是一个符号链接时, stat(2) 函数传出的是它所指向的目标文件的 inode,而 lstat 函数传出的就是符号链接文件本身的 inode。
- access(2) 函数检查执行当前进程的用户是否有权限访问某个文件,传入文件路径和要执行的访问操作(读/写/执行),access 函数取出文件 inode 中的 st_mode 字段,比较一下访问权限,然后返回 0 表示允许访问,返回 -1 表示错误或不允许访问。
chmod(2) 和 fchmod(2) 函数改变文件的访问权限,也就是修改 inode 中的 st_mode 字段。这两个函数的区别类似于 stat/fstat 。chmod(1) 命令是基于 chmod 函数实现的。
chown(2) / fchown(2) / lchown(2) 改变文件的所有者和组,也就是修改 inode 中的 User 和 Group 字段,只有超级用户才能正确调用这几个函数,这几个函数之间的区别类似于 stat/fstat/lstat。 chown(1) 命令是基于 chown 函数实现的。
utime(2) 函数改变文件的访问时间和修改时间,也就是修改 inode 中的 atime 和 mtime 字段。touch(1) 命令是基于 utime 函数实现的。
truncate(2) 和 ftruncate(2) 函数把文件截断到某个长度,如果新的长度比原来的长度短,则后面的数据被截掉了,如果新的长度比原来的长度长,则后面多出来的部分用 0 填充,这需要修改 inode 中的 Blocks 索引项以及块位图中相应的 bit。这两个函数的区别类似于 stat/fstat。
link(2) 函数创建硬链接,其原理是在目录的数据块中添加一条新记录,其中的 inode 号字段和原文件相同。
symlink(2) 函数创建一个符号链接,这需要创建一个新的 inode,其中 st_mode 字段的文件类型是符号链接,原文件的路径保存在 inode 中或者分配一个数据块来保存。
ln(1) 命令是基于 link 和 symlink 函数实现的。
- unlink(2) 函数删除一个链接。如果是符号链接则释放这个符号链接的 inode 和数据块,清除 inode 位图和块位图中相应的位。如果是硬链接则从目录的数据块中清除一条文件名记录,如果当前文件的硬链接数已经是 1 了还要删除它,就同时释放它的 inode 和数据块,清除 inode 位图和块位图中相应的位,这样就真的删除文件了。
unlink(1) 命令和 rm(1) 命令是基于 unlink 函数实现的。
- rename(2) 函数改变文件名,需要修改目录数据块中的文件名记录,如果原文件名和新文件名不在一个目录下则需要从原目录数据块中清除一条记录然后添加到新目录的数据块中。
mv(1) 命令是基于 rename 函数实现的,因此在同一分区的不同目录中移动文件并不需要复制和删除文件的 inode 和数据块,只需要一个改名操作,即使要移动整个目录,这个目录下有很多子目录和文件也要随着一起移动,移动操作也只是对顶级目录的改名操作,很快就能完成。但是,如果在不同的分区之间移动文件就必须复制和删除 inode 和数据块,如果要移动整个目录,所有子目录和文件都要复制删除,这就很慢了。
readlink(2) 函数读取一个符号链接所指向的目标路径,其原理是从符号链接的 inode 或数据块中读出保存的数据,这就是目标路径。
mkdir(2) 函数创建新的目录,要做的操作是在它的父目录数据块中添加一条记录,然后分配新的 inode 和数据块, inode 的 st_mode 字段的文件类型是目录,在数据块中填两个记录,分别是 . 和 ..,由于 .. 表示父目录,因此父目录的硬链接数要加 1。mkdir(1) 命令是基于 mkdir 函数实现的。
rmdir(2) 函数删除一个目录,这个目录必须是空的(只包含 . 和 .. )才能删除,要做的操作是释放它的 inode 和数据块,清除 inode 位图和块位图中相应的位,清除父目录数据块中的记录,父目录的硬链接数要减 1。 rmdir(1) 命令是基于 rmdir 函数实现的。
opendir(3)/readdir(3)/closedir(3) 用于遍历目录数据块中的记录。opendir 打开一个目录,返回一个 DIR * 指针代表这个目录,它是一个类似 FILE * 指针的句柄,closedir 用于关闭这个句柄,把 DIR * 指针传给 readdir 读取目录数据块中的记录,每次返回一个指向 struct dirent 的指针,反复读就可以遍历所有记录,所有记录遍历完之后 readdir 返回 NULL。结构体 struct dirent 的定义如下:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file */
char d_name[256]; /* filename */
};
- 这些字段和上图 基本一致。这里的文件名 d_name 被库函数处理过,已经在结尾加了 '\0',而上图中的文件名字段不保证是以 '\0' 结尾的,需要根据前面的文件名长度字段确定文件名到哪里结束。
- 下面这个例子递归地打印出一个目录下的所有子目录和文件,类似 ls -R。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#define MAX_PATH 1024
/* dirwalk: apply fcn to all files in dir */
void dirwalk(char *dir, void (*fcn)(char *))
{
char name[MAX_PATH];
struct dirent *dp;
DIR *dfd;
if ((dfd = opendir(dir)) == NULL) {
fprintf(stderr, "dirwalk: can't open %s\n", dir);
return;
}
while ((dp = readdir(dfd)) != NULL) {
if (strcmp(dp->d_name, ".") == 0
|| strcmp(dp->d_name, "..") == 0)
continue; /* skip self and parent */
if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name))
fprintf(stderr, "dirwalk: name %s %s too long\n",
dir, dp->d_name);
else {
sprintf(name, "%s/%s", dir, dp->d_name);
(*fcn)(name);
}
}
closedir(dfd);
}
/* fsize: print the size and name of file "name" */
void fsize(char *name)
{
struct stat stbuf;
if (stat(name, &stbuf) == -1) {
fprintf(stderr, "fsize: can't access %s\n", name);
return;
}
if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
dirwalk(name, fsize);
printf("%8ld %s\n", stbuf.st_size, name);
}
int main(int argc, char **argv)
{
if (argc == 1) /* default: current directory */
fsize(".");
else
while (--argc > 0)
fsize(*++argv);
return 0;
}
然而这个程序还是不如 ls -R 健壮,它有可能死循环,思考一下什么情况会导致死循环。
转载自:http://docs.linuxtone.org/ebooks/C&CPP/c/ch29s02.html