2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-08

名前の変更と移動

RustでFUSEを利用したファイルシステムを作成する記事の5回目です。
最新のソースコードはこちらにあります。

記事一覧

Rustで学ぶFUSE (1) リードオンリーなファイルシステムの実装
Rustで学ぶFUSE (2) ファイルの書き込み
Rustで学ぶFUSE (3) ファイル作成と削除
Rustで学ぶFUSE (4) ディレクトリ作成と削除
Rustで学ぶFUSE (5) 名前の変更と移動

概要

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

DB関数

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

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

rename

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には該当する引数がありません。

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.txttouch3.txt に変更します。

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

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

# カーネルが変更前と変更先のチェックをしている(と思われる)
[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.txttestdir に移動します。

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

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

# カーネルが変更前と変更先のチェックをしている(と思われる)
[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に置いてあります。

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

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?