FUSEをつかってみる
http://sourceforge.net/apps/mediawiki/fuse/index.php?title=Hello_Worldを丸パクリ。
Linuxで、FUSE(Filesystem in userspace)を使ってHello worldなプログラムを書いてみる。
書かなきゃいけないもの
- ファイル情報を取得する getattr()
- ディレクトリ内のファイルを列挙する readdir()
- ファイルを開く open()
- ファイルの内容を読み込む read()
- FUSEでのファイル操作を行う関数をまとめた構造体
- エントリポイント main()
最低限これだけ。
ソースコード
ごちゃごちゃ説明するよりもソースコードを見た方がわかりやすいはず。
myfuse.c:
#define FUSE_USE_VERSION 28 #include <stdlib.h> #include <fuse.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> static const char *hello_str = "Hello Fuse World!\n"; static const char *hello_path = "/hello"; /* getattr() 関数 pathで指定されたファイルについての情報を格納したstat構造体を返す。 fuse_operations構造体に渡すために、static関数として宣言される必要がある。 */ static int hello_getattr(const char *path, struct stat *stbuf) { int res = 0; /* 返値 */ /* stat構造体をクリア */ memset(stbuf, 0, sizeof(struct stat)); /* どのファイルについての情報を返すべきかをチェックする。 今回は、1つの通常ファイルと1つのディレクトリしかないので、 次のように簡単に書ける。 以下、ディレクトリの情報が要求された場合の処理。*/ if(strcmp(path,"/") == 0) { /* st_modeメンバにファイルの属性を格納する。 S_IFDIR は 0040000 という定数値で、これはディレクトリであることを意味する。 これに通常のディレクトリのパーミッション(755)を加えたものをセットする。 (8進数の値として格納することに注意。×755 ○0755 ) */ stbuf->st_mode = S_IFDIR | 0755; /* st_nlinkメンバにハードリンクの数を格納する。このディレクトリには サブディレクトリが存在しないので、リンク数として2をセットする。 */ stbuf->st_nlink = 2; } /* /hello ファイルへのリクエストだった場合の処理 */ else if(strcmp(path, hello_path) == 0){ /* st_modeメンバにファイルの属性を格納する。 通常のファイルを意味する S_IFREG 定数に、ファイルパーミッション 0444 を 加えたものをセットする。*/ stbuf->st_mode = S_IFREG | 0444; /* st_nlinkメンバにハードリンクの数を格納する。他にファイルは存在しないので 親ディレクトリからしかリンクされないため、1をセットする。 */ stbuf->st_nlink = 1; /* st_size メンバにファイルサイズを格納する。今回は文字列の長さと同じ。 */ stbuf->st_size = strlen(hello_str); } /* それ以外の場合。他にファイルは存在しないので、ENOENT を返してその旨を通知する。 ENOENT は error.h で宣言されており、"pathで指定されたファイルは存在しないか、 path文字列が空っぽです"というエラーを意味する。 */ else { res = -ENOENT; } return res; } /* readdir()関数 ディレクトリ内のファイルなどに関する情報を取得する。 今回は非常にシンプルな例なので、path, buf, fillerだけしか扱わない。 pathは取得対象となるファイルパスで、 bufは情報を格納するバッファ、 fillerは fuse_fill_dir_t()関数で、ディレクトリに情報を追加する。 offsetとfiはここでは特に重要ではないので無視する。 */ static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { /* offset, fi はここでは使わない(警告対策) */ (void) offset; (void) fi; /* 今回は"/"しかディレクトリが存在しないため、"/"に対する問い合わせかどうかを チェックする。そうでない場合には、"ディレクトリが存在しない"旨を意味する エラー値を返す。 */ if ( strcmp(path, "/") != 0 ) { return -ENOENT; } /* "." と ".." についての情報をfiller()関数を使って設定する。 */ filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); /* 今回は "hello" というファイルただ1つを提供するが、パスは "/hello" ではなく "hello" なので、先頭の "/" を除外して同様に設定する。 */ filler(buf, hello_path+1, NULL, 0); return 0; } /* open()関数 この関数では、fuse_file_info構造体で指定されたフラグに基づいて、 ユーザがファイルを開く権限を持っているかをチェックする。 */ static int hello_open(const char *path, struct fuse_file_info *fi) { /* fi はここでは使わない(警告対策) */ (void) fi; /* "/hello" 以外のファイルに対する要求が出された場合には、 "そのようなファイルは存在しない"旨を返す。 */ if (strcmp(path, hello_path) != 0 ) { return -ENOENT; } /* 読み込み以外の要求が出された場合には、その権限が与えられていない旨を返す。 */ if ( (fi->flags & 3) != O_RDONLY ) { return -EACCES; } /* それ以外の場合には、ユーザにファイルのオープンを許可する。 */ return 0; } /* read()関数 この関数は、ファイルの中身をユーザに提供する際に呼ばれる。 pathはファイルのパス、 bufはユーザに返すデータのを格納したバッファ、 sizeはバッファのサイズで、どれだけのデータをファイルから読み込むべきかを示し、 offsetは読み込み開始場所を示すオフセット、 fiはファイルアクセスについての追加的な情報を格納している。 この関数はopen()関数の後にしか呼ばれない。今回の例では "/hello" しかファイルは 存在せず、そのファイルは読み込み専用なので、fiに関しては特に使用しない。 */ static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { /* fi はここでは使わない(警告対策) */ (void) fi; /* len は読み込んだバイト数を表す */ size_t len; /* "/hello" 以外のファイルに対する要求が出された場合には、 "そのようなファイルは存在しない"旨を返す。 */ if (strcmp(path, hello_path) != 0 ) { return -ENOENT; } /* "/hello" のファイルの長さは文字列の長さ */ len = strlen(hello_str); /* オフセットがファイルサイズを超えていないかをチェック */ if ( offset < len ) { /* ファイルの終わり以降を含めて要求されている場合は サイズとしてファイルの終わりまでの長さを設定する */ if ( (offset + size) > len ) { size = (len - offset); } /* バッファにコピーする */ memcpy(buf, hello_str + offset, size); } /* オフセットが超えている場合には何も読み込まないで返す */ else { size = 0; } return size; } /* FUSEファイル操作構造体に関数ポインタを設定する */ static struct fuse_operations hello_oper = { .getattr = hello_getattr, .readdir = hello_readdir, .open = hello_open, .read = hello_read, }; /* エントリポイント。fuseのメイン関数 fuse_main() を呼ぶだけ */ int main(int argc, char *argv[]) { return fuse_main(argc, argv, &hello_oper, NULL); }
ここで、
(void) offset;
という一見ムダな行は、コンパイラの警告への対策。これがないと、「使われてない引数があるぞ!」と警告を出すコンパイラがあるようだ。
今回のFedora12+gcc4.3の環境では出なかったけど。
コンパイル方法
gcc -Wall `pkg-config fuse --cflags --libs` myfuse.c -o myfuse
でおk、と書いてあるが、あらかじめ次のようにして確認しておくと良い。
$ pkg-config fuse --cflags --libs Package fuse was not found in the pkg-config search path. Perhaps you should add the directory containing `fuse.pc' to the PKG_CONFIG_PATH environment variable No package 'fuse' found
怒られてしまった。fuseをソースから/usr/local以下にインストールした関係からか、fuse.pcが見つからないようだ。
というわけで、fuse.pcを探す。
$ find / -name 'fuse.pc' 2>/dev/null /usr/src/fuse/fuse-2.8.4/fuse.pc /usr/local/lib/pkgconfig/fuse.pc
/usr/local/lib/pkgconfig にあるようだ。エラーメッセージで、「PKG_CONFIG_PATH環境変数にセットしろ」と書いてあるので、おとなしく従ってみる。
$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config fuse --cflags --libs -D_FILE_OFFSET_BITS=64 -I/usr/local/include/fuse -pthread -L/usr/local/lib -lfuse -lrt -ldl
動いた。これを踏まえ、コンパイルし直す。「``」で囲っている部分でこの環境変数が必要となるので、次のようにして実行してやればよい。
$ gcc -Wall `PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config fuse --cflags --libs` myfuse.c -o myfuse
あるいは、こうだ。
$ PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" $ export PKG_CONFIG_PATH $ gcc -Wall `pkg-config fuse --cflags --libs` myfuse.c -o myfuse
実行
./test_dir というディレクトリを作り、そこに自作したmyfuseをマウントしてみる。
$ mkdir test_dir $ ./myfuse ./test_dir $ ls ./test_dir/ hello $ ls -l ./test_dir/ 合計 0 -r--r--r--. 1 root root 18 1970-01-01 09:00 hello $ cat ./test_dir/hello Hello Fuse World! $ cat ./test_dir/no_such_file_or.directory cat: ./test_dir/no_such_file_or.directory: そのようなファイルやディレクトリはありません
あとかたづけ
このままだと myfuse のプロセスが残ってしまうので、終了させてやる必要がある。
きみはmyfuseのプロセスを殺しても良いし、丁寧にアンマウントすることもできる。
プロセスを探してkillする
$ kill `pidof myfuse` $ ls test_dir $ cat ./test_dir/hello cat: ./test_dir/hello: そのようなファイルやディレクトリはありません
あるいは、丁寧に(管理者権限で)アンマウントする
$ sudo umount ./test_dir
次のように、fusermountコマンドを使えば管理者権限は不要らしい。※ユーザがマウントした場合
$ fusermount -u ./test_dir