浅析linux中目录枚举的具体实现
如果我们知道文件名,我们可以直接输入文件名来打开它,
但是如果一个目录下有什么文件我们不知道呢,那我们该怎么办呢,
那就需要使用listdir先列出当前目录下包含的所有文件和目录,
然后我们就可以从ls列出来的文件名中,输入需要访问的文件名进行文件访问了,
这其中起到关键作用的函数一共有3个,
(1).用户空间的opendir()库函数,
(2).内核空间系统调用sys_getdents()
(3).相应挂接上的文件系统的目录操作函数机的readdir()方法实现,
比如proc_dir_operations方法集中的proc_readdir()方法实现.
经过上面3个主要函数的通力协作,枚举目录下文件的工作就成了小case了[luther.gliethttp]
下面来看看具体源码实现.
1.库函数opendir
// 打开目录,获取目录方法集,比如proc_dir_operations
DIR* opendir( const char* dirpath )
{
DIR* dir = malloc(sizeof(DIR));
if (!dir)
goto Exit;
dir->_DIR_fd = open(dirpath, O_RDONLY|O_DIRECTORY); // 打开O_DIRECTORY,在sys_open==>__link_path_walk发现该标志LOOKUP_DIRECTORY后,
// 那么此次打开操作将只到最后一个'/'分隔符前面的内容,比如'/proc/sys/',那么将打开sys这个目录[luther.gliethttp]
// 同时赋给该目录操作方法集file->f_op = proc_dir_operations;
if (dir->_DIR_fd < 0)
{
free(dir);
dir = NULL;
}
else
{
dir->_DIR_avail = 0;
dir->_DIR_next = NULL;
pthread_mutex_init( &dir->_DIR_lock, NULL );
}
Exit:
return dir;
}
struct dirent*
readdir(DIR * dir)
{
struct dirent *entry = NULL;
pthread_mutex_lock( &dir->_DIR_lock );
entry = _readdir_unlocked(dir); // 使用sys_getdents系统调用读取目录[luther.gliethttp]
pthread_mutex_unlock( &dir->_DIR_lock );
return entry;
}
static int listdir(const char *name, int flags)
{
char tmp[4096];
DIR *d;
struct dirent *de;
d = opendir(name);
if(d == 0) {
fprintf(stderr, "opendir failed, %s\n", strerror(errno));
return -1;
}
while((de = readdir(d)) != 0){ // 循环读取,直到空,首先读取的是'.'和'..'目录[luther.gliethttp]
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue;
if ((flags & LIST_LONG) != 0) {
sprintf(tmp, "%s/%s", name, de->d_name);
listfile(tmp, flags);
} else {
printf("%s\n", de->d_name);
}
}
......
}
2.内核中系统调用函数sys_getdents()
sys_getdents
==>vfs_readdir(file, filldir, &buf);
==>file->f_op->readdir(file, buf, filler);
==>proc_dir_operations.proc_readdir返回目录
asmlinkage long sys_getdents(unsigned int fd, struct linux_dirent __user * dirent, unsigned int count)
{
struct file * file;
struct linux_dirent __user * lastdirent;
struct getdents_callback buf;
int error;
error = -EFAULT;
if (!access_ok(VERIFY_WRITE, dirent, count))
goto out;
error = -EBADF;
file = fget(fd);
if (!file)
goto out;
buf.current_dir = dirent; // 当前dir
buf.previous = NULL; // 前一个为空
buf.count = count; // 内存大小
buf.error = 0;
error = vfs_readdir(file, filldir, &buf);
if (error < 0)
goto out_putf;
error = buf.error;
lastdirent = buf.previous;
if (lastdirent) {
if (put_user(file->f_pos, &lastdirent->d_off))
error = -EFAULT;
else
error = count - buf.count;
}
out_putf:
fput(file);
out:
return error;
}
3.文件系统支持的readdir()函数,比如proc_readdir
int proc_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
struct inode *inode = filp->f_path.dentry->d_inode;
return proc_readdir_de(PDE(inode), filp, dirent, filldir);
}
int proc_readdir_de(struct proc_dir_entry *de, struct file *filp, void *dirent,
filldir_t filldir)
{
unsigned int ino;
int i;
struct inode *inode = filp->f_path.dentry->d_inode; // /proc/sys/对应的inode
int ret = 0;
lock_kernel();
ino = inode->i_ino;
if (!de) {
ret = -EINVAL;
goto out;
}
i = filp->f_pos; // 这里f_pos表示到目前为止一共读取了多少个目录下的数据了.
switch (i) {
case 0: // 第一次读取
if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0)
goto out;
i++;
filp->f_pos++;
/* fall through */
case 1: // 第二次读取
if (filldir(dirent, "..", 2, i,
parent_ino(filp->f_path.dentry),
DT_DIR) < 0)
goto out;
i++;
filp->f_pos++;
/* fall through */
default:
spin_lock(&proc_subdir_lock);
// 接下来就是读取当前/proc/sys/目录下存在的目录了,sys目录下的所有文件和目录都挂接在
// de->subdir中.
de = de->subdir;
i -= 2; // 忽略上面'.'和'..'所占的2个计数
for (;;) {
if (!de) {
ret = 1;
spin_unlock(&proc_subdir_lock);
goto out;
}
if (!i)
break;
de = de->next;
i--;
}
do {
struct proc_dir_entry *next;
/* filldir passes info to user space */
de_get(de);
spin_unlock(&proc_subdir_lock);
// 将一个个dirent顺序填入lib库指定的缓冲区,lib库指定的默认缓冲区一次只能容纳15个dirent:
// struct DIR
// {
// int _DIR_fd;
// size_t _DIR_avail;
// struct dirent* _DIR_next;
// pthread_mutex_t _DIR_lock;
// struct dirent _DIR_buff[15]; // 所以lib库一次最多读取15个dirent目录下的内容[luther.gliethttp]
// };
// 在filldir()函数中,
// dirent = buf->previous;
// if (dirent) {
// 如果previous存在了,那么将filp->f_pos存入dirent->d_off,
// if (__put_user(offset, &dirent->d_off))
// goto efault;
// }
if (filldir(dirent, de->name, de->namelen, filp->f_pos,
de->low_ino, de->mode >> 12) < 0) {
de_put(de);
goto out;
}
spin_lock(&proc_subdir_lock);
filp->f_pos++; // 读取目录下文件个数计数加1[luther.gliethttp]
next = de->next;
de_put(de);
de = next;
} while (de);
spin_unlock(&proc_subdir_lock);
}
ret = 1;
out: unlock_kernel();
return ret;
}
|