在上一期中,我们使用固定的文件编写了一个简单的文件系统,这一期我们将编写一个更加复杂的文件系统

开始吧!

我们讲过,FUSE需要我们实现的函数声明在“ fuse_operations”的结构中,具有以下定义:

struct fuse_operations {
    int (*getattr) (const char *, struct stat *);
    int (*readlink) (const char *, char *, size_t);
    int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
    int (*mknod) (const char *, mode_t, dev_t);
    int (*mkdir) (const char *, mode_t);
    int (*unlink) (const char *);
    int (*rmdir) (const char *);
    int (*symlink) (const char *, const char *);
    int (*rename) (const char *, const char *);
    int (*link) (const char *, const char *);
    int (*chmod) (const char *, mode_t);
    int (*chown) (const char *, uid_t, gid_t);
    int (*truncate) (const char *, off_t);
    int (*utime) (const char *, struct utimbuf *);
    int (*open) (const char *, struct fuse_file_info *);
    int (*read) (const char *, char *, size_t, off_t,
             struct fuse_file_info *);
    int (*write) (const char *, const char *, size_t, off_t,
              struct fuse_file_info *);
    int (*statfs) (const char *, struct statvfs *);
    int (*flush) (const char *, struct fuse_file_info *);
    int (*release) (const char *, struct fuse_file_info *);
    int (*fsync) (const char *, int, struct fuse_file_info *);
    int (*setxattr) (const char *, const char *, const char *, size_t, int);
    int (*getxattr) (const char *, const char *, char *, size_t);
    int (*listxattr) (const char *, char *, size_t);
    int (*removexattr) (const char *, const char *);
    int (*opendir) (const char *, struct fuse_file_info *);
    int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
            struct fuse_file_info *);
    int (*releasedir) (const char *, struct fuse_file_info *);
    int (*fsyncdir) (const char *, int, struct fuse_file_info *);
    void *(*init) (struct fuse_conn_info *conn);
    void (*destroy) (void *);
    int (*access) (const char *, int);
    int (*create) (const char *, mode_t, struct fuse_file_info *);
    int (*ftruncate) (const char *, off_t, struct fuse_file_info *);
    int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
    int (*lock) (const char *, struct fuse_file_info *, int cmd,
             struct flock *);
    int (*utimens) (const char *, const struct timespec tv[2]);
    int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
    int (*ioctl) (const char *, int cmd, void *arg,
              struct fuse_file_info *, unsigned int flags, void *data);
    int (*poll) (const char *, struct fuse_file_info *,
             struct fuse_pollhandle *ph, unsigned *reventsp);
    int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
              struct fuse_file_info *);
    int (*read_buf) (const char *, struct fuse_bufvec **bufp,
             size_t size, off_t off, struct fuse_file_info *);
    int (*flock) (const char *, struct fuse_file_info *, int op);
    int (*fallocate) (const char *, int, off_t, off_t,
              struct fuse_file_info *);
};

您可以看到此结构的所有字段都是指向函数的指针。当文件系统上发生特定事件时,FUSE将调用它们中的每一个。例如,当用户在文件上写入功能时,结构中“write”字段所指向的功能将被调用。如果快速浏览结构的字段并尝试弄清楚每个字段代表什么事件,您将发现自己熟悉其中的许多事件。例如,显然,当用户尝试创建新目录时,将调用“ mkdir”条目的功能,而当用户尝试删除文件时,将调用“ unlink”。

要实现文件系统,您需要使用此结构,并且需要定义此结构的功能,然后用实现的功能的指针填充该结构。这里的大多数功能都是可选的。您不需要全部实现;但是它们中的一些(例如getattr)是必不可少的。我们将在这里检查编写简单文件系统必须实现的最重要功能,这些功能是getattr,readdir和read。

系统尝试获取文件属性时,将调用getattr事件的功能。它类似于stat()。sata结构体中“ st_dev”和“ st_blksize”字段将被忽略。同时“ st_ino”字段也将被忽略,除非指定了use_ino安装选项。

如果我们阅读stat()的手册页,我们会看到它接收到两个参数,第一个是必须返回文件的属性文件的路径(文件元数据?),第二个是指向sata结构的指针。如果成功,此函数必须返回0。

如果您需要有关属性本身的更多信息,我建议您使用以下链接Unix Stat命令:如何识别文件属性Stat(系统调用)文件属性的含义

