getdentsシステムコール
Rev.1を表示中。最新版はこちら。
readdir()でlsコマンドを作成できます。#include <stdio.h> #include <sys/stat.h> #include <dirent.h> int main () { struct dirent *dirst; int i =1; DIR *dp = opendir("/"); while((dirst = readdir(dp)) != NULL) { if (!(i++ % 8)) { printf("\n"); } printf("%11s", dirst->d_name); } closedir(dp); printf("\n"); return 0; } [root@localhost kitamura]# ./a.out bin home . .. debugfs opt proc lost+found var boot mnt pstore mnt1 sbin tmp dev sys etc va usr run sd root sysroot srv.autorelabel media libちなみにstruct direntは下記のように定義されています。これはext2/3等のファイルシステムのディレクトフォーマットに準じての宣言です。
struct dirent { ino_t d_ino; /* inode 番号 */ off_t d_off; /* オフセットではない; 注意を参照 */ unsigned short d_reclen; /* このレコードの長さ */ unsigned char d_type; /* ファイル種別。全ファイルシステム */ でサポートされているわけではない */ char d_name[256]; /* ファイル名 */ };readdir()はシステムコールgetdentsがコールされます。引数は順にディレクトリのファイルID、バッファ、バッファサイズです。ディレクトリの読み出しは、ファイルの読み出しと同じようにデータブロック領域の読み出しを行うわけですが、ファイルと違って、inodeディレクトリのフォーマットとして読み込む必要があります。それ故ユーザ引数のバッファはdirentとなっている訳です。
SYSCALL_DEFINE3(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; buf.previous = NULL; buf.count = count; buf.error = 0; error = vfs_readdir(file, filldir, &buf); if (error >= 0) error = buf.error; lastdirent = buf.previous; if (lastdirent) { if (put_user(file->f_pos, &lastdirent->d_off)) error = -EFAULT; else error = count - buf.count; } fput(file); out: return error; }vfs_readdir()の第2引数fillerは、ファイルシステムのファーマットに依存するinodeデータブロックに有するディレクトリ情報を、buffに設定するコールバック関数です。初期チェック後、ファイルオペレーションのreaddirをコールします。
int vfs_readdir(struct file *file, filldir_t filler, void *buf) { struct inode *inode = file->f_path.dentry->d_inode; int res = -ENOTDIR; if (!file->f_op || !file->f_op->readdir) goto out; res = security_file_permission(file, MAY_READ); if (res) goto out; res = mutex_lock_killable(&inode->i_mutex); if (res) goto out; res = -ENOENT; if (!IS_DEADDIR(inode)) { res = file->f_op->readdir(file, buf, filler); file_accessed(file); } mutex_unlock(&inode->i_mutex); out: return res; }dcache_readdir()はファイルオペレーション simple_dir_operationsの.readdirコールバック関数です。simple_dir_operationsはramfsのような実デバイスを有しないファイルシステムで使われています。
.、..そして処理対象のdentryの子dentryを操作し、その内容をfilldir()でdirentに設定します。子dentryを操作では、いったん子のdentry->d_subdirsのdentryをcursor->d_u.d_childに移して、そのリストを操作しています。
filp->f_posはバッファー内のオフセットですが、ディレクトリの操作ではインデックスとなり、ユーザプロセス側では、返り値のオフセットで、取得できた数として処理します。
なお、.、..ディレクトリは仮想ディレクトリ(実デバイスはもちろん、vsf下においてもdentryとして存在していない。)で、inode番号は、親のinode番号となります。
int dcache_readdir(struct file * filp, void * dirent, filldir_t filldir) { struct dentry *dentry = filp->f_path.dentry; struct dentry *cursor = filp->private_data; struct list_head *p, *q = &cursor->d_u.d_child; ino_t ino; int i = filp->f_pos; switch (i) { case 0: ino = dentry->d_inode->i_ino; if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0) break; filp->f_pos++; i++; case 1: ino = parent_ino(dentry); if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0) break; filp->f_pos++; i++; default: spin_lock(&dentry->d_lock); if (filp->f_pos == 2) list_move(q, &dentry->d_subdirs); for (p=q->next; p != &dentry->d_subdirs; p=p->next) { struct dentry *next; next = list_entry(p, struct dentry, d_u.d_child); spin_lock_nested(&next->d_lock, DENTRY_D_LOCK_NESTED); if (!simple_positive(next)) { spin_unlock(&next->d_lock); continue; } spin_unlock(&next->d_lock); spin_unlock(&dentry->d_lock); if (filldir(dirent, next->d_name.name, next->d_name.len, filp->f_pos, next->d_inode->i_ino, dt_type(next->d_inode)) < 0) return 0; spin_lock(&dentry->d_lock); spin_lock_nested(&next->d_lock, DENTRY_D_LOCK_NESTED); /* next is still alive */ list_move(q, p); spin_unlock(&next->d_lock); p = q; filp->f_pos++; } spin_unlock(&dentry->d_lock); } return 0; }filldir()でディレクトリ配下の内容をユーザバッファに設定します。引数は順に、ユーザバッファ、ディレクトリ名、ディレクトリ名長、取得するインデックス、inode属性。ただし、実際のユーザバッファは、buf->current_dirとなります。(sys_getdentsでbuf.current_dir = direntとしています。)
dirent = buf->current_dir()とし、
dirent->d_off/dirent->d_ino/dirent->d_reclen/dirent->d_nameと最後の位置にinode属性を設定し、buf->current_dir = (void __user *)dirent + reclenで次の設定に備えます。
static int filldir(void * __buf, const char * name, int namlen, loff_t offset, u64 ino, unsigned int d_type) { struct linux_dirent __user * dirent; struct getdents_callback * buf = (struct getdents_callback *) __buf; unsigned long d_ino; int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2, sizeof(long)); buf->error = -EINVAL; /* only used if we fail.. */ if (reclen > buf->count) return -EINVAL; d_ino = ino; if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) { buf->error = -EOVERFLOW; return -EOVERFLOW; } dirent = buf->previous; if (dirent) { if (__put_user(offset, &dirent->d_off)) goto efault; } dirent = buf->current_dir; if (__put_user(d_ino, &dirent->d_ino)) goto efault; if (__put_user(reclen, &dirent->d_reclen)) goto efault; if (copy_to_user(dirent->d_name, name, namlen)) goto efault; if (__put_user(0, dirent->d_name + namlen)) goto efault; if (__put_user(d_type, (char __user *) dirent + reclen - 1)) goto efault; buf->previous = dirent; dirent = (void __user *)dirent + reclen; buf->current_dir = dirent; buf->count -= reclen; return 0; efault: buf->error = -EFAULT; return -EFAULT; }