ディレクトリの作成/削除
RustでFUSEを利用したファイルシステムを作成する記事の4回目です。
最新のソースコードはこちらにあります。
記事一覧
Rustで学ぶFUSE (1) リードオンリーなファイルシステムの実装
Rustで学ぶFUSE (2) ファイルの書き込み
Rustで学ぶFUSE (3) ファイル作成と削除
Rustで学ぶFUSE (4) ディレクトリ作成と削除
Rustで学ぶFUSE (5) 名前の変更と移動
概要
今まではルートディレクトリのみでファイル操作を行っていましたが、今回はディレクトリの作成/削除を実装して、サブディレクトリでいろいろできるようにします。
実装すべき関数
fn mkdir(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, reply: ReplyEntry) {
...
}
fn rmdir(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEmpty) {
...
}
追加したDB関数
/// ディレクトリが空かチェックする
fn check_directory_is_empty(&self, inode: u32) -> Result<bool, Error>;
mkdir
fn mkdir(
&mut self,
req: &Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
reply: ReplyEntry
);
引数の parent
で親ディレクトリのinode番号、 name
で作成するディレクトリ名、 mode
でアクセス権が指定されるので、ディレクトリを作成します。
成功した場合、作成したディレクトリのメタデータをreply
に入れて返します。
作成したユーザ、グループは、引数の req
から req.uid()
req.gid()
で取得できます。
ただし、マウントオプションで –o grpid
または –o bsdgroups
が指定されている場合や、親ディレクトリにSGIDが設定されている場合は、親ディレクトリと同じグループを設定しないといけません。
また、親ディレクトリにSUIDが設定されていても、SUIDはディレクトリには関係ないので子ディレクトリは無視します。親ディレクトリにSGID、 スティッキービットが設定されている場合、子ディレクトリにも属性を引き継がないといけません。
この辺りは処理系定義なので、Linux以外のシステムで、常にディレクトリ作成時にはoffにするものもあるようです。
下記の例では、 libc::S_ISGID
(SGID) と libc::S_ISVTX
(スティッキービット) で親ディレクトリのパーミッションのANDを取ってチェックして、ONになっている場合は子ディレクトリのパーミッションに追加しています。
既に同名のディレクトリやファイルがあるかどうか、はカーネル側でチェックしてくれているようで、関数が呼ばれる前にエラーになるので対策していません。
mkdir
のコードは以下のようになります。
fn mkdir(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, reply: ReplyEntry) {
let now = SystemTime::now();
// 初期メタデータ
let mut attr = DBFileAttr {
ino: 0,
size: 0,
blocks: 0,
atime: now,
mtime: now,
ctime: now,
crtime: now,
kind: FileType::Directory,
perm: mode as u16,
nlink: 0,
uid: req.uid(),
gid: req.gid(),
rdev: 0,
flags: 0
};
// 親ディレクトリのメタデータ取得
let parent_attr = match self.db.get_inode(parent) {
Ok(n) => match n {
Some(n) => n,
None => {reply.error(ENOENT); return;}
},
Err(err) => {reply.error(ENOENT); debug!("{}", err); return;}
};
// 親のSGIDがONの場合、子にSGIDを追加
if parent_attr.perm & S_ISGID as u16 > 0 {
attr.perm |= S_ISGID as u16;
attr.gid = parent_attr.gid;
}
// 親のスティッキービットがONの場合、子にスティッキービットを追加
if parent_attr.perm & S_ISVTX as u16 > 0 {
attr.perm |= S_ISVTX as u16;
}
// ディレクトリを作成してinode番号を取得
let ino = match self.db.add_inode(parent as u32, name.to_str().unwrap(), &attr) {
Ok(n) => n,
Err(err) => {reply.error(ENOENT); debug!("{}", err); return;}
};
// inode番号を入れてメタデータを返却
attr.ino = ino;
reply.entry(&ONE_SEC, &attr.get_file_attr(), 0);
// lookup countを増やす
let mut lc_list = self.lookup_count.lock().unwrap();
let lc = lc_list.entry(ino).or_insert(0);
*lc += 1;
}
rmdir
fn rmdir(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
reply: ReplyEmpty
);
引数の parent
で親ディレクトリのinode番号が、 name
でディレクトリ名が指定されるので、ディレクトリを削除します。
ディレクトリ内に何かがある場合は削除できません。
カーネル側で確認はしてくれないようなので、ファイルシステム側でチェックを行い、ディレクトリが空ではない( .
と ..
以外のエントリがある) 場合はエラーを返します。
rmdir(2)
のmanページによると、 ENOTEMPTY
または EEXIST
を返します。Linuxファイルシステムでは ENOTEMPTY
がメジャーのようです。
unlink
と同様に、 lookup count
が0でない場合、0になるタイミングまでinodeの削除を遅延します。
今回のプログラムでは、 forget
等の内部でファイルとディレクトリの区別をしていないので現状の実装のまま進めますが、区別する場合はforget
内部できちんとディレクトリも削除されるようにしてください。
fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
let parent = parent as u32;
let name = name.to_str().unwrap();
let attr = match self.db.lookup(parent, name) {
Ok(n) => n,
Err(err) => {reply.error(ENOENT); debug!("{}", err); return;}
};
// ディレクトリが空かどうかチェック
let empty = match self.db.check_directory_is_empty(attr.ino){
Ok(n) => n,
Err(err) => {reply.error(ENOENT); debug!("{}", err); return;}
};
if !empty {
reply.error(ENOTEMPTY);
return;
}
// dentry削除
let ino = match self.db.delete_dentry(parent, name) {
Ok(n) => n,
Err(err) => {reply.error(ENOENT); debug!("{}", err); return;}
};
// lookup countの処理
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();
}
実行結果
ここまでの実行結果は以下のようになります。
# ディレクトリ作成
$ mkdir ~/mount/testdir/
$ echo "test" > ~/mount/testdir/test.txt
# 中身のあるディレクトリは削除できないことの確認
$ rmdir ~/mount/testdir
rmdir: failed to remove '/home/jiro/mount/testdir': Directory not empty
$ rm ~/mount/testdir/test.txt
# ディレクトリ削除
$ rmdir ~/mount/testdir
FUSE関数の呼び出しログは以下のようになります。
// mkdir ~/mount/testdir/
[2019-10-31T06:15:19Z DEBUG fuse::request] GETATTR(146) ino 0x0000000000000001
[2019-10-31T06:15:32Z DEBUG fuse::request] LOOKUP(148) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:15:32Z DEBUG fuse::request] MKDIR(150) parent 0x0000000000000001, name "testdir", mode 0o775
[2019-10-31T06:15:32Z DEBUG fuse::request] GETATTR(152) ino 0x0000000000000001
// echo "test" > ~/mount/testdir/test.txt
[2019-10-31T06:15:56Z DEBUG fuse::request] LOOKUP(154) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:15:56Z DEBUG fuse::request] LOOKUP(156) parent 0x0000000000000004, name "test.txt"
[2019-10-31T06:15:56Z DEBUG fuse::request] CREATE(158) parent 0x0000000000000004, name "test.txt", mode 0o100664, flags 0x8241
[2019-10-31T06:15:56Z DEBUG fuse::request] WRITE(160) ino 0x0000000000000005, fh 0, offset 0, size 5, flags 0x0
[2019-10-31T06:15:56Z DEBUG fuse::request] RELEASE(162) ino 0x0000000000000005, fh 0, flags 0x8001, release flags 0x0, lock owner 0
[2019-10-31T06:15:56Z DEBUG fuse::request] GETATTR(164) ino 0x0000000000000001
// rmdir(1回目)
[2019-10-31T06:16:07Z DEBUG fuse::request] LOOKUP(166) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:16:07Z DEBUG fuse::request] RMDIR(168) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:16:07Z DEBUG fuse::request] GETATTR(170) ino 0x0000000000000001
// rm test.txt
[2019-10-31T06:16:24Z DEBUG fuse::request] LOOKUP(174) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:16:24Z DEBUG fuse::request] LOOKUP(176) parent 0x0000000000000004, name "test.txt"
[2019-10-31T06:16:24Z DEBUG fuse::request] ACCESS(178) ino 0x0000000000000005, mask 0o002
[2019-10-31T06:16:24Z DEBUG fuse::request] UNLINK(180) parent 0x0000000000000004, name "test.txt"
[2019-10-31T06:16:24Z DEBUG fuse::request] FORGET(182) ino 0x0000000000000005, nlookup 2
[2019-10-31T06:16:24Z DEBUG fuse::request] GETATTR(184) ino 0x0000000000000001
// rmdir(2回目)
[2019-10-31T06:16:34Z DEBUG fuse::request] LOOKUP(186) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:16:34Z DEBUG fuse::request] RMDIR(188) parent 0x0000000000000001, name "testdir"
[2019-10-31T06:16:34Z DEBUG fuse::request] FORGET(190) ino 0x0000000000000004, nlookup 5
[2019-10-31T06:16:34Z DEBUG fuse::request] GETATTR(192) ino 0x0000000000000001
まとめ
これでディレクトリの作成と削除ができるようになりました。
ファイル、ディレクトリの作成と削除ができるようになったので、大分自由度が増えてきました。
ここまでのコードは githubに置いてあります。
次回は rename
で名前の変更と移動ができるようにします。