panda's tech note

Advent Calendar 2019: advfs

Day 10: ブロックデバイス対応(2)

今日も引き続きブロックデバイス対応をします。

空きブロックの管理

空きブロックを管理するために,_alloc_block() および _free_block() 関数を実装します。これらの関数では,ブロック番号のまま扱いますので,ブロック番号からブロックの実体のアドレスを解決するための関数 _get_block() も実装します。それぞれの関数の実装は以下の通りです。

static uint64_t
_alloc_block(advfs_t *advfs)
{
    uint64_t b;
    advfs_free_list_t *fl;

    b = advfs->superblock->freelist;
    if ( 0 == b ) {
        return 0;
    }
    fl = _get_block(advfs, b);
    advfs->superblock->freelist = fl->next;

    advfs->superblock->n_block_used++;

    return b;
}
static void
_free_block(advfs_t *advfs, uint64_t b)
{
    advfs_free_list_t *fl;

    fl = _get_block(advfs, b);
    fl->next = advfs->superblock->freelist;
    advfs->superblock->freelist = b;

    advfs->superblock->n_block_used--;
}
static void *
_get_block(advfs_t *advfs, uint64_t b)
{
    return (void *)advfs->superblock + ADVFS_BLOCK_SIZE * b;
}

inode の探索

パス名からエントリを探す関数として advfs_path2ent() を実装し使っていましたが,この関数を inode に対応させます。これに伴い,関数名を advfs_path2inode() に変更します。アプローチはほぼ advfs_path2ent() の通りですが,inode 配列がスーパーブロックの後に続くようになっているため,そこの実装が多少異なっています。

static advfs_inode_t *
_path2inode_rec(advfs_t *advfs, advfs_inode_t *cur, const char *path,
                int create)
{
    int ret;
    advfs_inode_t *e;
    char name[ADVFS_NAME_MAX + 1];
    char *s;
    size_t len;
    ssize_t i;
    uint64_t inode;

    if ( cur->attr.type != ADVFS_DIR ) {
        return NULL;
    }

    /* Remove the head '/'s */
    if ( '/' != *path ) {
        return NULL;
    }
    while ( '/' == *path ) {
        path++;
    }

    /* Get the file/directory entry name */
    s = index(path, '/');
    if ( NULL == s ) {
        len = strlen(path);
    } else {
        len = s - path;
    }
    if ( len > ADVFS_NAME_MAX ) {
        /* Invalid path name */
        return NULL;
    } else if ( len == 0 ) {
        return cur;
    }
    memcpy(name, path, len);
    name[len] = '\0';
    path += len;

    /* Resolve the entry */
    for ( i = 0; i < (ssize_t)cur->attr.size; i++ ) {
        e = _get_inode(advfs, _get_inode_in_dir(advfs, cur, i));
        if ( 0 == strcmp(name, e->name) ) {
            /* Found */
            if ( '\0' == *path ) {
                return e;
            } else if ( e->attr.type == ADVFS_DIR ) {
                return _path2inode_rec(advfs, e, path, create);
            } else {
                /* Invalid file type */
                return NULL;
            }
        }
    }

    /* Not found */
    if ( '\0' == *path && create ) {
        /* Create */
        if ( cur->attr.size >= ADVFS_MAX_CHILDREN ) {
            return NULL;
        }
        /* Search an unused inode */
        ret = _find_free_inode(advfs, &inode);
        if ( 0 != ret ) {
            return NULL;
        }
        ret = _set_inode_in_dir(advfs, cur, inode);
        if ( 0 != ret ) {
            return NULL;
        }
        e = _get_inode(advfs, inode);
        memset(e, 0, sizeof(advfs_inode_t));
        memcpy(e->name, name, len + 1);

        return e;
    }

    return NULL;
}
advfs_inode_t *
advfs_path2inode(advfs_t *advfs, const char *path, int create)
{
    advfs_inode_t *e;

    e = &advfs->superblock->root;
    return _path2inode_rec(advfs, e, path, create);
}

