Linux FUSE(用户态文件系统)的使用:用libfuse创建FUSE文件系统

作者:李佶澳  更新时间:

  技巧    linux    刷新

目录

说明

FUSE是Linux Kernel的特性之一:一个用户态文件系统框架,a userspace filesystem framework。 形象的说就是可以在用户态运行一个程序,这个程序暴露出一个FUSE文件系统,对这个文件系统进行的读写操作都会被转给用户态的程序处理。

FUSE由内核模块fuse.ko和用户空间的动态链接库libfuse.*组成,如果要开发使用fuse的用户态程序,需要安装fuse-devel

yum install fuse-devel

资料

Kernel中有两份关于FUSE的文档:

  1. kernel/Documentation/filesystems/fuse.txt

  2. kernel/Documentation/filesystems/fuse-io.txt

内核文档写的都超级简单,可以结合使用fuse的例子来学习fuse的使用:lxc/lxcfs

libfuse初级使用

Libfuse的头文件/usr/include/fuse/fuse.h中给出的了libfuse的接口,内容比较多。

下面例子中使用的源代码

libfuse的命令行参数

main.c内容如下,就是单纯调用fuse_main():

/*
 * main.c
 * Copyright (C) 2019 lijiaocn <[email protected]>
 *
 * Distributed under terms of the GPL license.
 */

#include <fuse/fuse.h>

int main(int argc, char *argv[])
{
	if (!fuse_main(argc, argv, NULL, NULL)){
		return -1;
	}
	return 0;
}

编译方法:

all: main.c
	gcc -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26 -lfuse main.c

编译的时候需要指定FUSE的版本-DFUSE_USE_VERSION=26,上面代码中fuse_main()用的是fuse的新接口,和老接口不一样。

如果不指定26版本,会遇到下面的错误:

In file included from /usr/include/fuse/fuse.h:26:0,
                 from main.c:8:
/usr/include/fuse/fuse_common.h:497:4: error: #error Compatibility with API version other than 21, 22, 24, 25 and 11 not supported
 #  error Compatibility with API version other than 21, 22, 24, 25 and 11 not supported
    ^
