Linux FUSE(使用者態檔案系統)的使用:用libfuse建立FUSE檔案系統
說明
FUSE 是Linux Kernel的特性之一:一個使用者態檔案系統框架,a userspace filesystem framework。 形象的說就是可以在使用者態執行一個程式,這個程式暴露出一個FUSE檔案系統,對這個檔案系統進行的讀寫操作都會被轉給使用者態的程式處理。
FUSE由核心模組 fuse.ko
和使用者空間的動態連結庫 libfuse.*
組成,如果要開發使用fuse的使用者態程式,需要安裝 fuse-devel
:
yum install fuse-devel
資料
Kernel中有兩份關於FUSE的文件:
核心文件寫的都超級簡單,可以結合使用fuse的例子來學習fuse的使用: lxc/lxcfs 。
Fuse control filesystem
載入fuse.ko後,可以用下面的命令載入fusectl fs:
mount -t fusectl none /sys/fs/fuse/connections
每個使用fuse的程序有一個對應的目錄:
$ ls/sys/fs/fuse/connections 3842
直接掛載 fuse filesystem 檔案系統
kernel/Documentation/filesystems/fuse.txt 中說fuse提供了 fuse
和 fuseblk
兩種檔案系統型別,可以作為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
中定義。
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--helpprint help -V--versionprint version FUSE options: -d-o debugenable debug output (implies -f) -fforeground operation -sdisable multi-threaded operation -o allow_otherallow access to other users -o allow_rootallow access to root ...(省略)... Module options: [iconv] -o from_code=CHARSEToriginal encoding of file names (default: UTF-8) -o to_code=CHARSETnew encoding of the file names (default: UTF-8) [subdir] -o subdir=DIRprepend this directory to all paths (mandatory) -o [no]rellinkstransform absolute symlinks to relative
通過 -h
,我們知道了fuse支援的所有 options
。
掛載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/ 384042
這時候對/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(),篇幅關係就不貼出所有程式碼了,只展示下程式碼輪廓:
#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
這時候開一個新的登入視窗操作/tmp/x:
$ ls /tmp/x/abc ls: cannot access /tmp/x/abc: Input/output error
在另一個視窗中,可以看到下面的內容:
unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 44, pid: 25404 LOOKUP /abc getattr /abc input: /abc NODEID: 2 unique: 2, success, outsize: 144
參考
本文 原創首發 於網站:www.lijiaocn.com