Rustで学ぶFUSE (5) 名前の変更と移動

# 名前の変更と移動
RustでFUSEを利用したファイルシステムを作成する記事の5回目です。
最新のソースコードは[こちら](https://github.com/narumatt/sqlitefs)にあります。

## 記事一覧
[Rustで学ぶFUSE (1) リードオンリーなファイルシステムの実装](https://qiita.com/narumatt/items/2dfc3aedf3aafd459e81)
[Rustで学ぶFUSE (2) ファイルの書き込み](https://qiita.com/narumatt/items/5b15dbabfeef32bfaf5c)
[Rustで学ぶFUSE (3) ファイル作成と削除](https://qiita.com/narumatt/items/ad2d9227710dbeb600be)
[Rustで学ぶFUSE (4) ディレクトリ作成と削除](https://qiita.com/narumatt/items/bec85dd5218a85a089b8)
**Rustで学ぶFUSE (5) 名前の変更と移動**

## 概要
作成/削除の関数は一通り実装したので、今回はファイル/ディレクトリの移動(`mv`)ができるようにします。
実装するべき関数はただ一つ、 `rename` です。

## DB関数
作成したDB関数は以下になります。

```rust
/// dentryを移動させる。移動先にファイル/ディレクトリがあって上書きする場合、元からあったファイル/ディレクトリのinode番号をSomeで返す。
/// 移動先に何もない場合はNoneを返す。
fn move_dentry(&mut self, parent: u32, name: &str, new_parent: u32, new_name: &str) -> Result<Option<u32>, Error>;
```

## rename
```rust
fn rename(
    &mut self,
    _req: &Request,
    parent: u64,
    name: &OsStr,
    newparent: u64,
    newname: &OsStr,
    reply: ReplyEmpty
);
```

引数 `parent` で親ディレクトリのinode番号、 `name` でファイルまたはディレクトリ名、
`newparent` で変更後の親ディレクトリのinode番号、 `newname` で変更後の名前が指定されるので、ファイルまたはディレクトリを移動し、名前を変更します。

C言語の `fuse_lowlevel` の説明によると、**変更先にファイルまたはディレクトリが存在する場合は自動で上書きしなければなりません。**
つまり、変更先のファイルまたはディレクトリを一旦削除してから移動する事になります。
こちらも削除処理と同様に、 `lookup count` が0でない場合は、0になるまでinodeの削除を遅延します。
上書き時に変更前と変更先のファイルタイプが異なる場合のチェックはカーネル側がやってくれるようで、 `rename(2)` を実行しても `rename` が呼ばれずにエラーになります。

ただし、変更前がディレクトリで、変更先のディレクトリを上書きする場合、**変更先のディレクトリは空であるかどうかはファイルシステムがチェックする必要があります。**
中身がある場合は、エラーとして `ENOTEMPTY` を返します。

また、`rename(2)` にはこの他に、以下の制約があります。

- 移動前と移動後のファイルが同じ(同じinode番号を指す)場合何もしない
- ディレクトリを自分自身のサブディレクトリに移動できない

ただし、この辺りはカーネルが処理してくれているようで、 `rename` 関数が呼ばれずにエラーになります。ファイルシステム側は気にする必要はなさそうです。

libfuseでは上書き禁止を指定したりできる `flag` が引数に指定されますが、fuse-rsには該当する引数がありません。

```rust
fn rename(
    &mut self,
    _req: &Request<'_>,
    parent: u64,
    name: &OsStr,
    newparent: u64,
    newname: &OsStr,
    reply: ReplyEmpty
) {
    let parent = parent as u32;
    let name = name.to_str().unwrap();
    let newparent = newparent as u32;
    let newname = newname.to_str().unwrap();
    // rename. 上書きされる場合は、上書き先のinode番号を取得
    let entry =  match self.db.move_dentry(parent, name, newparent, newname) {
        Ok(n) => n,
        Err(err) => match err.kind() {
            // ディレクトリが空でない場合エラー
            ErrorKind::FsNotEmpty {description} => {reply.error(ENOTEMPTY); debug!("{}", &description); return;},
            // ファイル -> ディレクトリの場合エラー(カーネルがチェックしているので発生しないはず)
            ErrorKind::FsIsDir{description} => {reply.error(EISDIR); debug!("{}", &description); return;},
            // ディレクトリ -> ファイルの場合エラー(カーネルがチェックしているので発生しないはず)
            ErrorKind::FsIsNotDir{description} => {reply.error(ENOTDIR); debug!("{}", &description); return;},
            _ => {reply.error(ENOENT); debug!("{}", err); return;},
        }
    };
    // 上書きがあった場合はentryに上書き先のinode番号が帰ってくるので、削除する必要がある場合は削除する
    if let Some(ino) = entry {
        let lc_list = self.lookup_count.lock().unwrap();
        if !lc_list.contains_key(&ino) {
            match self.db.delete_inode_if_noref(ino) {
                Ok(n) => n,
                Err(err) => {reply.error(ENOENT); debug!("{}", err); return;},
            };
        }
    }
    reply.ok();
}
```

## 実行結果

ファイルの名前変更をします。`touch.txt` を `touch3.txt` に変更します。

```shell
$ ls ~/mount/
hello.txt  testdir  touch.txt
$ mv ~/mount/touch.txt ~/mount/touch3.txt
$ ls ~/mount/
hello.txt  testdir  touch3.txt
```

実行ログは以下のようになります。

```text
# カーネルが変更前と変更先のチェックをしている（と思われる）
[2019-10-31T10:54:17Z DEBUG fuse::request] LOOKUP(442) parent 0x0000000000000001, name "touch.txt"
[2019-10-31T10:54:17Z DEBUG fuse::request] LOOKUP(444) parent 0x0000000000000001, name "touch3.txt"
[2019-10-31T10:54:17Z DEBUG fuse::request] LOOKUP(446) parent 0x0000000000000001, name "touch3.txt"
[2019-10-31T10:54:17Z DEBUG fuse::request] LOOKUP(448) parent 0x0000000000000001, name "touch3.txt"
# rename処理
[2019-10-31T10:54:17Z DEBUG fuse::request] RENAME(450) parent 0x0000000000000001, name "touch.txt", newparent 0x0000000000000001, newname "touch3.txt"
[2019-10-31T10:54:17Z DEBUG fuse::request] GETATTR(452) ino 0x0000000000000001
```

今度は `touch3.txt` を `testdir` に移動します。

```text
$ mv ~/mount/touch3.txt ~/mount/testdir
$ ls ~/mount/testdir
touch3.txt
```

実行ログは以下のようになります。

```text
# カーネルが変更前と変更先のチェックをしている（と思われる）
[2019-10-31T10:57:28Z DEBUG fuse::request] LOOKUP(500) parent 0x0000000000000001, name "touch3.txt"
[2019-10-31T10:57:28Z DEBUG fuse::request] LOOKUP(502) parent 0x0000000000000005, name "touch3.txt"
[2019-10-31T10:57:28Z DEBUG fuse::request] LOOKUP(504) parent 0x0000000000000005, name "touch3.txt"
[2019-10-31T10:57:28Z DEBUG fuse::request] LOOKUP(506) parent 0x0000000000000005, name "touch3.txt"
# rename処理
[2019-10-31T10:57:28Z DEBUG fuse::request] RENAME(508) parent 0x0000000000000001, name "touch3.txt", newparent 0x0000000000000005, newname "touch3.txt"
[2019-10-31T10:57:28Z DEBUG fuse::request] GETATTR(510) ino 0x0000000000000001
```

## まとめ
今回はファイル移動を実装しました。
基本的な操作ができるようになって、大分ファイルシステムらしくなってきました。
ここまでのコードは [github](https://github.com/narumatt/sqlitefs/tree/rename)に置いてあります。

ちなみに`nlink` をメタデータテーブルに数値で入れてましたが、この辺りで「扱いづらいな…」と思うようになり、最新版ではディレクトリエントリテーブルから集計して出すようにしています。

次回はシンボリックリンク、ハードリンクを実装していきます。