panda's tech note

Advent Calendar 2019: advfs

Day 8: ファイルの削除,ディレクトリの作成・削除

今日はファイルの削除とディレクトリの追加・削除を実装します。ファルの削除は unlink API,ディレクトリの作成は mkdir API,ディレクトリの削除は rmdir APIで行います。

unlink

unlink APIを実装するために,特定のエントリを削除する関数を以下の通り実装します。パス名からエントリを検索する advfs_path2ent() 関数の実装に似ていますが,エントリが見つかった場合に,ファイルまたは空ディレクトリであれば削除するようにします。

int
_remove_ent_rec(advfs_t *advfs, advfs_entry_t *cur, const char *path)
{
    advfs_entry_t *e;
    char name[ADVFS_NAME_MAX + 1];
    char *s;
    size_t len;
    int i;

    if ( cur->type != ADVFS_DIR ) {
        return -ENOENT;
    }

    /* Remove the head '/'s */
    if ( '/' != *path ) {
        return -ENOENT;
    }
    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 -ENOENT;
    } else if ( len == 0 ) {
        return -ENOENT;
    }
    memcpy(name, path, len);
    name[len] = '\0';
    path += len;

    /* Resolve the entry */
    for ( i = 0; i < cur->u.dir.nent; i++ ) {
        e = &advfs->entries[cur->u.dir.children[i]];
        if ( 0 == strcmp(name, e->name) ) {
            /* Found */
            if ( '\0' == *path ) {
                break;
            } else if ( e->type == ADVFS_DIR ) {
                return _remove_ent_rec(advfs, e, path);
            } else {
                /* Invalid file type */
                return -ENOENT;
            }
        }
    }
    if ( i == cur->u.dir.nent ) {
        return -ENOENT;
    }

    /* Free the entry */
    e = &advfs->entries[cur->u.dir.children[i]];
    if ( e->type == ADVFS_DIR && e->u.dir.nent > 0 ) {
        return -ENOTEMPTY;
    }
    e->type = ADVFS_UNUSED;

    /* Shift the child entries */
    cur->u.dir.nent--;
    for ( ; i < cur->u.dir.nent; i++ ) {
        cur->u.dir.children[i] = cur->u.dir.children[i + 1];
    }

    return 0;
}
int
advfs_remove_ent(advfs_t *advfs, const char *path)
{
    advfs_entry_t *root;

    root = &advfs->entries[advfs->root];
    return _remove_ent_rec(advfs, root, path);
}

この関数を用いて, unlink APIを実装します。

int
advfs_unlink(const char *path)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;

    /* 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 -ENOENT;
    }

    return advfs_remove_ent(advfs, path);
}

これでファイルの削除が実装できました。

mkdir

次にディレクトリの作成を実装します。と言っても,ファイルの作成とやることはほとんど変わりません。以下のようにファイルの作成と同様にエントリを作成し,そのエントリタイプを ADVFS_DIR とするだけです。

int
advfs_mkdir(const char *path, mode_t mode)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;
    struct timeval tv;

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

    gettimeofday(&tv, NULL);

    e = advfs_path2ent(advfs, path, 0);
    if ( NULL != e ) {
        /* Already exists */
        return -EEXIST;
    }

    e = advfs_path2ent(advfs, path, 1);
    if ( NULL == e ) {
        /* No entry found or non-directory entry */
        return -EACCES;
    }
    e->type = ADVFS_DIR;
    e->mode = mode;
    e->atime = tv.tv_sec;
    e->mtime = tv.tv_sec;
    e->ctime = tv.tv_sec;

    return 0;
}

なお,ディレクトリの検索にバグがあったので advfs_path2ent を一部修正しています。

rmdir

ディレクトリの削除も unlink とほぼ同じです。一箇所違うところは,対象がディレクトリでなかった場合のエラーとして -ENOTDIR を返している点だけです。

int
advfs_rmdir(const char *path)
{
    struct fuse_context *ctx;
    advfs_t *advfs;
    advfs_entry_t *e;

    /* 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_DIR ) {
        return -ENOTDIR;
    }

    return advfs_remove_ent(advfs, path);
}

実行

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

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

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

を実行し,

$ docker exec -it advfs_advfs_1 bash

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

以下のようにディレクトリの追加,ファイルの作成・書き込み,読み込み,ファイル削除,ディレクトリの削除が行えていることが確認できます。

root@3eb65fb8378b:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 2 root root    0 Dec  8 15:14 .
drwxr-xr-x 1 root root 4096 Dec  8 15:14 ..
root@3eb65fb8378b:/usr/src# mkdir /mnt/foo
root@3eb65fb8378b:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 3 root root    0 Dec  8 15:14 .
drwxr-xr-x 1 root root 4096 Dec  8 15:14 ..
drwxr-xr-x 2 root root    0 Dec  8 15:15 foo
root@3eb65fb8378b:/usr/src# ls -al /mnt/foo
total 0
drwxr-xr-x 2 root root 0 Dec  8 15:15 .
drwxrwxrwx 3 root root 0 Dec  8 15:14 ..
root@3eb65fb8378b:/usr/src# echo "testing" > /mnt/foo/test
root@3eb65fb8378b:/usr/src# ls -al /mnt/foo
total 1
drwxr-xr-x 3 root root 0 Dec  8 15:15 .
drwxrwxrwx 3 root root 0 Dec  8 15:14 ..
-rw-r--r-- 1 root root 8 Dec  8 15:15 test
root@3eb65fb8378b:/usr/src# cat /mnt/foo/test
testing
root@3eb65fb8378b:/usr/src# rmdir /mnt/foo
rmdir: failed to remove '/mnt/foo': Directory not empty
root@3eb65fb8378b:/usr/src# rm /mnt/foo/test
root@3eb65fb8378b:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 3 root root    0 Dec  8 15:14 .
drwxr-xr-x 1 root root 4096 Dec  8 15:14 ..
drwxr-xr-x 2 root root    0 Dec  8 15:15 foo
root@3eb65fb8378b:/usr/src# rmdir /mnt/foo
root@3eb65fb8378b:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 2 root root    0 Dec  8 15:14 .
drwxr-xr-x 1 root root 4096 Dec  8 15:14 ..

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

今日はファイルの削除,ディレクトリの追加・削除を実装しました。明日は,これまでの実装のリファクタリングをしながら今後の方針を決めようと思います。