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
)以外も扱えるようにしていこうと思います。