panda's tech note

Advent Calendar 2019: advfs

Day 11: ブロックデバイス対応(3)

今日も引き続きブロックデバイス対応をします。今回は ramfs なので,バイト単位でアクセスできますが,ブロックデバイスは本来ブロック単位でデータを読み書きするため,まずはブロック単位での読み書きを行う関数を実装します。

ブロックの読み込み

1ブロック読み込む関数として _read_block() 関数を以下の通り実装します。第2引数 inode は,ファイルの inode 構造体,第3引数 pos はブロックのポジションを表します。

static int
_read_block(advfs_t *advfs, advfs_inode_t *inode, void *buf, uint64_t pos)
{
    uint64_t b;
    uint64_t *block;

    if ( pos < ADVFS_INODE_BLOCKPTR - 1 ) {
        /* The block number is included in the inode structure */
        b = inode->blocks[pos];
    } else {
        /* Resolve from the chain */
        b = inode->blocks[ADVFS_INODE_BLOCKPTR - 1];
        block = _get_block(advfs, b);
        pos -= ADVFS_INODE_BLOCKPTR - 1;
        while ( pos >= (ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1) ) {
            /* Get the next chain */
            b = block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1];
            block = _get_block(advfs, b);
            pos -= ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1;
        }
        b = block[pos];
    }
    block = _get_block(advfs, b);
    memcpy(buf, block, ADVFS_BLOCK_SIZE);

    return 0;
}

ブロックの書き込み

static int
_write_block(advfs_t *advfs, advfs_inode_t *inode, void *buf, uint64_t pos)
{
    uint64_t b;
    uint64_t *block;

    if ( pos < ADVFS_INODE_BLOCKPTR - 1 ) {
        /* The block number is included in the inode structure */
        b = inode->blocks[pos];
    } else {
        /* Resolve from the chain */
        b = inode->blocks[ADVFS_INODE_BLOCKPTR - 1];
        block = _get_block(advfs, b);
        pos -= ADVFS_INODE_BLOCKPTR - 1;
        while ( pos >= (ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1) ) {
            /* Get the next chain */
            b = block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1];
            block = _get_block(advfs, b);
            pos -= ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1;
        }
        b = block[pos];
    }
    block = _get_block(advfs, b);
    memcpy(block, buf, ADVFS_BLOCK_SIZE);

    return 0;
}

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_inode_t *e;
    int perm;
    uint8_t block[ADVFS_BLOCK_SIZE];
    off_t pos;
    ssize_t remain;
    ssize_t i;
    ssize_t j;
    off_t k;

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

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

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

    remain = size;
    k = 0;
    while ( remain > 0 ) {
        pos = offset / ADVFS_BLOCK_SIZE;
        _read_block(advfs, e, block, pos);
        for ( i = (offset % ADVFS_BLOCK_SIZE), j = 0;
              i < ADVFS_BLOCK_SIZE && j < remain; i++, j++, k++ ) {
            buf[k] = block[j];
        }
        offset += j;
        remain -= j;
    }

    return k;
}

advfs_write

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_inode_t *e;
    int perm;
    size_t nsize;
    uint64_t nb;
    int ret;
    ssize_t i;
    ssize_t j;
    ssize_t remain;
    off_t pos;
    uint8_t block[ADVFS_BLOCK_SIZE];

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

    e = advfs_path2inode(advfs, path, 0);
    if ( NULL == e ) {
        return -ENOENT;
    }
    if ( e->attr.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;
    }

    /* Resize the block region */
    nsize = offset + size;
    nb = (nsize + ADVFS_BLOCK_SIZE - 1) / ADVFS_BLOCK_SIZE;
    ret = _resize_block(advfs, e, nb);
    if ( 0 != ret ) {
        return -EFAULT;
    }
    if ( nsize > e->attr.size ) {
        e->attr.size = nsize;
    }

    remain = size;
    while ( remain > 0 ) {
        if ( 0 != (offset % ADVFS_BLOCK_SIZE) ) {
            pos = offset / ADVFS_BLOCK_SIZE;
            _read_block(advfs, e, block, pos);
            for ( i = (offset % ADVFS_BLOCK_SIZE), j = 0;
                  i < ADVFS_BLOCK_SIZE && j < remain; i++, j++ ) {
                block[i] = buf[j];
            }
            _write_block(advfs, e, block, pos);
        }
        offset += j;
        remain -= j;
    }

    return size;
}

advfs_truncate

int
advfs_truncate(const char *path, off_t size)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_inode_t *e;
    uint64_t nb;
    uint8_t block[ADVFS_BLOCK_SIZE];
    int i;
    int ret;
    uint64_t pos;

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

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

    /* Calculate the number of blocks */
    nb = (size + ADVFS_BLOCK_SIZE - 1) / ADVFS_BLOCK_SIZE;
    ret = _resize_block(advfs, e, nb);
    if ( 0 != ret ) {
        return -EFAULT;
    }

    while ( (off_t)e->attr.size < size ) {
        pos = e->attr.size / ADVFS_BLOCK_SIZE;
        _read_block(advfs, e, block, pos);
        for ( i = e->attr.size % ADVFS_BLOCK_SIZE;
              i < ADVFS_BLOCK_SIZE && (off_t)e->attr.size < size; i++ ) {
            block[i] = 0;
            e->attr.size++;
        }
        _write_block(advfs, e, block, pos);
    }
    e->attr.size = size;

    return 0;
}

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

一昨日,昨日に引き続きブロックデバイスに対応するためリファクタリングを進めました。ファイルの読み書きやサイズ変更に対応し,8日目と同等の機能を提供できるようになりました。この時点での具体的な実装は src/main.c を参照してください。

明日からは @_ko1 先生からアドバイス頂いた dedup を実装してみようと思います。