读写文件
在第一个程序的基础上增加读写 hello-world 的功能:
#define FUSE_USE_VERSION 26
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fuse.h>
#define BUFFSIZE 8192
static int content_size;
static char content[BUFFSIZE];
static const char* fname = "hello-world";
static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info* fi)
{
return filler(buf, fname, 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;
st->st_size = strlen(fname);
} else {
st->st_mode = 0644 | S_IFREG;
st->st_size = content_size;
}
return 0;
}
static int ou_read(const char* path, char* buf, size_t bytes, off_t offset,
struct fuse_file_info* fi)
{
size_t available;
if (strcmp(path + 1, fname) != 0)
return -ENOENT;
if (offset >= content_size)
return 0;
available = content_size - offset;
if (available < bytes)
bytes = available;
memcpy(buf, content + offset, bytes);
return bytes;
}
static int ou_write(const char* path, const char* buf, size_t bytes,
off_t offset, struct fuse_file_info* fi)
{
size_t new_size;
if (strcmp(path + 1, fname) != 0)
return -ENOENT;
new_size = offset + bytes;
if (new_size > BUFFSIZE)
return -EFBIG;
memcpy(content + offset, buf, bytes);
if (content_size < new_size)
content_size = new_size;
return bytes;
}
static int ou_truncate(const char* path, off_t size)
{
if (size > BUFFSIZE)
return -EFBIG;
if (content_size < size)
memset(content + content_size, 0, size - content_size);
content_size = size;
return 0;
}
static struct fuse_operations oufs_ops = {
.readdir = ou_readdir,
.getattr = ou_getattr,
.read = ou_read,
.write = ou_write,
.truncate = ou_truncate,
};
int main(int argc, char* argv[])
{
return fuse_main(argc, argv, &oufs_ops, NULL);
}
一个全局缓冲区 content 用来记录 hello-world 的内容,content_size 用来记录缓冲区长度。函数 ou_read() 和 ou_write() 分别用来读写 hello-world 的内容,ou_truncate() 用来改变文件大小。这些函数的功能不具体描述了,manpage 里面说得很详细。
创建/删除目录
#define FUSE_USE_VERSION 26
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fuse.h>
#include "list.h"
#define MAX_NAMELEN 255
struct ou_entry {
mode_t mode;
struct list_node node;
char name[MAX_NAMELEN + 1];
};
static struct list_node entries;
static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info* fi)
{
struct list_node* n;
if (strcmp(path, "/") != 0)
return -EINVAL;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
list_for_each (n, &entries) {
struct ou_entry* o = list_entry(n, struct ou_entry, node);
filler(buf, o->name, NULL, 0);
}
return 0;
}
static int ou_getattr(const char* path, struct stat* st)
{
struct list_node* n;
memset(st, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
st->st_mode = 0755 | S_IFDIR;
st->st_nlink = 2;
st->st_size = 0;
list_for_each (n, &entries) {
struct ou_entry* o = list_entry(n, struct ou_entry, node);
++st->st_nlink;
st->st_size += strlen(o->name);
}
return 0;
}
list_for_each (n, &entries) {
struct ou_entry* o = list_entry(n, struct ou_entry, node);
if (strcmp(path + 1, o->name) == 0) {
st->st_mode = o->mode;
st->st_nlink = 2;
return 0;
}
}
return -ENOENT;
}
static int ou_mkdir(const char* path, mode_t mode)
{
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 the leading '/' */
o->mode = mode | S_IFDIR;
list_add_prev(&o->node, &entries);
return 0;
}
static int ou_rmdir(const char* path)
{
struct list_node *n, *p;
if (strcmp(path, "/") != 0)
return -EINVAL;
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;
}
static struct fuse_operations oufs_ops = {
.getattr = ou_getattr,
.readdir = ou_readdir,
.mkdir = ou_mkdir,
.rmdir = ou_rmdir,
};
int main(int argc, char* argv[])
{
list_init(&entries);
return fuse_main(argc, argv, &oufs_ops, NULL);
}
为了防止对新建的目录执行 ls 得到错误的结果,程序里把所有函数的执行目录都限定在根目录下。和创建/删除文件的程序比较一下,除了把 create()/unlink() 分别替换成 mkdir()/rmdir() 外其它内容基本没有变化,使用的还是同样的数据结构。在 linux vfs 的实现中目录的确是被作为一个特殊的文件对待,只是普通文件使用 read()/write() 访问,而目录使用 readdir()/mkdir() 访问。
命令行参数传递
FUSE 提供了统一解析命令行参数的方法。
#define FUSE_USE_VERSION 26
#include <stdio.h>
#include <fuse.h>
#include <fuse_opt.h>
#include <stddef.h>
struct ou_options {
const char* device;
const char* extra;
} ou_options;
static struct fuse_opt opts[] = {
{"--device %s", offsetof(struct ou_options, device), 0},
{"--extra %s", offsetof(struct ou_options, extra), 0},
/* #define FUSE_OPT_KEY(templ, key) { templ, -1U, key } */
FUSE_OPT_KEY("--optional %s", 5),
FUSE_OPT_KEY("-o%s", 6),
FUSE_OPT_END
};
static int optional_opt_func(void* data, const char* arg, int key,
struct fuse_args* outargs)
{
printf("get optional option: %s, key is %d\n", arg, key);
return 0;
}
static inline void errmsg(const char* bin)
{
fprintf(stderr, "usage: %s --device device --extra extra --optional whatever -owhatever\n", bin);
}
int main(int argc, char* argv[])
{
int i;
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
fuse_opt_parse(&args, &ou_options, opts, optional_opt_func);
if (ou_options.device)
printf("get device -> %s\n", ou_options.device);
if (ou_options.extra)
printf("get extra -> %s\n", ou_options.extra);
printf("after parsing, argc = %d\n", args.argc);
for (i = 0; i <= args.argc; ++i)
printf("argv[%d] = %s\n", i, args.argv[i]);
/*fuse_main(args.argc, args.argv, NULL, NULL);*/
fuse_opt_free_args(&args);
return 0;
}
首先是一个 struct fuse_opt 结构体数组,它指定了接收的参数形式(定义在 /usr/include/fuse/fuse_opt.h):
struct fuse_opt {
/** Matching template and optional parameter formatting */
const char *templ;
/**
* Offset of variable within 'data' parameter of fuse_opt_parse()
* or -1
*/
unsigned long offset;
/**
* Value to set the variable to, or to be passed as 'key' to the
* processing function. Ignored if template has a format
*/
int value;
};
其中 templ 是参数形式,如“--device %s”表示命令行参数“--device”后跟一个字符串,“-o%s”表示“-o”后直接接参数;offset 表示如果解析到相应的参数后保存在什么地方,这个选项待会结合 fuse_opt_parse() 来看;最后是一个 value,这个对于参数处理关系不大。
接着是调用 fuse_opt_parse() 来解析参数:
typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
struct fuse_args *outargs);
int fuse_opt_parse(struct fuse_args *args, void *data,
const struct fuse_opt opts[], fuse_opt_proc_t proc);
如果 fuse_opt 中的 offset 不为 -1,则会将解析出来的参数保存在 data 对应的 offset 位置。例如,如果找到了参数“--device abc”,则字符串“abc”被保存在 data 中偏移量为 offsetof(struct ou_options, device) 中,也就是让 ou_options.device 指向“abc”。
如果 offset 为 -1,则会调用 fuse_opt_proc_t 来处理。这里的参数传递挺奇怪的,如果选项和参数间有空格,则两个字符串会被合并,例如“--optional xxx”传递到 fuse_opt_proc_t 中的 arg 就是“--optionalxxx”,而“-oxxx”传递过去却是“xxx”(“-o”被去掉了)。如果 fuse_opt_proc_t 的返回值是 0 表示解析出来的参数不会保存在 args 中,为 1 则保存(这个可从解析完后打印的 args 结果看到不同)。
其它
同一时间可能有多个程序访问目录,在实现中要注意并发的问题。
另外还有一些功能如权限修改,在子目录下创建/删除文件/目录,硬/软链接等,有兴趣的可以试着实现一下。