panda's tech note

Advent Calendar 2019: advfs

Day 3: ファイルの定義

今回から数日間メモリ上にデータを配置する ramfs を実装していきます。 ramfs は FUSE でファイルシステムを作成する上で最も基本的な構造だと思われるので,今回はまず ramfs を実装します。最終的なゴールについては,Twitter で dedup (重複排除)するファイルシステムであったり,投機的プリフェッチを行うファイルシステムなど,最終的なゴールアイディアをいくつか頂いているので,今週末くらいまでにはなにかしらの方針を決めようかと思います。

1ファイル /test の最上位ディレクトリへの追加

まず,ファイルの読み書きを実装する前に,ファイルシステムの最上位ディレクトリに1つの通常ファイル /test を追加します。このファイルの属性情報や実体として以下の変数をグローバル変数として定義します。

static const char *ramfs_file_path = "/test";
static uint8_t *ramfs_file_buf = NULL;
static size_t ramfs_file_size = 0;
static size_t ramfs_file_max_size = 1 * 1024 * 1024;
static time_t ramfs_file_timestamp = 1575370225;

ramfs_file_path はファイルパスを指定します。 ramfs_file_buf はこのファイルの実体のメモリバッファです。 ramfs_file_sizeramfs_file_max_size はファイルの中身のサイズおよびバッファの最大サイズを定義します。 ramfs_file_timestamp はこのファイルの作成・アクセス・変更時刻の Unix タイムスタンプであり,今回は Tue Dec 3 19:50:25 JST 2019 を表す 1575370225 を使います。

このファイルをファイルシステムの最上位ディレクトリに追加するために, advfs_readdir() 関数を以下のように書き換えます。

if ( strcmp(path, "/") == 0 ) {
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, ramfs_file_path + 1, NULL, 0);
    return 0;
}

return -ENOENT;

昨日からの追加は

    filler(buf, ramfs_file_path + 1, NULL, 0);

の行です。この行で test をディレクトリに追加します。

また, advfs_getattr() 関数に以下の else if のブロック内に定義したように /test のファイル属性を取得するロジックを実装します。 st_mode はファイルタイプ(S_IFREGは通常ファイル)とパーミッション(0666)を定義します。また,st_nlink は通常ファイルの場合は 1 とします。 st_size はファイル名の長さを代入します。 st_atime, st_mtime, st_ctime, st_birthtime (macOSのみ) はそれぞれファイルの最終アクセス時刻,更新時刻,作成時刻,macOSにおける作成時刻を表します。st_uid, st_gid はファイルの所有者(ユーザ・グループ)を表します。最後に,st_size はファイルサイズ,st_blksize はブロックサイズ,st_blocks はブロック数を表します。

これに加え, / から参照されるファイル・ディレクトリが ., .., test の3つになったため,この属性を表す st_nlink2 から 3 に変更しています。

if ( strcmp(path, "/") == 0 ) {
    stbuf->st_mode = S_IFDIR | 0777;
    stbuf->st_nlink = 3;
    stbuf->st_uid = ctx->uid;
    stbuf->st_gid = ctx->gid;
    status = 0;
} else if ( strcmp(path, ramfs_file_path) == 0 ) {
    stbuf->st_mode = S_IFREG | 0666;
    stbuf->st_nlink = 1;
    stbuf->st_atime = (time_t)ramfs_file_timestamp;
    stbuf->st_mtime = (time_t)ramfs_file_timestamp;
    stbuf->st_ctime = (time_t)ramfs_file_timestamp;
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
    stbuf->st_birthtime = (time_t)ramfs_file_timestamp;
#endif
    stbuf->st_uid = ctx->uid;
    stbuf->st_gid = ctx->gid;
    stbuf->st_rdev = 0;
    stbuf->st_size = ramfs_file_size;
    stbuf->st_blksize = 4096;
    stbuf->st_blocks = (ramfs_file_size + 4095) / 4096;
    status = 0;
} else {
    status = -ENOENT;
}

また,ファイルシステムの状態を返す advfs_statfs() 関数の中身を以下のように書き換えます。

ssize_t used;

memset(buf, 0, sizeof(struct statvfs));

used = (ramfs_file_size + 4096 - 1) / 4096;

buf->f_bsize = 4096;
buf->f_frsize = 4096;
buf->f_blocks = 1024;       /* in f_frsize unit */
buf->f_bfree = 1024 - used;
buf->f_bavail = 1024 - used;

buf->f_files = 1000;
buf->f_ffree = 100 - 1;
buf->f_favail = 100 - 1;

buf->f_fsid = 0;
buf->f_flag = 0;
buf->f_namemax = 255;

return 0;

/test で利用済みのブロックを used 変数で計算し,空き領域から引いています。また,利用可能 inode の数を1減らしています(現状は適当に定義しているのであまり意味はありませんが)。

この時点での具体的な実装は src/main.c を参照してください。

実行

昨日同様に実装したプログラムをビルド・実行するために

$ docker-compose build && docker-compose up -d

を実行し,

$ docker exec -it advfs_advfs_1 bash

と実行することで, /mnt に実装したファイルシステムをマウントしたコンテナで bash を実行できます。

/mnt に対して ls コマンドを実行すると,以下の通り, . および .. ディレクトリと今回追加した /test ファイルが追加されていることがわかります。

root@6561c12bfb9c:/usr/src# ls -al /mnt/
total 4
drwxrwxrwx 2 root root    0 Jan  1  1970 .
drwxr-xr-x 1 root root 4096 Dec  3 11:30 ..
-rw-rw-rw- 1 root root    0 Dec  3 10:50 test

今日のまとめと明日の予定

今日は昨日使った最低限の3 APIを使って,最上位ディレクトリに test というファイルを追加しました。明日はこのファイルに対して読み書き(open, read, write)を実装します。