main.c: In function ‘main’:
main.c:12:2: error: too many arguments to function ‘fuse_main_compat1’
  if (!fuse_main(argc, argv, NULL, NULL)){
  ^
In file included from /usr/include/fuse/fuse.h:1012:0,
                 from main.c:8:
/usr/include/fuse/fuse_compat.h:198:6: note: declared here
 void fuse_main_compat1(int argc, char *argv[],
      ^
main.c:12:2: error: invalid use of void expression
  if (!fuse_main(argc, argv, NULL, NULL)){
  ^
make: *** [all] Error 1

上面的代码编译得到程序不会做任何事情,但我们可以用它来看一下,libfuse的版本和支持的命令行参数:

$ ./a.out -V
FUSE library version: 2.9.2
fusermount version: 2.9.2
using FUSE kernel interface version 7.19

命令行参数:

$ ./a.out -h
usage: ./a.out mountpoint [options]

general options:
    -o opt,[opt...]        mount options
    -h   --help            print help
    -V   --version         print version

FUSE options:
    -d   -o debug          enable debug output (implies -f)
    -f                     foreground operation
    -s                     disable multi-threaded operation
    -o allow_other         allow access to other users
    -o allow_root          allow access to root
    ...(省略)...
Module options:

[iconv]
    -o from_code=CHARSET   original encoding of file names (default: UTF-8)
    -o to_code=CHARSET     new encoding of the file names (default: UTF-8)

[subdir]
    -o subdir=DIR           prepend this directory to all paths (mandatory)
    -o [no]rellinks        transform absolute symlinks to relative

通过-h,我们知道了fuse支持的所有options

FUSE options:
    -d   -o debug          enable debug output (implies -f)
    -f                     foreground operation
    -s                     disable multi-threaded operation
    -o allow_other         allow access to other users
    -o allow_root          allow access to root
    ...(省略)...

挂载fuse文件系统

先用上面的程序尝试挂载,直接挂载/tmp/x目录,没有使用任何挂载参数:

$ ./a.out /tmp/x

挂载后,在/proc/mounts中可以看到多出了一个挂载点:

$ cat /proc/mounts |grep "/tmp/x"
a.out /tmp/x fuse.a.out rw,nosuid,nodev,relatime,user_id=0,group_id=0 0 0

/sys/fs/fuse/connections/中也会相应多出一个connection目录:

$ ls /sys/fs/fuse/connections/
38  40  42

这时对/tmp/x目录进行任何操作,都会提示下面错误:

$ cat /tmp/x
cat: /tmp/x: Function not implemented

$ ls /tmp/x
ls: cannot access /tmp/x: Function not implemented

$ echo "abc" >/tmp/x/a
-bash: /tmp/x/a: Function not implemented

这是因为前面的代码中,fuse_main()的第三个参数没有设置,是NULL:

if (!fuse_main(argc, argv, NULL, NULL)){
	return -1;
}

文件操作函数的实现放在下一节中,卸载fuse的方式是直接umount:

umount /tmp/x

实现几个假的文件操作函数

在上面代码的基础上,实现几个假的文件处理函数,并将它们传给fuse_main(),篇幅关系就不贴出所有代码了,只展示下代码轮廓:

完整代码:示例代码 workspace/studys/study-libfuse/

#include <fuse/fuse.h>
#include "operations.h"

int fake_getattr(const char * input, struct stat * stat){
    printf("input: %s\n", input);
    return 0;
}

int fake_mkdir(const char * input, mode_t mode){
    printf("input: %s\n", input);
    return 0;
}

...省略...

const struct fuse_operations fake_ops = {
    .getattr = fake_getattr,
    .readlink = NULL,
    .getdir = NULL,
    .mknod = NULL,
    .mkdir = fake_mkdir,
    ...省略...
}

int main(int argc, char *argv[])
{
    if (!fuse_main(argc, argv, &fake_ops, NULL)){
        return -1;
    }
    return 0;
}

加上-d参数,在debug模式运行:

$ ./demo-fake -d /tmp/x
FUSE library version: 2.9.2
nullpath_ok: 0
nopath: 0
utime_omit_ok: 0
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56, pid: 0
INIT: 7.22
flags=0x0000f7fb
max_readahead=0x00020000
   INIT: 7.19
   flags=0x00000011
   max_readahead=0x00020000
   max_write=0x00020000
   max_background=0
   congestion_threshold=0
   unique: 1, success, outsize: 40

这时候在一个新开的shell中操作/tmp/x:

$ ls /tmp/x/abc
ls: cannot access /tmp/x/abc: Input/output error

在demo-fake运行的shell中,可以看到下面的内容:

unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 25404
LOOKUP /abc
getattr /abc
input: /abc
   NODEID: 2
   unique: 2, success, outsize: 144

输出的正是代码中实现的几个假的操作函数打印出的内容。

fuse control filesystem

加载内核模块fuse.ko后,可以用下面的命令加载fusectl fs:

mount -t fusectl none /sys/fs/fuse/connections

每个使用fuse的进程有一个对应的目录:

$ ls  /sys/fs/fuse/connections
38  42

直接挂载 fuse filesystem 文件系统

kernel/Documentation/filesystems/fuse.txt中说fuse提供了fusefuseblk两种文件系统类型,可以作为mount命令的-t参数的参数值。

没搞清楚要怎样用mount直接挂载fuse文件系统,这里先收录文档给出的挂载选项,具体挂载方法弄明白以后再补充(2019-01-21 19:12:47):

'fd=N'

  The file descriptor to use for communication between the userspace
  filesystem and the kernel.  The file descriptor must have been
  obtained by opening the FUSE device ('/dev/fuse').

'rootmode=M'

  The file mode of the filesystem's root in octal representation.

'user_id=N'

  The numeric user id of the mount owner.

'group_id=N'

  The numeric group id of the mount owner.

'default_permissions'

  By default FUSE doesn't check file access permissions, the
  filesystem is free to implement its access policy or leave it to
  the underlying file access mechanism (e.g. in case of network
  filesystems).  This option enables permission checking, restricting
  access based on file mode.  It is usually useful together with the
  'allow_other' mount option.

'allow_other'

  This option overrides the security measure restricting file access
  to the user mounting the filesystem.  This option is by default only
  allowed to root, but this restriction can be removed with a
  (userspace) configuration option.

'max_read=N'

  With this option the maximum size of read operations can be set.
  The default is infinite.  Note that the size of read requests is
  limited anyway to 32 pages (which is 128kbyte on i386).

'blksize=N'

  Set the block size for the filesystem.  The default is 512.  This
  option is only valid for 'fuseblk' type mounts.

lxcfs中的libfuse用法

lxc/lxcfs是一个使用了libfuse的程序,下面把它对libfuse的用法摘出来,作为libfuse用法的例子:

一无所知的时候,看一下别人怎么用的,会特别有帮助,感觉这个例子比较“大”,可以看后面章节中我整理出来的更简短的例子。

lxcfs代码中开始使用fuse的地方:

if (!fuse_main(nargs, newargv, &lxcfs_ops, NULL))
    ret = EXIT_SUCCESS;

fuse_main()定义如下:

/**
 * Main function of FUSE.
 *
 * This is for the lazy.  This is all that has to be called from the
 * main() function.
 *
 * This function does the following:
 *   - parses command line options (-d -s and -h)
 *   - passes relevant mount options to the fuse_mount()
 *   - installs signal handlers for INT, HUP, TERM and PIPE
 *   - registers an exit handler to unmount the filesystem on program exit
 *   - creates a fuse handle
 *   - registers the operations
 *   - calls either the single-threaded or the multi-threaded event loop
 *
 * Note: this is currently implemented as a macro.
 *
 * @param argc the argument counter passed to the main() function
 * @param argv the argument vector passed to the main() function
 * @param op the file system operation
 * @param user_data user data supplied in the context during the init() method
 * @return 0 on success, nonzero on failure
 */
/*
  int fuse_main(int argc, char *argv[], const struct fuse_operations *op,
  void *user_data);
*/
#define fuse_main(argc, argv, op, user_data)                \
    fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)

lxcfs中fuse_main的输入参数: argc、argv,命令行参数

fuse_main()的第一个参数argc是输入参数的个数,等于第二个参数argv数组的长度。

上面的头文件注释中没有明确说支持哪些参数,lxcfs中传入的参数是:

-d/-f:二选一,没有参数值:

-f running foreground by default
-d enable debug output

-o:挂载参数,有参数值,lxcfs中设置的参数是:

allow_other,direct_io,entry_timeout=0.5,attr_timeout=0.5,nonempty

lxcfs中fuse_main的输入参数:op,文件操作函数

fuse_main()的第三个参数op非常关键,里面设置了所有文件操作对应的处理函数。

const struct fuse_operations lxcfs_ops = {
    .getattr = lxcfs_getattr,
    .readlink = NULL,
    .getdir = NULL,
    .mknod = NULL,
    .mkdir = lxcfs_mkdir,
    .unlink = NULL,
    .rmdir = lxcfs_rmdir,
    .symlink = NULL,
    .rename = NULL,
    .link = NULL,
    .chmod = lxcfs_chmod,
    .chown = lxcfs_chown,
    .truncate = lxcfs_truncate,
    .utime = NULL,

    .open = lxcfs_open,
    .read = lxcfs_read,
    .release = lxcfs_release,
    .write = lxcfs_write,

    .statfs = NULL,
    .flush = lxcfs_flush,
    .fsync = lxcfs_fsync,

    .setxattr = NULL,
    .getxattr = NULL,
    .listxattr = NULL,
    .removexattr = NULL,

    .opendir = lxcfs_opendir,
    .readdir = lxcfs_readdir,
    .releasedir = lxcfs_releasedir,

    .fsyncdir = NULL,
    .init = NULL,
    .destroy = NULL,
    .access = lxcfs_access,
    .create = NULL,
    .ftruncate = NULL,
    .fgetattr = NULL,
};

fuse_operations在文件/usr/include/fuse/fuse.h中定义。

参考

  1. kernel/Documentation/filesystems/fuse.txt
  2. kernel/Documentation/filesystems/fuse-io.txt
  3. lxc/lxcfs
  4. 示例代码:workspace/studys/study-libfuse/

关注加微信,一般不闲聊(直接说事)

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  微信公众号精选文章  发现知识星球