kakts-log

programming について調べたことを整理していきます

C: ディレクトリストリームによるディレクトリエントリ情報の取得

UNIX/linuxベースのOSにおいて、C標準ライブラリを使って、ファイルシステムにおける指定したディレクトリの情報を取得する方法をまとめます。

環境

m1 mac OS(11.6)

ファイルシステムにおけるディレクトリについて

ディレクトリはデータ領域にディレクトリに関する情報を保持しています。 ディレクトリに関する情報は、主に下記になります。

ディレクトリ配下のファイル情報

  • ファイル名
  • ファイル名に対応するiノード番号

iノードはここでは詳しく説明しませんが、ファイルシステムにおいて、ファイルの属性情報を管理するためのデータとなります。 そのiノードごとに番号が割り振られていて、この番号のことをiノード番号と呼びます。

ディレクトリ配下に存在するファイルごとに、それを特定するファイル名と、ファイル名に紐づいたiノード番号が対応づけられ、これによってファイル名と対応したファイルデータをiノード番号から取得できます。

ディレクトリストリームを使ったディレクトリ情報の取得

ここで、C言語にて、ファイルシステムディレクトリ情報をとるための標準ライブラリ関数が用意されています。 C言語において、dirent.hをincludeすることで利用できます。

dirent.h(0p) - Linux manual page

ファイル操作でストリームを使うのと同様に、ディレクトリに対してもディレクトリストリームの仕組みが用意されていて、そのストリームをもとにディレクトリの操作をします。

ディレクトリの操作について

ディレクトリストリームを使ったディレクトリの操作は、主に以下の手順でおこないます。

  1. opendir(3)で指定したパスのディレクトリストリームを取得する
  2. readdir(3)で、1で取得したストリームから、ディレクトリ配下のファイル情報を取得する。 (readdir(3)は一度の呼び出しで1ファイルの情報を取得する。
  3. closedir(3)で、ディレクトリストリームを閉じる

ここで、2のreaddir(3)でディレクトリエントリ(ディレクトリ内の項目)の情報を読み取ります。 readdir(3)の戻り値の型はstruct direntで、POSIXで定義されているフィールドは下記のような構成になっています。

struct dirent {
  ino_t d_fileno; // iノード番号
  char d_name[]; // ファイル名
}

ディレクトリストリームを使った簡単なlsを作る

ここで、ディレクトリストリームを使って、カレンとディレクトリに対するディレクトリエントリの情報を表示する簡易lsコマンドを実装してみます。

/** 
 * ディレクトリストリームを使った簡単なls
 */
#include <errno.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main(void)
{
    DIR *dirp; // ディレクトリストリーム
    struct dirent *p; // ディレクトリ項目用の構造体 readdirで取得する

    // カレントディレクトリのディレクトリストリームを取得
    if ((dirp = opendir(".")) == NULL) {
        perror("opendir");
        exit(1);
    }
    errno = 0;
    // ディレクトリ項目がなくなるまでreaddirする
    while((p = readdir(dirp)) != NULL) {
        // ファイル名とiノード番号を表示する。
        printf("filename: %s, inode: %llu\n", p->d_name, p->d_ino);
    }

    if (errno != 0) {
        perror("readdir");
        exit(1);
    }

    // ディレクトリストリームを閉じる
    if (closedir(dirp) != 0) {
        perror("closedir");
        exit(1);
    }
    return 0;
}

カレントディレクトリに対するストリームを開き、そのストリームに対して、ディレクトリエントリを全部たどりきるまでreaddirを実行して、ディレクトリエントリ(ファイル)の情報を取得します。 そこでファイル名:d_name、iノード番号:d_inoを表示させます。

上記のコードをコンパイルして実行すると、下記の結果となります。

./a.out         
filename: ., inode: 4323177
filename: .., inode: 81985
filename: ch06, inode: 6747507
filename: ch01, inode: 4378311
filename: test022, inode: 4743777
filename: LICENSE, inode: 4323246
filename: test, inode: 4743763
filename: ch04, inode: 4956826
filename: ch03, inode: 4616053
filename: README.md, inode: 4323247
filename: sub, inode: 4684166
filename: ch02, inode: 4516118
filename: a.out, inode: 6748530
filename: ch05, inode: 5625378
filename: foo.txt, inode: 4527223
filename: .gitignore, inode: 4323245
filename: test.txt, inode: 4743184
filename: phone1.dat, inode: 4706419
filename: .git, inode: 4323178
filename: .vscode, inode: 4392160

こんな感じで、カレントディレクトリのディレクトリエントリ情報を表示することができました。