inode を inode 番号から inode 構造体を解決する処理は頻繁に実行するため, _get_inodes() 関数として以下の通り実装しました。

static advfs_inode_t *
_get_inodes(advfs_t *advfs, uint64_t nr)
{
    uint64_t b;
    advfs_inode_t *inodes;

    b = advfs->superblock->ptr_inode;
    inodes = (void *)advfs->superblock + ADVFS_BLOCK_SIZE * b;

    return &inodes[nr];
}

また,ディレクトリ inode inodenr 番目の子エントリを取得する _get_inode_in_dir() 関数を以下の通り実装しました。最初の15ブロックは inode->blocks 配列から参照でき,次の511ブロックは inode->blocks[15] の指し示すブロック番号のブロックに配列として保存されます。この配列の512番目は,次のブロック番号配列のブロックを指し示します。このように,ブロックを連鎖させることで,大きなファイル・ディレクトリに対応しています。

static uint64_t
_get_inode_in_dir(advfs_t *advfs, advfs_inode_t *inode, uint64_t nr)
{
    uint64_t b;
    uint64_t idx;
    uint64_t *block;
    uint64_t bidx;

    /* Get the block index for the specified index nr */
    bidx = nr / (ADVFS_BLOCK_SIZE / sizeof(uint64_t));
    idx = nr % (ADVFS_BLOCK_SIZE / sizeof(uint64_t));
    if ( bidx < ADVFS_INODE_BLOCKPTR - 1 ) {
        /* The block number is included in the inode structure */
        b = inode->blocks[bidx];
    } else {
        /* Resolve from the chain */
        b = inode->blocks[ADVFS_INODE_BLOCKPTR - 1];
        block = _get_block(advfs, b);
        bidx -= ADVFS_INODE_BLOCKPTR - 1;
        while ( bidx >= (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);
            bidx -= ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1;
        }
        b = block[bidx];
    }
    block = _get_block(advfs, b);

    /* Get the index to the inode number in the block */
    return block[idx];
}

advfs_path2inode() 関数では,ファイルが存在せずに第3引数の create フラグが指定されていている場合,ファイルまたはディレクトリを作成します。つまり inode を新たに割り当てます。この inode の割り当てのために, inode 領域から空き inode を見つける _find_free_inode() 関数を以下の通り実装しました。

static int
_find_free_inode(advfs_t *advfs, uint64_t *nr)
{
    ssize_t i;
    advfs_inode_t *inode;

    for ( i = 0; i < ADVFS_NUM_ENTRIES; i++ ) {
        inode = _get_inode(advfs, i);
        if ( inode->attr.type == ADVFS_UNUSED ) {
            *nr = i;
            return 0;
        }
    }

    return -1;
}

また,ディレクトリ dir に新しい inode inode を追加する関数 _set_inode_in_dir() を以下の通り実装します。

_set_inode_in_dir(advfs_t *advfs, advfs_inode_t *dir, uint64_t inode)
{
    uint64_t nb;
    uint64_t b;
    uint64_t idx;
    uint64_t *block;
    uint64_t bidx;
    int ret;

    if ( dir->attr.type != ADVFS_DIR ) {
        return -1;
    }

    /* Get the block index for the specified index nr */
    bidx = dir->attr.size / (ADVFS_BLOCK_SIZE / sizeof(uint64_t));
    nb = bidx + 1;
    idx = dir->attr.size % (ADVFS_BLOCK_SIZE / sizeof(uint64_t));

    /* Increase the block region */
    ret = _resize_block(advfs, dir, nb);
    if ( 0 != ret ) {
        return -1;
    }

    if ( bidx < ADVFS_INODE_BLOCKPTR - 1 ) {
        /* The block number is included in the inode structure */
        b = dir->blocks[bidx];
    } else {
        /* Resolve from the chain */
        b = dir->blocks[ADVFS_INODE_BLOCKPTR - 1];
        block = _get_block(advfs, b);
        bidx -= ADVFS_INODE_BLOCKPTR - 1;
        while ( bidx >= (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);
            bidx -= ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1;
        }
        b = block[bidx];
    }
    block = _get_block(advfs, b);
    block[idx] = inode;
    dir->attr.size++;

    return 0;
}

