panda's tech note

Advent Calendar 2019: advfs

Day 7: 作成したファイルの読み書き

やることは4日目とほとんど同じです。4日目にグローバル変数で管理していたファイル属性をファイルエントリで管理するようになったくらいです。

getattr

ファイル属性を取得する advfs_getattr() 関数のファイル属性を返す部分を以下のように変更します。単純にファイルエントリの情報をコピーするだけでOKです。

    } else if ( e->type == ADVFS_REGULAR_FILE ) {
        stbuf->st_mode = S_IFREG | e->mode;
        stbuf->st_nlink = 1;
        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
        stbuf->st_rdev = 0;
        stbuf->st_size = e->u.file.size;
        stbuf->st_blksize = 4096;
        stbuf->st_blocks = (e->u.file.size + 4095) / 4096;
    } else {

read

基本的には4日目の内容そのままなのですが,ディレクトリの場合(ファイルでない場合)には -EISDIR エラーを返します。以下に advfs_read() 関数の実装を掲載します。

int
advfs_read(const char *path, char *buf, size_t size, off_t offset,
           struct fuse_file_info *fi)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;
    int perm;

    /* Get the context */
    ctx = fuse_get_context();
    advfs = ctx->private_data;

    e = advfs_path2ent(advfs, path, 0);
    if ( NULL == e ) {
        return -ENOENT;
    }
    if ( e->type != ADVFS_REGULAR_FILE ) {
        return -EISDIR;
    }

    /* Mode check */
    perm = fi->flags & 3;
    if ( perm != O_RDONLY && perm != O_RDWR ) {
        return -EACCES;
    }

    if ( offset < (off_t)e->u.file.size ) {
        if ( offset + size > e->u.file.size ) {
            size = e->u.file.size - offset;
        }
        (void)memcpy(buf, e->u.file.buf + offset, size);
    } else {
        size = 0;
    }

    return size;
}

write

write API では,read API よりも多少やることが増えます。4日目ではファイルの最大サイズのメモリを予め確保しましたが,今回は動的に割り当てます。以下のように realloc() でファイルサイズと同じメモリサイズに割り当て直しています。

int
advfs_write(const char *path, const char *buf, size_t size, off_t offset,
            struct fuse_file_info *fi)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;
    int perm;
    size_t nsize;
    uint8_t *nbuf;

    /* Get the context */
    ctx = fuse_get_context();
    advfs = ctx->private_data;

    e = advfs_path2ent(advfs, path, 0);
    if ( NULL == e ) {
        return -ENOENT;
    }
    if ( e->type != ADVFS_REGULAR_FILE ) {
        return -EISDIR;
    }

    /* Mode check */
    perm = fi->flags & 3;
    if ( perm != O_WRONLY && perm != O_RDWR ) {
        return -EACCES;
    }
    if ( size <= 0 ) {
        return 0;
    }

    nsize = offset + size;
    if ( nsize > e->u.file.size ) {
        /* Reallocate */
        nbuf = realloc(e->u.file.buf, nsize);
        if ( NULL == nbuf ) {
            return -EDQUOT;
        }
        e->u.file.buf = nbuf;
        e->u.file.size = nsize;
    }

    (void)memcpy(e->u.file.buf + offset, buf, size);

    return size;
}

truncate

truncate API も write API と同様に,以下のように realloc() 関数により,ファイルの実体のメモリを truncate するサイズに合わせ動的に割り当て直します。

int
advfs_truncate(const char *path, off_t size)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;
    uint8_t *nbuf;

    /* Get the context */
    ctx = fuse_get_context();
    advfs = ctx->private_data;

    e = advfs_path2ent(advfs, path, 0);
    if ( NULL == e ) {
        return -ENOENT;
    }
    if ( e->type != ADVFS_REGULAR_FILE ) {
        return -EISDIR;
    }

    if ( (off_t)e->u.file.size != size ) {
        if ( size > 0 ) {
            nbuf = realloc(e->u.file.buf, size);
            if ( NULL == nbuf ) {
                return -EFBIG;
            }
            e->u.file.buf = nbuf;
        } else {
            free(e->u.file.buf);
            e->u.file.buf = NULL;
        }
    }

    while ( (off_t)e->u.file.size < size ) {
        e->u.file.buf[e->u.file.size] = 0;
        e->u.file.size++;
    }
    e->u.file.size = size;

    return 0;
}

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

実行

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

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

を実行し,

$ docker exec -it advfs_advfs_1 bash

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

以下のように /mnt/test に対して echo コマンドの出力をリダイレクトし, cat コマンドで出力することで,ファイルの読み書きが正常に行われていることが確認できます。

root@8cc941723b56:/usr/src# ls -al /mnt/
total 4
drwxrwxrwx 2 root root    0 Dec  7 15:09 .
drwxr-xr-x 1 root root 4096 Dec  7 15:09 ..
root@8cc941723b56:/usr/src# echo "test1234" > /mnt/test
root@8cc941723b56:/usr/src# ls -al /mnt/
total 5
drwxrwxrwx 3 root root    0 Dec  7 15:09 .
drwxr-xr-x 1 root root 4096 Dec  7 15:09 ..
-rw-r--r-- 1 root root    9 Dec  7 15:09 test
root@8cc941723b56:/usr/src# cat /mnt/test
test1234
root@8cc941723b56:/usr/src# echo "test56789" >> /mnt/test
root@8cc941723b56:/usr/src# ls -al /mnt/
total 5
drwxrwxrwx 3 root root    0 Dec  7 15:09 .
drwxr-xr-x 1 root root 4096 Dec  7 15:09 ..
-rw-r--r-- 1 root root   19 Dec  7 15:09 test
root@8cc941723b56:/usr/src# cat /mnt/test
test1234
test56789
root@8cc941723b56:/usr/src# echo "a" > /mnt/test
root@8cc941723b56:/usr/src# ls -al /mnt/
total 5
drwxrwxrwx 3 root root    0 Dec  7 15:09 .
drwxr-xr-x 1 root root 4096 Dec  7 15:09 ..
-rw-r--r-- 1 root root    2 Dec  7 15:09 test
root@8cc941723b56:/usr/src# cat /mnt/test
a

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

今日は作成したファイルに対する読み書きを実装しました。明日はファイルの削除やディレクトリの追加を実装しようと思います。