5
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 (4) ディレクトリ作成と削除

Last updated at Posted at 2020-02-15

ディレクトリの作成/削除

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 で名前の変更と移動ができるようにします。

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

5
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
5
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?