ディレクトリ(inode 番号を保存)やファイルの中身を保存するブロックを増減するための関数 _resize_block() を以下の通り実装しました。

static int
_resize_block(advfs_t *advfs, advfs_inode_t *e, uint64_t nb)
{
    if ( nb < e->attr.n_blocks ) {
        /* Shrink the file size */
        return _shrink_block(advfs, e, nb);
    } else if ( nb > e->attr.n_blocks ) {
        /* Increase the file size */
        return _increase_block(advfs, e, nb);
    } else {
        return 0;
    }
}

ブロック数の増減で処理が異なりますので,以下のようにブロックが増加する場合は _increase_block() 関数,ブロックが減少する場合は _shrink_block() 関数として実装します。

static int
_increase_block(advfs_t *advfs, advfs_inode_t *e, uint64_t nb)
{
    uint64_t b1;
    uint64_t b2;
    uint64_t pos;
    uint64_t *block;
    ssize_t i;
    int alloc;

    block = e->blocks;
    pos = 0;
    for ( i = 0; i < (ssize_t)nb; i++ ) {
        alloc = (i >= (ssize_t)e->attr.n_blocks) ? 1 : 0;

        b2 = 0;
        /* Next chain */
        if ( i == ADVFS_INODE_BLOCKPTR - 1 ) {
            if ( alloc ) {
                b2 = _alloc_block(advfs);
                if ( 0 == b2 ) {
                    return -1;
                }
                block[ADVFS_INODE_BLOCKPTR - 1] = b2;
            } else {
                b2 = block[ADVFS_INODE_BLOCKPTR - 1];
            }
            block = _get_block(advfs, b2);
            pos = 0;
        } else if ( pos == (ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1) ) {
            if ( alloc ) {
                b2 = _alloc_block(advfs);
                if ( 0 == b2 ) {
                    return -1;
                }
                block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1] = b2;
            } else {
                b2 = block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1];
            }
            block = _get_block(advfs, b2);
            pos = 0;
        }

        if ( alloc ) {
            /* Allocate */
            b1 = _alloc_block(advfs);
            if ( 0 == b1 ) {
                if ( 0 != b2 ) {
                    _free_block(advfs, b2);
                }
                return -1;
            }
            block[pos] = b1;
            e->attr.n_blocks++;
        }
        pos++;
    }

    return 0;
}
static int
_shrink_block(advfs_t *advfs, advfs_inode_t *e, uint64_t nb)
{
    uint64_t fb;
    uint64_t b;
    uint64_t pos;
    uint64_t *block;
    ssize_t i;
    int free;

    block = e->blocks;
    pos = 0;
    fb = 0;
    for ( i = 0; i < (ssize_t)nb; i++ ) {
        free = (i >= (ssize_t)nb) ? 1 : 0;

        /* Next chain */
        if ( i == ADVFS_INODE_BLOCKPTR - 1 ) {
            if ( 0 != fb ) {
                _free_block(advfs, fb);
            }
            b = block[ADVFS_INODE_BLOCKPTR - 1];
            block = _get_block(advfs, b);
            pos = 0;
            if ( free ) {
                fb = b;
            }
        } else if ( pos == (ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1) ) {
            if ( 0 != fb ) {
                _free_block(advfs, fb);
            }
            b = block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1];
            block = _get_block(advfs, b);
            pos = 0;
            if ( free ) {
                fb = b;
            }
        }

        if ( free ) {
            _free_block(advfs, block[pos]);
        }
        pos++;
    }
    if ( 0 != fb ) {
        _free_block(advfs, fb);
    }

    e->attr.n_blocks = nb;

    return 0;
}

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

今日も,昨日に引き続きブロックデバイスに対応できるようにリファクタリングを進めました。明日は,ファイルの読み書きやサイズ変更までリファクタリングを進めて,8日目と同等の機能を提供できるようにしたいと思います。