名前の変更と移動
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.txt
を touch3.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.txt
を testdir
に移動します。
$ 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
をメタデータテーブルに数値で入れてましたが、この辺りで「扱いづらいな…」と思うようになり、最新版ではディレクトリエントリテーブルから集計して出すようにしています。
次回はシンボリックリンク、ハードリンクを実装していきます。