fuse实现原理
本文是对httpdirfs源码的一个学习,源码在https://github.com/fangfufu/httpdirfs
.实现在linux系统下利用fuse技术,把http文件服务器挂载至本地路径.
fuse原理
流程介绍
图中可以看出,fuse和ext3,ntfs都是文件系统模块.我们使用fuse实现文件系统并挂在至/tmp/fuse上,当我们对此目录执行ls命令时,内核的fuse从vfs中获取参数,调用我们自己实现ls的函数,得到结果再从vfs返回至ls.简单来说就是
ls->fuse挂载文件夹->VFS->libfuse->自己实现的函数->结果返回至ls
,demo的源码在https://github.com/MecryWork/Learn-C-to-implement-fuse
简单demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int ou_re addir(const char* path, void* buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info* fi)//读取目录
{
return filler(buf, "hello-world", NULL, 0);
}
static int ou_getattr(const char* path, struct stat* st)//获取状态
{
memset(st, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0)
st->st_mode = 0755 | S_IFDIR;
else
st->st_mode = 0644 | S_IFREG;
return 0;
}
static struct fuse_operations oufs_ops = {//fuse设置响应函数
.readdir = ou_readdir,
.getattr = ou_getattr,
};
int main(int argc, char* argv[])
{
return fuse_main(argc, argv, &oufs_ops, NULL);
}cmake编译成功后会看到生成的可执行文件 fusehello。建立一个挂载点 /tmp/mnt,然后运行
./fusehello /tmp/mnt
成功后试试“ls /tmp/mnt”,就能看到一个文件“hello-world”。要调试的时候可以加上“-d”选项,这样就能看到 FUSE 和自己 printf 的调试输出。代码第一行指定了要使用的 FUSE API 版本。这里使用的是 2.6 版本。简单demo2,实现创建/删除普通文件的功能,加了
ou_create
,和ou_unlink
两个函数,在fuse上被调用.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37static int ou_create(const char* path, mode_t mode, struct fuse_file_info* fi)
{
struct ou_entry* o;
struct list_node* n;//使用链表,文件放入链表中方便删除增加
if (strlen(path + 1) > MAX_NAMELEN)
return -ENAMETOOLONG;
list_for_each (n, &entries) {
o = list_entry(n, struct ou_entry, node);
if (strcmp(path + 1, o->name) == 0)
return -EEXIST;
}
o = malloc(sizeof(struct ou_entry));
strcpy(o->name, path + 1); /* skip leading '/' */
o->mode = mode | S_IFREG;
list_add_prev(&o->node, &entries);
return 0;
}
static int ou_unlink(const char* path)
{
struct list_node *n, *p;
list_for_each_safe (n, p, &entries) {
struct ou_entry* o = list_entry(n, struct ou_entry, node);
if (strcmp(path + 1, o->name) == 0) {
__list_del(n);
free(o);
return 0;
}
}
return -ENOENT;
}
- 总结:具体还是使用的linux中的fuse的库文件,来实现我们自己的fuse文件系统,我们只需要参照
fuse.h
文件的函数指针.需要实现哪个功能,就开始自己进行实现,最后把结构体传入至fuse,就可以成功编写一个属于我们自己的文件系统了.
fuse+http的实现
利用fuse的机制,再获取http接口,实现把http文件系统的文件挂载至本机指定文件夹中,虽然此程序是用纯C编写的,但是我们已知具体实现功能,并了解部分fuse实现原理,所以我们只需要解读关键函数的实现就能大致了解实现流程
link.c和link.h加载http中的Link中的路径转换至磁盘本地路径.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55LinkTable *LinkTable_disk_open(const char *dirn)//从磁盘加载链接表
{
char *metadirn = path_append(META_DIR, dirn);
char *path;
//http中的路径进行选择截取
if (metadirn[strnlen(metadirn, MAX_PATH_LEN)] == '/') {
path = path_append(metadirn, ".LinkTable");
} else {
path = path_append(metadirn, "/.LinkTable");
}
//打开路径开始获取目标文件
FILE *fp = fopen(path, "r");
fprintf(stderr, "path<%s>\n",path);
free(metadirn);
if (!fp) {
free(path);
return NULL;
}
LinkTable *linktbl = CALLOC(1, sizeof(LinkTable));
fread(&linktbl->num, sizeof(int), 1, fp);
linktbl->links = CALLOC(linktbl->num, sizeof(Link *));
//遍历link获取所需内容,并返回
for (int i = 0; i < linktbl->num; i++) {
linktbl->links[i] = CALLOC(1, sizeof(Link));
fread(linktbl->links[i]->linkname, sizeof(char), MAX_FILENAME_LEN, fp);
fread(linktbl->links[i]->f_url, sizeof(char), MAX_PATH_LEN, fp);
fread(&linktbl->links[i]->type, sizeof(LinkType), 1, fp);
fread(&linktbl->links[i]->content_length, sizeof(size_t), 1, fp);
fread(&linktbl->links[i]->time, sizeof(long), 1, fp);
if (feof(fp)) {
/* reached EOF */
fprintf(stderr,
"LinkTable_disk_open(): reached EOF!\n");
LinkTable_free(linktbl);
LinkTable_disk_delete(dirn);
return NULL;
}
if (ferror(fp)) {
fprintf(stderr, "LinkTable_disk_open(): encountered ferror!\n");
LinkTable_free(linktbl);
LinkTable_disk_delete(dirn);
return NULL;
}
}
if (fclose(fp)) {
fprintf(stderr,
"LinkTable_disk_open(): cannot close the file pointer, %s\n",
strerror(errno));
}
return linktbl;
}network.c 和network.h,获取http文件服务器内容,详情请参考
https://curl.haxx.se/libcurl/c/threaded-ssl.html
这位作者也是参考此源码完成.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49void NetworkSystem_init(void)
{
/* ------- Global related ----------*/
//初始化http的curl,如果非CURL_GLOBAL_ALL则初始化失败退出
if (curl_global_init(CURL_GLOBAL_ALL)) {
fprintf(stderr, "network_init(): curl_global_init() failed!\n");
exit_failure();
}
/* -------- Share related ----------*/
//返回curl句柄,获取相应我curl
CURL_SHARE = curl_share_init();
if (!(CURL_SHARE)) {
fprintf(stderr, "network_init(): curl_share_init() failed!\n");
exit_failure();
}
//给目标穿参
curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(CURL_SHARE, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
//thread的初始化
if (pthread_mutex_init(&curl_lock, NULL) != 0) {
fprintf(stderr, "network_init(): curl_lock initialisation failed!\n");
exit_failure();
}
curl_share_setopt(CURL_SHARE, CURLSHOPT_LOCKFUNC, curl_callback_lock);
curl_share_setopt(CURL_SHARE, CURLSHOPT_UNLOCKFUNC, curl_callback_unlock);
//处理多个curl
/* ------------- Multi related -----------*/
curl_multi = curl_multi_init();
if (!curl_multi) {
fprintf(stderr, "network_init(): curl_multi_init() failed!\n");
exit_failure();
}
curl_multi_setopt(curl_multi, CURLMOPT_MAX_TOTAL_CONNECTIONS,
CONFIG.max_conns);
curl_multi_setopt(curl_multi, CURLMOPT_MAX_HOST_CONNECTIONS,
CONFIG.max_conns);
/* ------------ Initialise locks ---------*/
if (pthread_mutex_init(&transfer_lock, NULL)) {
fprintf(stderr,
"network_init(): transfer_lock initialisation failed!\n");
exit_failure();
}
crypto_lock_init();
}fuse_local.c和fuse_local.h,搭建本地fuse文件系统,本文上半部分已经详细介绍原理.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29static int fs_readdir(const char *path, void *buf, fuse_fill_dir_t dir_add,off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;
Link *link;
LinkTable *linktbl;
if (!strcmp(path, "/")) {
linktbl = ROOT_LINK_TBL;
} else {
linktbl = path_to_Link_LinkTable_new(path);//如果非根目录,则从link中重新拉去当前目录所有文件夹,和文件从而实现文件夹的存储.缺点是要试试刷新多文件会有较高延迟
if(!linktbl) {
return -ENOENT;
}
}
/* start adding the links */
dir_add(buf, ".", NULL, 0);
dir_add(buf, "..", NULL, 0);
for (int i = 1; i < linktbl->num; i++) {
link = linktbl->links[i];
if (link->type != LINK_INVALID) {
dir_add(buf, link->linkname, NULL, 0);
}
}
return 0;
}
- 总结:通过http(curl库),和fuse(fuse库).点击当前文件夹就会从http拉去当前文件夹所有内容,从而实现了把http文件系统服务器挂在至本机磁盘中.makefile直接使用make生成可执行文件httpdirfs,然后调用如下命令即可使用
./httpdirfs -f –cache $URL $MOUNT_POINT