Rustで学ぶFUSE (4) ディレクトリ作成と削除

# ディレクトリの作成/削除
RustでFUSEを利用したファイルシステムを作成する記事の4回目です。
最新のソースコードは[こちら](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) ディレクトリ作成と削除**
[Rustで学ぶFUSE (5) 名前の変更と移動](https://qiita.com/narumatt/items/293a1c1f30b1aa9a8270)

## 概要
今まではルートディレクトリのみでファイル操作を行っていましたが、今回はディレクトリの作成/削除を実装して、サブディレクトリでいろいろできるようにします。

## 実装すべき関数
```rust
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関数

```rust
/// ディレクトリが空かチェックする
fn check_directory_is_empty(&self, inode: u32) -> Result<bool, Error>;
```

## mkdir
```rust
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` のコードは以下のようになります。

```rust
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
```rust
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`内部できちんとディレクトリも削除されるようにしてください。

```rust
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();
}
```

## 実行結果
ここまでの実行結果は以下のようになります。

```shell
# ディレクトリ作成
$ 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](https://github.com/narumatt/sqlitefs/tree/mkdir-rmdir)に置いてあります。
次回は `rename` で名前の変更と移動ができるようにします。

[Rustで学ぶFUSE (5) 名前の変更と移動](https://qiita.com/narumatt/items/293a1c1f30b1aa9a8270)