使用 fuse 编写文件系统 (2)

读写文件

在第一个程序的基础上增加读写 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 结果看到不同)。

其它

同一时间可能有多个程序访问目录,在实现中要注意并发的问题。

另外还有一些功能如权限修改,在子目录下创建/删除文件/目录,硬/软链接等,有兴趣的可以试着实现一下。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注