Advent Calendar 2019: advfs
Day 4: ファイルの読み書き
昨日に引き続き ramfs を FUSE 上に実装していきます。今日は,昨日追加した /test の読み書きを行います。読み書きを行うために実装するAPIは,open,read,write の3つですが,今回はリダイレクトでの上書きや vim などのエディタでの書き込みもサポートするために,truncate も実装します。
初期化
fuse_main を呼び出す前に,/test ファイルの実体 ramfs_file_buf のメモリを確保します。具体的には以下のようにします。単純に malloc でメモリを確保しているだけです。
int
main(int argc, char *argv[])
{
ramfs_file_buf = malloc(ramfs_file_max_size);
if ( NULL == ramfs_file_buf ) {
return -1;
}
return fuse_main(argc, argv, &advfs_oper, NULL);
open
まずファイルを開く open を実装します。今回は /test ファイルのみを扱うので,これ以外のファイル名が指定された場合は -ENOENT を返します。open() システムコールは,VFS内部では vnode,アプリケーションにはファイルディスクリプタを返し,それらを用いて read, write などの関数(やシステムコール)を呼び出しますが,FUSE ではパスのまま扱います。また,ファイルの生成もカーネルで create に変換されるため,open 必要なのはパーミッションチェックとキャッシュの取り扱いのみです。今回は,以下のように /test に対しては何も行わないようにしています。
int
advfs_open(const char *path, struct fuse_file_info *fi)
{
if ( strcmp(path, ramfs_file_path) != 0 ) {
return -ENOENT;
}
return 0;
}
パーミッションチェックをする場合は,第二引数の struct fuse_file_info *fi の fi->flags に保存されるフラグをチェックします。
read
read は以下のように実装します。ファイルパスは第1引数で渡されるため, open と同様に /test 以外のファイルに対しては -ENOENT を返します。 open で指定されたオプションは第5引数で渡される fi に保存されているため, fi->flags を確認することで,このファイルが読み込みモード( O_RDONLY または O_RDWR )で開かれたことを確認します。第2引数,第3引数は,読み込みバッファとバッファサイズであり,ここにファイルの中身を書き込みます。第4引数の offset は読み込むファイルのオフセットが指定されます。この関数は read() 関数と同様に読み込んだバイト数を返します。今回は /test の中身は ramfs_file_buf に保存しているため,この offset バイト目から size バイト分またはファイルの終端までをコピーします。
int
advfs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
int perm;
if ( strcmp(path, ramfs_file_path) != 0 ) {
return -ENOENT;
}
/* Permission check */
perm = fi->flags & 3;
if ( perm != O_RDONLY && perm != O_RDWR ) {
return -EACCES;
}
if ( offset < (off_t)ramfs_file_size ) {
if ( offset + size > ramfs_file_size ) {
size = ramfs_file_size - offset;
}
(void)memcpy(buf, ramfs_file_buf + offset, size);
} else {
size = 0;
}
return size;
}
write
write も read と同様にファイルの書き込みモードのチェックを行い,ramfs_file_buf に書き込むだけです。単純なコードなので以下のコードを参照してください。 ramfs_file_buf には ramfs_file_max_size バイト分のみメモリを確保しているので,このサイズを超える場合はクオータサイズを超えたことを表すエラー -EDQUOT を返します。
int
advfs_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
int perm;
if ( strcmp(path, ramfs_file_path) != 0 ) {
return -ENOENT;
}
/* Permission check */
perm = fi->flags & 3;
if ( perm != O_WRONLY && perm != O_RDWR ) {
return -EACCES;
}
if ( size <= 0 ) {
return 0;
}
if ( offset + size > ramfs_file_max_size ) {
return -EDQUOT;
}
(void)memcpy(ramfs_file_buf + offset, buf, size);
if ( ramfs_file_size < offset + size ) {
ramfs_file_size = offset + size;
}
return size;
}
fuse_operations
open, read, write, truncate を実装したので,それらの関数を以下のように fuse_main() に渡す struct fuse_operations 構造体のメンバ変数に追加します。
static struct fuse_operations advfs_oper = {
.getattr = advfs_getattr,
.readdir = advfs_readdir,
.statfs = advfs_statfs,
.open = advfs_open,
.read = advfs_read,
.write = advfs_write,
.truncate = advfs_truncate,
};
この時点での具体的な実装は src/main.c を参照してください。
実行
昨日同様に実装したプログラムをビルド・実行するために
$ docker-compose build && docker-compose up -d
を実行し,
$ docker exec -it advfs_advfs_1 bash
と実行することで, /mnt に実装したファイルシステムをマウントしたコンテナで bash を実行できます。
以下のように /mnt/test に対して echo コマンドの出力をリダイレクトし, cat コマンドで出力することで,ファイルの読み書きが正常に行われていることが確認できます。
root@b2cf2d9bacc9:/usr/src# ls -al /mnt/
total 4
drwxrwxrwx 2 root root 0 Jan 1 1970 .
drwxr-xr-x 1 root root 4096 Dec 4 13:01 ..
-rw-rw-rw- 1 root root 0 Dec 3 10:50 test
root@b2cf2d9bacc9:/usr/src# cat /mnt/test
root@b2cf2d9bacc9:/usr/src# echo "test1234" > /mnt/test
root@b2cf2d9bacc9:/usr/src# cat /mnt/test
test1234
root@b2cf2d9bacc9:/usr/src# echo "test56789" >> /mnt/test
root@b2cf2d9bacc9:/usr/src# cat /mnt/test
test1234
test56789
root@b2cf2d9bacc9:/usr/src# echo "a" > /mnt/test
root@b2cf2d9bacc9:/usr/src# cat /mnt/test
a
今日のまとめと明日の予定
今日は open, read, write, truncate の4 API関数を実装しました。これで, ramfs 上の /test ファイルを読み書きできるようになりました。明日は,特定のファイル(/test)以外も扱えるようにしていこうと思います。