FUSEは,カーネルを変更することなく ユーザプログラムで新たなファイルシステムを作るためのLinuxの仕組みである
FUSEライブラリを用いてプログラムを作り,
そのプログラムを実行することでOSに新たなファイルシステムを追加することができる.
例えば,後の例題のプログラム simplefs.c
を:
$ cc -lfuse simplefs.c -o simplefs
のようにコンパイルし,
あらかじめ空のディレクトリ/mnt/a
を作っておいてから
$ ./simplefs /mnt/a
と実行すると,このプログラムで定義する新たなファイルシステムが
/mnt/a
にマウント(接続)
され,このファイルシステム内のファイルが/mnt/a/ファイル名
として読み書きできるようになる. simplefs
コマンドは,
ファイルシステムがマウントされている間,ずっと実行しており,例えば
$ cp simple.c /mnt/a/simple.c
のようにこのファイルシステムの中にファイルを作ったり書き込んだりして
open, writeなどのシステムコールが実行されると,そのシステムコールの内容が
カーネル内のモジュールを通じてsimplefs
プログラムに通知される.
simplefs
から処理結果を返すと,逆の経路を辿ってcp
などの
プログラムへ返される.
#define FUSE_USE_VERSION 26
#define _FILE_OFFSET_BITS 64
#include <fuse.h>
/*
... open, read, write などの操作を実行する関数定義を
ここに書く ...
*/
static struct fuse_operations myfs_oper = {
.open = myfs_open,
.read = myfs_read,
.write = myfs_write,
.mknod = myfs_mknod,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &myfs_oper, NULL);
}
ファイルシステムの機能を実現するための処理関数を作り,
それらの関数へのポインタを struct fuse_operation
型の構造体の中に
入れて,fuse_main
関数に渡してやる.
カーネルとやりとりをして,システムコールに相当する関数を呼び出したり,
結果をカーネルに返す作業はfuse_main
が行なってくれる.
struct fuse_operation
構造体に定義すべき操作(関数へのポインタ)
の種類としては,以下のようなものが定義されている.
(詳細は/usr/local/include/fuse/fuse.hを参照.)
struct fuse_operations {
int (*getattr) (const char *, struct stat *);
int (*mknod) (const char *, mode_t, dev_t);
int (*mkdir) (const char *, mode_t);
int (*unlink) (const char *);
int (*rmdir) (const char *);
int (*rename) (const char *, const char *);
int (*chmod) (const char *, mode_t);
int (*chown) (const char *, uid_t, gid_t);
int (*truncate) (const char *, off_t);
int (*open) (const char *, struct fuse_file_info *);
int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
int (*write) (const char *, const char *, size_t, off_t,
struct fuse_file_info *);
int (*opendir) (const char *, struct fuse_file_info *);
int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
struct fuse_file_info *);
int (*access) (const char *, int);
int (*utimens) (const char *, const struct timespec tv[2]);
/* 以下省略 */
};
これら全てを定義する必要はないが,定義しないとファイルシステムの機能が限定される. 以下の例(simplefs)では,getattr(ファイルが存在するか,普通のファイルかディレクトリか などの情報を得る), readdir(ディレクトリ内のファイル一覧を得る), mknod(ディレクトリでない 普通のファイルを作る), open(既に存在するファイルを開く), read, write のみを定義している.
このファイルシステムでは,
それぞれの関数は,対応するシステムコールに応じて,成功すると0を返すもの (getattr, readdir, openなど)や,バイト数を返すもの(read, writeなど)がある. 負の値を返すとエラーの意味になり,その場合は返す値がエラーコードを表す. たとえば,-ENOENTを返すと「No such file or directory」のエラーになる. (エラーコードの詳細は/usr/include/asm/errno.hやシステムコールのマニュアルを参照.)
#define FUSE_USE_VERSION 26
#define _FILE_OFFSET_BITS 64
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#define INITIAL_SIZE 1024
/* 1個の普通のファイルを表す構造体 */
struct file_metadata {
int mode; // ファイルの種類・読み書きのモード
int nlink; // ファイルのリンクカウント
int length; // ファイル内のデータのバイト数
int capacity; // data配列の大きさ
char *data; // データを保持する配列へのポインタ
};
/* ディレクトリを表すリストの要素 */
struct directory_entry {
char *name; // ファイル名
struct file_metadata *file; // ファイルの本体
struct directory_entry *next; // 次の要素へのポインタ
};
struct directory_entry *root = NULL; // このFSのルートディレクトリ
// ディレクトリ内のファイルを探す補助関数
static struct directory_entry *search_file(const char *name)
{
struct directory_entry *p;
for (p = root; p != NULL; p = p->next) {
if (strcmp(p->name, name + 1) == 0) {
return p;
}
}
return NULL;
}
static int simple_getattr(const char *path, struct stat *stbuf)
{
int res = 0;
struct directory_entry *p;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (p = search_file(path)) {
stbuf->st_mode = p->file->mode;
stbuf->st_nlink = p->file->nlink;
stbuf->st_size = p->file->length;
} else
res = -ENOENT;
return res;
}
static int simple_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
struct directory_entry *p;
if (strcmp(path, "/") != 0)
return -ENOENT;
filler(buf, ".", NULL, 0); // fillerを呼ぶと,返す一覧情報に1つ要素を追加する.
filler(buf, "..", NULL, 0);
for (p = root; p != NULL; p = p->next) {
if (filler(buf, p->name, NULL, 0)) {
break;
}
}
return 0;
}
static int simple_open(const char *path, struct fuse_file_info *fi)
{
struct directory_entry *p;
if ((p = search_file(path)) == 0) {
return -ENOENT;
}
fi->fh = (long)p->file;
return 0;
}
static int simple_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
struct file_metadata *f = (struct file_metadata *)(long)fi->fh;
if (offset > f->length) {
return 0;
}
if (offset + size > f->length) {
size = f->length - offset;
}
memcpy(buf, f->data + offset, size);
return size;
}
// 十分に大きなdata配列を確保する補助関数.data配列は2倍ずつ大きくしていく.
static int assure_size(struct file_metadata *f, int size)
{
int new_capacity = f->capacity;
char *new_data;
while (new_capacity < size) {
new_capacity *= 2;
}
new_data = calloc(new_capacity, 1);
if (new_data == NULL) {
return -1;
}
memcpy(new_data, f->data, f->length);
free(f->data);
f->data = new_data;
f->capacity = new_capacity;
return 0;
}
static int simple_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
struct file_metadata *f = (struct file_metadata *)(long)fi->fh;
int block_num, begin, end;
char *block;
if (assure_size(f, offset + size) < 0) {
return -ENOSPC;
}
if (offset + size > f->length) {
f->length = offset + size;
}
memcpy(f->data + offset, buf, size);
return size;
}
static int simple_mknod(const char *path, mode_t mode, dev_t device)
{
struct directory_entry *p;
if (search_file(path)) {
return -EEXIST;
}
p = malloc(sizeof(struct directory_entry));
if (p == NULL) {
return -ENOSPC;
}
p->file = calloc(sizeof(struct file_metadata), 1);
if (p->file == NULL) {
return -ENOSPC;
}
p->file->mode = mode;
p->file->nlink = 1;
p->file->length = 0;
p->file->capacity = INITIAL_SIZE;
p->file->data = calloc(INITIAL_SIZE, 1);
p->name = strdup(path + 1);
if (p->name == NULL) {
return -ENOSPC;
}
p->next = root;
root = p;
return 0;
}
static struct fuse_operations simple_oper = {
.getattr = simple_getattr,
.readdir = simple_readdir,
.open = simple_open,
.read = simple_read,
.write = simple_write,
.mknod = simple_mknod,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &simple_oper, NULL);
}
$ tar xvfz fuse-2.7.4.tar.gz
$ cd fuse-2.7.4
$ ./configure
$ make
# make install