fuse一般称为用户态度文件系统,可以实现在用户层实现对文件系统的控制。
通过fuse内核模块的支持,基于libfuse提供的接口,就可以实现一个文件系统。
fuse内核模块实现了与VFS的对接,运行后可以看到/dev/fuse,拦截相关请求,反馈给fuse进程,进行操作。
使用fuse进行开发,不用关注内核模块,直接使用libfuse库在用户态实现文件系统即可。
0:总结
1:fuse的开发,实际上和前面借助内核插入文件系统大框架感觉差不多,都是实现文件系统基础指令对应的函数。
2:借助opencv实现给图片加水印,opencv的编译以及简单测试。
3:简单了解fuse库下的example下demo,以及简单验证demo。
4:fuse的扩展功能可以根据业务自行扩展,这里只是入门了解。
1:fuse库的使用场景。
fuse库使我们可以在用户态灵活控制文件系统,按需要进行开发(在虚拟化,数据库,云存储,等方向都可以按需使用)。
这里可以用fuse实现对该文件系统下的文件进行特定处理,比如给该文件系统下的图片加水印,给该系统下的文件加特定字段(日志加前缀等)
2:libfuse库的安装
#磁盘满了 借用du -h 查看目录下占用大小 依次进行处理
ubuntu@ubuntu:/home$ du -h -d 1
1.2G ./ubuntu
1.2G .
ubuntu@ubuntu:~/fuse$ ls
libfuse-fuse-3.16.2.tar.gz
ubuntu@ubuntu:~/fuse$ tar -zxvf libfuse-fuse-3.16.2.tar.gz
#进行构建
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2$ mkdir build
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2$ cd build/
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ meson setup ..
#编译和安装 注意日志,看安装位置。
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ ninja
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ sudo ninja install
...
Installing /home/ubuntu/fuse/libfuse-fuse-3.16.2/include/fuse.h to /usr/local/include/fuse3
...
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ ls /dev/fuse
/dev/fuse
3:首先了解一下libfuse下的example(了解笔记未深入)
hello.c是最基础的 会在文件系统下创建一个hello文件 有基本的写入。 ./hello -f ./test2
hello_ll.c 和hello同样的功能 用的更详细的接口。
hello_ll_uds.c 在上面基础上借助fuse_session_custom_io 定制了相关输入和输出通道
invalidate_path.c 借助多线程的方式,实现文件内容的持续变化。 fuse_invalidate_path 标记触发,告诉文件系统特定目录发生变化,需要重新处理。
ioctl.c 实现使支持ioctl, 配合ioctl_client.c测试 调用ioctl可以设置或者获取对应文件大小
一个终端上:
^Croot@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl -f ./test2/
另一个终端上:
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc
6
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 12
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc
12
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ll ./test2/fioc
-rw-r--r-- 1 root root 12 Sep 6 05:13 ./test2/fioc
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 18
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ll ./test2/fioc
-rw-r--r-- 1 root root 18 Sep 6 05:13 ./test2/fioc
notify_inval_entry.c 文件系统下文件名动态反应当前时间
也是新线程 然后通过fuse_lowlevel_notify_expire_entry 移除老的文件
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_entry --update-interval=1 --timeout=30 --no-notify ./test2/
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/
Time_is_05h_45m_04s
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/
Time_is_05h_45m_06s
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/
notify_inval_inode.c 内核识别inode节点变化 分别不同处理测试
#查看说明 --no-notify 参数 不允许内核修改 文件内容不必变化
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_inode --update-interval=1 --no-notify ./test
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cat current_time
The current time is 05:58:07
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cat current_time
The current time is 05:58:07
#没有定义 --no-notify 参数 文件内容会随时间而变化
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_inode --update-interval=1 ./test3
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cd ../test3/
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time
The current time is 06:12:50
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time
The current time is 06:12:51
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time
notify_store_retrieve.c 和notify_inval_inode.c 功能相同 方法不同,主动将更新数据推送到内核 fuse_lowlevel_notify_store
passthrough.c 系列 实际上是fuse直接透传到底层真正的文件系统中 只是一个挂载的功能。 在该目录下操作,实际上是底层真正的文件系统中。
//测试代码貌似是把根目录进行映射了
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_fh -f ./test1
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test1# ls
bin boot dev etc home lib lib32 lib64 libx32 lost+found media mnt opt proc root run sbin snap srv sys tmp usr var
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test1# touch 1
//在映射目录中操作 底层实际文件系统已经实时进行操作了 创建了文件1
ubuntu@ubuntu:/$ ls
1 dev lib libx32 mnt root snap tmp
bin etc lib32 lost+found opt run srv usr
boot home lib64 media proc sbin sys var
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_hp -h
./passthrough_hp: Option ‘h’ does not exist
Usage: ./passthrough_hp --help
./passthrough_hp [options] <source> <mountpoint>
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_hp ./test1 ./test2
//在映射目录test2目录下创建操作文件 在实际文件系统目录test1下能识别到
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# touch 1
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# touch 2
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# ls ../test1/
1 2
poll.c 借助fuse_notify_poll 触发事件 实现了监控文件操作进行打印。 ./poll -f /mnt
null.c 就是一个基本的空的文件系统
struct fuse_operations 是一个结构体,用于定义 FUSE 文件系统操作的回调函数。以下是一些常见的成员和其功能介绍:
getattr: 获取文件属性(如大小、权限等)的回调函数。
readdir: 读取目录内容的回调函数。
open: 打开文件的回调函数。
read: 读取文件内容的回调函数。
write: 写入文件内容的回调函数。
mkdir: 创建目录的回调函数。
rmdir: 删除目录的回调函数。
unlink: 删除文件或符号链接的回调函数。
rename: 重命名文件或移动文件到另一个目录的回调函数。
truncate: 调整文件大小(截断或扩展)的回调函数。
4: 了解demo运行时的参数
-f 或 --foreground: 让文件系统在前台运行,输出日志到控制台。
-d 或 --debug: 启用调试模式,输出更详细的日志信息。
-s 或 --singlethread: 强制单线程模式,即所有回调函数都在主线程中执行。
-o <options>: 指定其他额外的选项。例如,可以使用 -o allow_other 来允许其他用户访问该文件系统。
<mountpoint>: 指定挂载点路径,即将文件系统挂载到哪个目录。
struct fuse_cmdline_opts {
char *mountpoint; // 挂载点路径
int fd; // 文件描述符
int show_help; // 是否显示帮助信息
int foreground; // 是否前台运行
int debug; // 是否开启调试模式
};
5:实现一个demo,用fuse给图片加水印
fuse就是一个进程,用户可以按业务进行控制,可以控制在操作文件系统时的行为,比如读图片时给图片加水印。
5.1 加水印要借助opencv,实现一个demo
5.1.1 安装opencv
tar -zxvf opencv-4.10.0.tar.gz
cd opencv-4.10.0/
mkdir build
cd build/
cmake .. -D CMAKE_BUILD_TYPE=Release -D OPENCV_GENERATE_PKGCONFIG=ON
make -j8
sudo make install
sudo ldconfig
#后面编译测试时用pkg-config 链接库
ubuntu@ubuntu:~/fuse/opencv-4.10.0/build$ opencv_version
4.10.0
ubuntu@ubuntu:~/fuse/opencv-4.10.0/build$ pkg-config --list-all|grep opencv
opencv4 OpenCV - Open Source Computer Vision Library
5.1.2 实现加水印的demo
//直接调用opencv的接口实现图片加水印的demo
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int add_water_mark(void) {
Mat image = imread("/home/ubuntu/storage/fuse/test/China.jpg");
if (image.empty())
{
cout << "empty file"<< endl;
return -1;
}
Mat watermarkedImage = image.clone();
string watermarkText = "opencv add to picture";
int fontFace = FONT_HERSHEY_SIMPLEX;
double fontScale = 2;
int thickness = 2;
int baseline = 0;
Size textSize = getTextSize(watermarkText, fontFace, fontScale, thickness, &baseline);
Point textOrg((image.cols - textSize.width) / 2, (image.rows + textSize.height) / 2);
putText(watermarkedImage, watermarkText, textOrg, fontFace, fontScale,
Scalar(0, 0, 255), thickness);
imwrite("/home/ubuntu/storage/fuse/test/output.jpg", watermarkedImage);
return 0;
}
int main()
{
return add_water_mark();
}
5.1.3 demo编译及运行结果
#编译两种方式
ubuntu@ubuntu:~/start_test$ g++ opencv_test.c -o opencv_test `pkg-config --cflags --libs opencv4`
ubuntu@ubuntu:~/start_test$ g++ opencv_test.c -o opencv_test -I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs
root@ubuntu:/home/ubuntu/start_test# ./opencv_test
root@ubuntu:/home/ubuntu/start_test# ls
opencv_test.c image.jpg image_out.jpg io_uring.c opencv_test
#打开image_out.jpg图片,可以看到已经增加相关水印到图片中。。。 其他功能可扩展。
5.2 使用fuse实现测试代码。
参考example,实际上和前面几次借助内核插入文件系统大框架类似,实现struct fuse_operations结构体中对应的函数实现对应文件指令的调用。
5.2.1 实现一个example 编译运行测试
#define FUSE_USE_VERSION 30
#include <fuse3/fuse.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int sample_getattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi);
int sample_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *fi, enum fuse_readdir_flags flags);
int sample_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi);
int sample_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi);
//新的方案demo 这个结构 还有一个结构 struct fuse_lowlevel_ops
struct fuse_operations sample_opera = {
.getattr = sample_getattr, //获取文件属性
.read = sample_read, //文件读
.write = sample_write, //文件写
.readdir = sample_readdir, //目录内容
};
int main(int argc, char *argv[]) {
return fuse_main(argc, argv, &sample_opera, NULL);
}
#define FILE_MAX_LENGTH 1024
static void *sample_buffer = NULL;
static size_t sample_size = 0;
enum {
SAMPLE_NONE = 0,
SAMPLE_FOLDER,
SAMPLE_FILE,
};
static int file_type(const char *path) {
if (strcmp(path, "/") == 0) {
return SAMPLE_FOLDER;
}
if (strcmp(path, "/safe") == 0) {
return SAMPLE_FILE;
}
return SAMPLE_NONE;
}
//获取文件属性 进行处理
//只是简单对目录和文件进行区分 复杂的需要参考example
int sample_getattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
stbuf->st_uid = getuid();
stbuf->st_gid = getgid();
stbuf->st_atime = stbuf->st_mtime = time(NULL);
int type = file_type(path);
printf("sample_getattr --> path: %s, type: %d\n", path, type);
if (type == SAMPLE_FOLDER) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (type == SAMPLE_FILE) {
stbuf->st_mode = S_IFREG | 0777;
stbuf->st_nlink = 2;
stbuf->st_size = sample_size;
} else {
return -ENOENT;
}
return 0;
}
//简单演示目录下这几个的读
int sample_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
{
//只是最简单的这几个目录的演示
filler(buf, ".", NULL, 0, (enum fuse_fill_dir_flags)0);
filler(buf, "..", NULL, 0, (enum fuse_fill_dir_flags)0);
filler(buf, "safe", NULL, 0, (enum fuse_fill_dir_flags)0);
return 0;
}
//文件写 先写入一个文件再读
int sample_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
int type = file_type(path); //写入safe文件
printf("sample_write %s --> type: %d\n", path , type);
if (type != SAMPLE_FILE) {
return -EINVAL;
}
if (sample_buffer == NULL) {
sample_buffer = malloc(FILE_MAX_LENGTH);
}
//写入的buffer进行大小和偏移的处理 返回实际写入的长度
if (size + offset > FILE_MAX_LENGTH)
{
memcpy((void*)((char *)sample_buffer+offset), buf, FILE_MAX_LENGTH - offset);
sample_size = FILE_MAX_LENGTH;
return FILE_MAX_LENGTH - offset;
}
sample_size = size;
memcpy((void*)((char *)sample_buffer+offset), buf, size);
return size;
}
//文件的读 要读取文件的路径 存储读取数据的缓冲区 期望读的大小 文件中开始读取偏移量 包含有关文件信息和打开标志的结构体指针。
int sample_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
int type = file_type(path);
printf("sample_read --> type: %d\n", type);
if (type != SAMPLE_FILE) {
return -EINVAL;
}
if (sample_buffer == NULL) {
return -EINVAL;
}
//考虑越界相关问题 直接从上面写入的指针处写入吗? 期望读size 从offset 读 实际读sample_size
printf("sample_read --> %ld, %ld , %ld\n", size, offset, sample_size);
memcpy(buf, (char *)sample_buffer+offset, sample_size);
printf("buf: %s\n", buf);
return sample_size;
}
//那文件的读和写,不就实际上在内存中管理了。
//可以参考example 更细节的操作。
//核心还是每个指令对应的函数 了解其函数参数细节,按要求依次进行处理即可。
5.2.2 编译和运行测试:
ubuntu@ubuntu:~/start_testgcc fuse_test.c -o fuse_test -lfuse3
ubuntu@ubuntu:~/start_test$ mkdir test1
#启动 在前台打印日志观察 fuse已经接管了test1 目录
ubuntu@ubuntu:~/start_test$ ./fuse_test -f test1/
sample_getattr --> path: /, type: 1
sample_getattr --> path: /, type: 1
sample_getattr --> path: /, type: 1
sample_getattr --> path: /safe, type: 2
sample_getattr --> path: /safe, type: 2
sample_write /safe --> type: 2
sample_getattr --> path: /safe, type: 2
sample_read --> type: 2
sample_read --> 4096, 0 , 30
buf: 11111111111111111111111111111
#在test1目录下执行如下操作,会观察到上面的日志
ubuntu@ubuntu:~/start_test/test1$ cd ../test1/
ubuntu@ubuntu:~/start_test/test1$ ls
safe
ubuntu@ubuntu:~/start_test/test1$ echo "11111111111111111111111111111" >safe
ubuntu@ubuntu:~/start_test/test1$ cat safe
11111111111111111111111111111
#如果把上面加水印的接口加入到对应的写或者读接口中,就可以在fuse中实现对文件系统中的特定图片进行处理了。
#参考example中的demo,也可以对fuse对文件系统的扩展有一定了解。