Advent Calendar 2019: advfs
Day 5: ディレクトリツリーの管理
今日からは /test
以外のファイルも扱える少しまともな ramfs を開発していきます。最上位ディレクトリから再度実装し直すため,昨日実装した open
, read
, write
, truncate
は一旦 -ENOENT
を返すようにします。
ファイルシステム固有データ
昨日まではグローバル変数にいろいろとファイルシステム情報を保存しましたが,実装としてきれいではありません。今日からは,ファイルシステム固有のデータ構造は全て advfs_t
構造体に入れ,各関数内で扱います。そのため,まず main()
関数内で
advfs_t advfs;
を定義します。そして,この変数へのポインタを以下のように fuse_main()
関数の第4引数に渡します。
return fuse_main(argc, argv, &advfs_oper, &advfs);
この第4引数で渡されたポインタは,FUSE のコンテキスト情報内に保存されます。各 FUSE API 関数の中からこの変数を取得するには,以下のように, fuse_get_context()
関数でコンテキストを取得し,そのメンバー変数である private_data
を参照することで取得できます。
struct fuse_context *ctx;
advfs_t *advfs;
ctx = fuse_get_context();
advfs = ctx->private_data;
今回は,まずファイルシステムのエントリ(inodeに相当)タイプとして,未使用,通常ファイル,ディレクトリの3種類を以下の通り enum 型の advfs_entry_type_t
として定義します。
typedef enum {
ADVFS_UNUSED,
ADVFS_REGULAR_FILE,
ADVFS_DIR,
} advfs_entry_type_t;
通常ファイル固有の情報として,ファイルの中身を保存するバッファおよびそのサイズを持つ構造体 advfs_entry_file_t
を以下の通り定義します。
typedef struct {
uint8_t *buf;
size_t size;
} advfs_entry_file_t;
ディレクトリは,通常ファイルまたはディレクトリ(まとめてエントリ advfs_entry_t
として定義)を子に持つので,それらの数およびエントリインデックス配列 children
を持つ構造体 advfs_entry_dir_t
として定義します。子のエントリインデックス配列は,エントリへのポインタの配列でも良いのですが,実際のブロックデバイスへの実装を考え,エントリがブロックデバイスの一部に予め配列として予約されているものとして,今回はその配列へのインデックスで定義しています。
typedef struct _advfs_entry advfs_entry_t;
typedef struct {
int nent;
int *children;
} advfs_entry_dir_t;
エントリは名前とエントリタイプ,パーミッション,各種タイムスタンプおよびエントリタイプ固有の情報を持つ構造体として定義します。
struct _advfs_entry {
char name[ADVFS_NAME_MAX];
advfs_entry_type_t type;
int mode;
time_t atime;
time_t mtime;
time_t ctime;
union {
advfs_entry_file_t file;
advfs_entry_dir_t dir;
} u;
};
ファイルシステム固有の構造体として,最上位ディレクトリのエントリのインデックス root
および エントリ配列 entries
を持つ構造体 advfs_t
を以下のように定義します。
typedef struct {
int root;
advfs_entry_t *entries;
} advfs_t;
ディレクトリの初期化
今回は2日目同様,最上位ディレクトリのみを実装します。
上記で定義した構造体を用いて,最上位ディレクトリの初期化を行います。コードは以下の通りです。
advfs_t advfs;
int i;
struct timeval tv;
/* Allocate entries */
advfs.entries = malloc(sizeof(advfs_entry_t) * ADVFS_NUM_ENTRIES);
if ( NULL == advfs.entries ) {
return -1;
}
for ( i = 0; i < ADVFS_NUM_ENTRIES; i++ ) {
advfs.entries[i].type = ADVFS_UNUSED;
}
/* root directory */
gettimeofday(&tv, NULL);
advfs.root = 0;
advfs.entries[advfs.root].type = ADVFS_DIR;
advfs.entries[advfs.root].name[0] = '\0';
advfs.entries[advfs.root].mode = 0777;
advfs.entries[advfs.root].atime = tv.tv_sec;
advfs.entries[advfs.root].mtime = tv.tv_sec;
advfs.entries[advfs.root].ctime = tv.tv_sec;
advfs.entries[advfs.root].u.dir.nent = 0;
advfs.entries[advfs.root].u.dir.children = NULL;
まず,ADVFS_NUM_ENTRIES
個の要素を持つエントリ配列のメモリを確保します。そして,すべてのエントリを未使用 ADVFS_UNUSED
とします。
その後,最上位ディレクトリのエントリを初期化します。最上位ディレクトリはディレクトリ名を持たないので,名前を表す name
は空,パーミッションは 0777
,各種タイムスタンプは現在時刻とします。また,このディレクトリは空ディレクトリとするので u.dir.nent
は 0
とします。
パスからエントリの解決
昨日までに説明してきた通り,FUSE では,各種APIはパス名でファイルを識別します。このパス名からエントリを解決する関数を以下の通り作成します。
advfs_entry_t *
advfs_path2ent(advfs_t *advfs, const char *path)
{
advfs_entry_t *e;
if ( '/' != *path ) {
return NULL;
}
path++;
/* Root */
e = &advfs->entries[advfs->root];
if ( '\0' == *path ) {
return e;
}
return NULL;
}
今回は最上位ディレクトリのみの対応であるため,パスが /
であれば最上位ディレクトリのエントリを,それ以外であれば NULL
を返すようにします。
getattr および readdir の実装
パス名からエントリが解決できれば,昨日までの知識で getattr
および readdir
APIが実装できます。今回は以下の通り実装しました。
int
advfs_getattr(const char *path, struct stat *stbuf)
{
struct fuse_context *ctx;
advfs_t *advfs;
advfs_entry_t *e;
int status;
/* Reset the stat structure */
memset(stbuf, 0, sizeof(struct stat));
/* Get the context */
ctx = fuse_get_context();
advfs = ctx->private_data;
e = advfs_path2ent(advfs, path);
if ( NULL == e ) {
/* No entry found */
return -ENOENT;
}
if ( e->type == ADVFS_DIR ) {
/* Directory */
stbuf->st_mode = S_IFDIR | e->mode;
stbuf->st_nlink = 2 + e->u.dir.nent;
stbuf->st_uid = ctx->uid;
stbuf->st_gid = ctx->gid;
status = 0;
stbuf->st_atime = e->atime;
stbuf->st_mtime = e->mtime;
stbuf->st_ctime = e->ctime;
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
stbuf->st_birthtime = e->ctime;
#endif
} else {
status = -ENOENT;
}
return status;
}
int
advfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
struct fuse_context *ctx;
advfs_t *advfs;
advfs_entry_t *e;
/* Get the context */
ctx = fuse_get_context();
advfs = ctx->private_data;
e = advfs_path2ent(advfs, path);
if ( NULL == e || e->type != ADVFS_DIR ) {
/* No entry found or non-directory entry */
return -ENOENT;
}
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
return 0;
}
実行
いつものように実装したプログラムをビルド・実行するために
$ docker-compose build && docker-compose up -d
を実行し,
$ docker exec -it advfs_advfs_1 bash
と実行し, /mnt
に実装したファイルシステムをマウントしたコンテナの bash
を起動します。
2日目同様,ls -al /mnt
を実行します。2日目とは違い,今回は最上位ディレクトリのタイムスタンプを起動時刻にしたため,下記のように .
のタイムスタンプが起動時の時刻になっていることが確認できるかと思います。
root@0c7ab5397d27:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 2 root root 0 Dec 5 14:41 .
drwxr-xr-x 1 root root 4096 Dec 5 14:41 ..
今日のまとめと明日の予定
今日は最上位ディレクトリを実装しました。明日からはこのディレクトリ化にファイルを追加し,読み書きできるようにしていこうと思います。