当用户查看目录中的文件和目录时,将调用readdir。从名称中可以看出,当系统尝试从文件中读取大量数据时,将调用read

预计效果

一个简单的文件系统,名为SSFS,可以查看目录,打开,编辑,关闭文件。

编写函数

编写“ getattr”

“ getattr”的功能将通过填充stat()结构体来返回有关文件系统中的每个文件的重要信息。为了简单起见,我们将覆盖此结构的基本字段,以使我们的文件系统正常工作。

让我们定义FUSE版本宏,并包括编写SSFS所需的文件:

#define FUSE_USE_VERSION 30

#include <fuse.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

函数“ do_getattr”是系统向SSFS询问特定文件的属性时将调用的函数。当然,我们从函数的头开始:

static int do_getattr( const char *path, struct stat *st )
{

我们可以看到它带有两个参数并返回一个整数。第一个参数是系统要求SSFS提供其属性的文件路径。另一个参数是“ stat”结构,该结构必须填充该文件的属性。成功后,返回值必须为0,否则为-1,并且“ errno”必须填充正确的错误代码。

让我们填写第一组统计信息字段。

        st->st_uid = getuid();
    st->st_gid = getgid();
    st->st_atime = time( NULL );
    st->st_mtime = time( NULL );

字段“ st_uid”代表相关文件的所有者。在SSFS中,所有文件/目录的所有者是安装文件系统的同一用户。同样对于代表文件/目录所有者组的“ st_gid”,它将是安装文件系统的用户组。我们使用函数getuid和getgid来获取当前用户(安装文件系统的用户)的用户ID和组ID。

字段“ st_atime”代表该文件的最后访问时间,“ st_mtime”代表该文件的最后修改时间。我们使用函数time将两个函数设置为当前时间。请注意,这两个字段的值必须是Unix时间。

现在,让我们填充其余字段:

    if ( strcmp( path, "/" ) == 0 )
    {
        st->st_mode = S_IFDIR | 0755;
        st->st_nlink = 2;
    }
    else
    {
        st->st_mode = S_IFREG | 0644;
        st->st_nlink = 1;
        st->st_size = 1024;
    }
        
    return 0;

从最后的代码中可以看到,根据所涉及文件的路径,我们用不同的信息填充“ st_mode”和“ st_nlink”字段。字段“ st_mode”指定文件是常规文件,目录还是其他文件。详情查看这里。另外,“ st_mode”指定该文件的权限位。字段“st_nlink”指定数量的硬链接。最后,字段“ st_size”以字节为单位指定该文件的大小。

回到前面的代码,我们可以看到,当所讨论的文件是SSFS的根目录时,if语句的第一块将要执行。在这种情况下,我们用宏S_IFDIR填充“ st_mode”,该宏表示所讨论的文件是目录,然后将权限位设置为以下内容:只有文件的所有者才能读取,写入和执行,即组的目录。用户和其他用户只能读取和执行,如果您不知道我在说什么,则可以阅读“ 了解Linux文件权限 ”。在为根目录填充“ st_mode”后,我们指定了硬链接的数量,您可能想知道为什么2个硬链接不仅是1个,您可以在此处找到答案。

将对SSFS中根目录以外的所有文件执行else块。在这些文件的“ st_mode”中,我们使用宏S_IFREG指示它们只是常规文件,然后我们将权限位指定如下:所有者可以读取和写入文件,组的用户和其他用户只能读取文件。之后,将硬链接数设置为1。最后,将文件的大小指定为1024字节。

现在,我们得到了getattr的简单愚蠢的实现,包括注释和调试消息的整个代码:

static int do_getattr( const char *path, struct stat *st )
{
    printf( "[getattr] Called\n" );
    printf( "\tAttributes of %s requested\n", path );
    
    // GNU's definitions of the attributes (http://www.gnu.org/software/libc/manual/html_node/Attribute-Meanings.html):
    //         st_uid:     The user ID of the file’s owner.
    //        st_gid:     The group ID of the file.
    //        st_atime:     This is the last access time for the file.
    //        st_mtime:     This is the time of the last modification to the contents of the file.
    //        st_mode:     Specifies the mode of the file. This includes file type information (see Testing File Type) and the file permission bits (see Permission Bits).
    //        st_nlink:     The number of hard links to the file. This count keeps track of how many directories have entries for this file. If the count is ever decremented to zero, then the file itself is discarded as soon 
    //                        as no process still holds it open. Symbolic links are not counted in the total.
    //        st_size:    This specifies the size of a regular file in bytes. For files that are really devices this field isn’t usually meaningful. For symbolic links this specifies the length of the file name the link refers to.
    
    st->st_uid = getuid(); // The owner of the file/directory is the user who mounted the filesystem
    st->st_gid = getgid(); // The group of the file/directory is the same as the group of the user who mounted the filesystem
    st->st_atime = time( NULL ); // The last "a"ccess of the file/directory is right now
    st->st_mtime = time( NULL ); // The last "m"odification of the file/directory is right now
    
    if ( strcmp( path, "/" ) == 0 )
    {
        st->st_mode = S_IFDIR | 0755;
        st->st_nlink = 2; // Why "two" hardlinks instead of "one"? The answer is here: http://unix.stackexchange.com/a/101536
    }
    else
    {
        st->st_mode = S_IFREG | 0644;
        st->st_nlink = 1;
        st->st_size = 1024;
    }
    
    return 0;
}

编写“ readdir”

在readdir中,我们可以列出特定目录中可用的文件/目录。在SSFS中,只有一个目录(根目录)。让我们从do_readdir函数的头开始:

static int do_readdir( const char *path, void *buffer, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi )
{

如您所见,do_readdir在这里有五个参数。我们对其中的前三个感兴趣。第一个参数“ path”是相关目录的路径。在第二个参数“buffer”中,我们将填充相关目录中可用的文件/目录的名称。第三个参数“ filler”是FUSE发送的函数,我们可以使用它来填充“ path”中可用文件的“ buffer”。该函数成功返回0。

“ filler”的声明如下:

typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
                const struct stat *stbuf, off_t off);

第一个参数是指写入条目(文件名或目录名)的缓冲区的指针。第二个参数是当前条目的名称。第三和第四参数将不在此处介绍。

    filler( buffer, ".", NULL, 0 );
    filler( buffer, "..", NULL, 0 );

如果相关目录为根目录,我们将为文件添加条目:“ file54”和“ file349”。就是这样!

    if ( strcmp( path, "/" ) == 0 )
    {
        filler( buffer, "file54", NULL, 0 );
        filler( buffer, "file349", NULL, 0 );
    }
    
    return 0;

实现“ read”:

我们要实现的最后一个功能是“read”。众所周知,通过此功能,系统可以读取特定文件的内容。让我们从标题开始:

static int do_read( const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *fi )
{

第一个参数“ path”是系统要读取的文件的路径。在第二个参数“buffer”中,我们将存储系统感兴趣的数据块,第三个参数“ size”表示该数据块的大小,第四个参数“ offset”是读取文件内容的偏移(就是从哪开始读)。“ do_read”必须返回已成功读取的字节数。

让我们从定义一些局部变量开始:

    char file54Text[] = "Hello World From File54!";
    char file349Text[] = "Hello World From File349!";
    char *selectedText = NULL;
    if ( strcmp( path, "/file54" ) == 0 )
        selectedText = file54Text;
    else if ( strcmp( path, "/file349" ) == 0 )
        selectedText = file349Text;
    else
        return -1;

如果有问题的文件是“ file54”,那么我们将“ file54”内容的指针分配给“ selectedText”,这显然表示将要返回到系统的内容。“ file349”也会发生同样的情况。如果系统请求读取另一个文件,则将发送错误,因为SSFS仅将这些文件包含在文件中。

    memcpy( buffer, selectedText + offset, size );
        
    return strlen( selectedText ) - offset;
}

在“ do_read”的最后一部分,我们将使用memcpy将有问题的文件的内容复制到“缓冲区”,从“offset”开始直到达到“大小”。然后,我们返回已读取的字节数。

填写“ fuse_operations”

static struct fuse_operations operations = {
    .getattr    = do_getattr,
    .readdir    = do_readdir,
    .read    = do_read,
};

int main( int argc, char *argv[] )
{
    return fuse_main( argc, argv, &operations, NULL );
}

编译和挂载文件系统

您可以使用GCC如下编译SSFS:

gcc ssfs.c -o ssfs `pkg-config fuse --cflags --libs`

“ pkg-config”部分将为编译器提供适当的参数,以包括“ fuse”库。要在编译后挂载文件系统:

./ssfs -f [mount point]
Last modification:October 15th, 2019 at 08:52 pm
如果觉得我的文章对你有用,请随意赞赏