panda's tech note

Advent Calendar 2019: advfs

Day 6: ファイルの作成

今日はディレクトリツリーに対し,ファイルの作成を実装します。ファイルを作成する create API に加え,今回はファイルを作成するために touch コマンドを使うため,タイムスタンプを更新する utimens APIを実装します。

エントリ作成オプションの追加

昨日実装した advfs_path2ent() 関数で,ファイル作成オプションを追加した場合にエントリーを新たに作成するようにします。ただし,2段以上のディレクトリ・ファイルの作成には対応していません。実装は以下の通り,再帰的にディレクトリを辿り,当該エントリがあれば当該エントリを返します。パスの最後で指定されたエントリが存在せずに create フラグが有効なときはその名前のエントリーを作成し,返すようにします。

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

    if ( cur->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 < cur->u.dir.nent; i++ ) {
        e = &advfs->entries[cur->u.dir.children[i]];
        if ( 0 == strcmp(name, e->name) ) {
            /* Found */
            if ( e->type == ADVFS_DIR ) {
                return _path2ent_rec(advfs, e, path, create);
            } else if ( '\0' == *path ) {
                return e;
            } else {
                /* Invalid file type */
                return NULL;
            }
        }
    }

    /* Not found */
    if ( '\0' == *path && create ) {
        /* Create */
        if ( cur->u.dir.nent >= ADVFS_MAX_CHILDREN ) {
            return NULL;
        }
        /* Search unused inode */
        for ( i = 0; i < ADVFS_NUM_ENTRIES; i++ ) {
            if ( advfs->entries[i].type == ADVFS_UNUSED ) {
                break;
            }
        }
        if ( i >= ADVFS_NUM_ENTRIES ) {
            /* Not found */
            return NULL;
        }
        cur->u.dir.children[cur->u.dir.nent] = i;
        e = &advfs->entries[i];
        memset(e, 0, sizeof(advfs_entry_t));
        memcpy(e->name, name, len + 1);
        cur->u.dir.nent++;
        return e;
    }

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

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

create APIの実装

ファイルの作成は create APIを通じて行われます。第1引数にファイルパス,第2引数にモード(パーミッション),第3引数にはファイル情報を保持する struct fuse_file_info 構造体へのポインタが渡されます。まず, advfs_path2ent() 関数を create オプションなしで呼び出すことで,ファイルの存在を確認します。ファイルが存在した場合は -EEXIST を返します。ファイルが存在しない場合は advfs_path2ent() 関数を create オプションを付け呼び出し,エントリが作成できれば,ファイル属性を設定します。実装は以下の通りです。

int
advfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
    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_REGULAR_FILE;
    e->mode = mode;
    e->atime = tv.tv_sec;
    e->mtime = tv.tv_sec;
    e->ctime = tv.tv_sec;

    return 0;
}

utimens APIの実装

次に,ファイルのタイムスタンプを更新する utimens API を実装します。第1引数にはパス,第2引数には struct timespec 構造体の配列が入ります。第2引数の配列はそれぞれ,アクセス時刻および更新時刻を表します。これらの情報を用い,以下のように対象のエントリの atime および mtime を更新します。

int
advfs_utimens(const char *path, const struct timespec tv[2])
{
    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 ) {
        /* No entry found or non-directory entry */
        return -ENOENT;
    }
    if ( NULL != tv ) {
        e->atime = tv[0].tv_sec;
        e->mtime = tv[1].tv_sec;
    }

    return 0;
}

実行

いつものように実装したプログラムをビルド・実行するために

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

を実行し,

$ docker exec -it advfs_advfs_1 bash

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

2日目同様,ls -al /mnt を実行します。2日目とは違い,今回は最上位ディレクトリのタイムスタンプを起動時刻にしたため,下記のように . のタイムスタンプが起動時の時刻になっていることが確認できるかと思います。

root@95298be9d52a:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 2 root root    0 Dec  6 14:58 .
drwxr-xr-x 1 root root 4096 Dec  6 14:58 ..
root@95298be9d52a:/usr/src# touch /mnt/test
root@95298be9d52a:/usr/src# ls -al /mnt
total 4
drwxrwxrwx 3 root root    0 Dec  6 14:58 .
drwxr-xr-x 1 root root 4096 Dec  6 14:58 ..
-rw-r--r-- 1 root root    0 Dec  6 14:59 test

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

今日はファイルの作成を実装しました。明日はこのファイルの読み書きを実装します。