fuse
ユーザプロセスはファイルをオープンする毎に、マウントされたファイルシステムのinodeのコールバック関数がfileにバインドされ、そのコールバック関数をコールする事で、ユーザプロセスから掛かるファイルシステムを参照できるわけです。このinodeのコールバック関数は、それぞれのカーネル下で動作するファイルシステム処理の実装としてインプリメントされています。
fsueはFilesystem in Userspaceと言うことで、このカーネル下でのインプリメントをユーザプロセスとして実現すると言う物です。
下記はsshによる応用例でsshfsと言うコマンドでのサンプルです。127.0.0.1:/home/kitamuraのネットワークのリモートのファイルを/tmp/hoge0にマウントしています。
sshfsをstraceしてみると、/dev/fuseをオープンし、デバイス名:127.0.0.1:/home/kitamura、マウントポイント:/tmp/hoge0、ファイルシステムタイプ:fuse.sshfs、肝となるvoid __user *, dataに、/dev/fuseをオープンしたfile id=3を引数として、mountシステムコールをコールしています。
要はファイル操作のfile operationにこの/dev/fuseを結びつけます。/dev/fuseのfile operationの読み書きのコールバックは、ユーザプロセスから渡されたユーザ空間のメモリに対して、読み書きする通常の操作で、従ってこのfileとやり取りすることで、ファイルシステムの操作にかかる処理をインプリメントする事ができるわけです。
fuseでの処理は、通常ファイルの読み書きに過ぎません。従って、その権限はファイル処理の権限に順ずるということです。たぶん。(一般ユーザでsshfsしたファイルを、rootで参照できませんでした。)
fsueはFilesystem in Userspaceと言うことで、このカーネル下でのインプリメントをユーザプロセスとして実現すると言う物です。
下記はsshによる応用例でsshfsと言うコマンドでのサンプルです。127.0.0.1:/home/kitamuraのネットワークのリモートのファイルを/tmp/hoge0にマウントしています。
[root@localhost ~]# ls /home/kitamura/ Desktop Downloads Pictures Templates etc lkm script.php test Documents Mail Public Videos hoge rpmbuild setuid [root@localhost ~]# ls /tmp/hoge0
[root@localhost ~]# sshfs 127.0.0.1:/home/kitamura /tmp/hoge0 root@127.0.0.1's password: [root@localhost ~]# ls /tmp/hoge0 Desktop Downloads Pictures Templates etc lkm script.php test Documents Mail Public Videos hoge rpmbuild setuidnfsと何が違うのか?ということですが、fuseモジュールがインストールされていれば、sshfsにかかるモジュールは必要ありません。sshfsのマウント先のファイル操作にかかる処理は、ユーザプロセスとして普通のコマンドとして実現しているのです。従ってこの処理を自由に書き換える事で、通常のアプリを作成するようにファイルシステムを実装出来るという事です。
sshfsをstraceしてみると、/dev/fuseをオープンし、デバイス名:127.0.0.1:/home/kitamura、マウントポイント:/tmp/hoge0、ファイルシステムタイプ:fuse.sshfs、肝となるvoid __user *, dataに、/dev/fuseをオープンしたfile id=3を引数として、mountシステムコールをコールしています。
要はファイル操作のfile operationにこの/dev/fuseを結びつけます。/dev/fuseのfile operationの読み書きのコールバックは、ユーザプロセスから渡されたユーザ空間のメモリに対して、読み書きする通常の操作で、従ってこのfileとやり取りすることで、ファイルシステムの操作にかかる処理をインプリメントする事ができるわけです。
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) [root@localhost ~]# strace sshfs 127.0.0.1:/home/kitamura /tmp/hoge0 : open("/dev/fuse", O_RDWR|O_LARGEFILE) = 3 getgid32() = 0 getuid32() = 0 mount("127.0.0.1:/home/kitamura", "/tmp/hoge0", "fuse.sshfs", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=0,gr"...) = 0 geteuid32() = 0 lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=24576, ...}) = 0 lstat64("/etc/mtab", {st_mode=S_IFLNK|0777, st_size=12, ...}) = 0mountの引数fuse.sshfsの"."まで文字列(fuse)が、ファイルシステムタイプとなります。そのファイルシステムからfile_system_type(この場合fuse_fs_type)を取得し、fuse_fs_type->mointをコールする事で、マウント処理を行います。
static struct file_system_type fuse_fs_type = { .owner = THIS_MODULE, .name = "fuse", .fs_flags = FS_HAS_SUBTYPE, .mount = fuse_mount, .kill_sb = fuse_kill_sb_anon, }; const struct file_operations fuse_dev_operations = { .owner = THIS_MODULE, .llseek = no_llseek, .read = do_sync_read, .aio_read = fuse_dev_read, .splice_read = fuse_dev_splice_read, .write = do_sync_write, .aio_write = fuse_dev_write, .splice_write = fuse_dev_splice_write, .poll = fuse_dev_poll, .release = fuse_dev_release, .fasync = fuse_dev_fasync, };fuse_mount()はスーパブロック処理のコールバック関数fuse_fill_superを引数にして、mount_nodev()をコールします。raw_dataには/dev/fuseのfile idが設定されています。なお、mount_nodev()は、デバイスブロックを有しないブロックデバイスに対してコールされます。ブロックデバイス有する場合、mount_dev()がコールされ、ブロックサイズ等実際のデバイスから取得されます。CONFIG_BLKでコンパイルすると、/dev/fuseblkというのが作成され、/dev/sdb等のブロックデバイスに対しても対応できるようになっています。
static struct dentry *fuse_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *raw_data) { return mount_nodev(fs_type, flags, raw_data, fuse_fill_super); } struct dentry *mount_nodev(struct file_system_type *fs_type, int flags, void *data, int (*fill_super)(struct super_block *, void *, int)) { int error; struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL); if (IS_ERR(s)) return ERR_CAST(s); s->s_flags = flags; error = fill_super(s, data, flags & MS_SILENT ? 1 : 0); if (error) { deactivate_locked_super(s); return ERR_PTR(error); } s->s_flags |= MS_ACTIVE; return dget(s->s_root); } EXPORT_SYMBOL(mount_nodev);fuse_fill_super()はstruct super_block *sbに所定の設定をする事です。エッセンスと言うべき事は、file = fget(d.fd);で/dev/fuseのfile idとsbを結びつけると言う事です。fuse_dev_operationsのread/writeはdo_sync_read/do_sync_writeで、通常のファイル読み書きとなるわけで、ユーザプロセスとしては、ファイルシステム内操作としての結果を、このfile idに対して読み書きすればいいわけです。
static int fuse_fill_super(struct super_block *sb, void *data, int silent) { struct fuse_conn *fc; struct inode *root; struct fuse_mount_data d; struct file *file; struct dentry *root_dentry; struct fuse_req *init_req; int err; int is_bdev = sb->s_bdev != NULL; err = -EINVAL; if (sb->s_flags & MS_MANDLOCK) goto err; sb->s_flags &= ~MS_NOSEC; if (!parse_fuse_opt((char *) data, &d, is_bdev)) goto err; if (is_bdev) { #ifdef CONFIG_BLOCK err = -EINVAL; if (!sb_set_blocksize(sb, d.blksize)) goto err; #endif } else { sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; } sb->s_magic = FUSE_SUPER_MAGIC; sb->s_op = &fuse_super_operations; sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_export_op = &fuse_export_operations; file = fget(d.fd); err = -EINVAL; if (!file) goto err; if (file->f_op != &fuse_dev_operations) goto err_fput; fc = kmalloc(sizeof(*fc), GFP_KERNEL); err = -ENOMEM; if (!fc) goto err_fput; fuse_conn_init(fc); fc->dev = sb->s_dev; fc->sb = sb; err = fuse_bdi_init(fc, sb); if (err) goto err_put_conn; sb->s_bdi = &fc->bdi; if (sb->s_flags & MS_POSIXACL) fc->dont_mask = 1; sb->s_flags |= MS_POSIXACL; fc->release = fuse_free_conn; fc->flags = d.flags; fc->user_id = d.user_id; fc->group_id = d.group_id; fc->max_read = max_t(unsigned, 4096, d.max_read); sb->s_fs_info = fc; err = -ENOMEM; root = fuse_get_root_inode(sb, d.rootmode); if (!root) goto err_put_conn; root_dentry = d_alloc_root(root); if (!root_dentry) { iput(root); goto err_put_conn; } sb->s_d_op = &fuse_dentry_operations; init_req = fuse_request_alloc(); if (!init_req) goto err_put_root; if (is_bdev) { fc->destroy_req = fuse_request_alloc(); if (!fc->destroy_req) goto err_free_init_req; } mutex_lock(&fuse_mutex); err = -EINVAL; if (file->private_data) goto err_unlock; err = fuse_ctl_add_conn(fc); if (err) goto err_unlock; list_add_tail(&fc->entry, &fuse_conn_list); sb->s_root = root_dentry; fc->connected = 1; file->private_data = fuse_conn_get(fc); mutex_unlock(&fuse_mutex); fput(file); fuse_send_init(fc, init_req); return 0; err_unlock: mutex_unlock(&fuse_mutex); err_free_init_req: fuse_request_free(init_req); err_put_root: dput(root_dentry); err_put_conn: fuse_bdi_destroy(fc); fuse_conn_put(fc); err_fput: fput(file); err: return err; }
補足
linux.2.6.0のfile_system_typestruct file_system_type { const char *name; int fs_flags; struct super_block *(*get_sb) (struct file_system_type *, int, const char *, void *); void (*kill_sb) (struct super_block *); struct module *owner; struct file_system_type * next; struct list_head fs_supers; };linux.3.3.8のfile_system_type
struct file_system_type { const char *name; int fs_flags; struct dentry *(*mount) (struct file_system_type *, int, const char *, void *); void (*kill_sb) (struct super_block *); struct module *owner; struct file_system_type * next; struct hlist_head fs_supers; struct lock_class_key s_lock_key; struct lock_class_key s_umount_key; struct lock_class_key s_vfs_rename_key; struct lock_class_key i_lock_key; struct lock_class_key i_mutex_key; struct lock_class_key i_mutex_dir_key; };struct dentry *(*mount)()が追加されています。ファイルシステム毎に独自のマウント処理を実装できるという事で、この機能によりfuseが実現可能となったわけです。
fuseでの処理は、通常ファイルの読み書きに過ぎません。従って、その権限はファイル処理の権限に順ずるということです。たぶん。(一般ユーザでsshfsしたファイルを、rootで参照できませんでした。)