Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZIM Fuse Filesystem #400

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Most famous tools are:
opened with a ZIM reader; [Kiwix](https://kiwix.org) is one example,
but there are [others](https://openzim.org/wiki/ZIM_Readers).

* `zimfuse` creates a filesystem FUSE module that enables access to
the content of a [ZIM](https://openzim.org) file, allowing users to
view entries as files.

A few other tools are provided as well but are of less value for most
of the usages.

Expand Down Expand Up @@ -57,6 +61,8 @@ need to be available:
* [Gumbo](https://github.com/google/gumbo-parser) (package `libgumbo-dev` on Debian/Ubuntu)
* [ICU](http://site.icu-project.org/) (package `libicu-dev` on Debian/Ubuntu)

`zimfuse` relies on [FUSE](https://github.com/libfuse/libfuse) (package `fuse3` on Debian/Ubuntu)

These dependencies may or may not be packaged by your operating
system. They may also be packaged but only in an older version. The
compilation script will tell you if one of them is missing or too old.
Expand Down
5 changes: 3 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ find_library_in_compiler = meson.version().version_compare('>=0.31.0')
rt_dep = dependency('rt', required:false)
docopt_dep = dependency('docopt', static:static_linkage)

with_writer = host_machine.system() != 'windows'
with_writer_and_mount = host_machine.system() != 'windows'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to have a on_windows, or split this in two variables with_writer and with_mount.

We should not link writer and mount compilation together (at least not explicitly in one variable)


if with_writer
if with_writer_and_mount
thread_dep = dependency('threads')
zlib_dep = dependency('zlib', static:static_linkage)
gumbo_dep = dependency('gumbo', static:static_linkage)
magic_dep = dependency('libmagic', static:static_linkage, required:false)
libfuse_dep = dependency('fuse3', version : '>=3.1')

# libmagic.pc has been introduced in version 5.39 of
# File. Unfortunately Ubuntu 20.04 (Focal) still uses version
Expand Down
3 changes: 2 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ executable('zimrecreate', ['zimrecreate.cpp', 'tools.cpp'],

subdir('zimcheck')

if with_writer
if with_writer_and_mount
subdir('zimwriterfs')
subdir('zimfuse')
endif
229 changes: 229 additions & 0 deletions src/zimfuse/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#define FUSE_USE_VERSION 31
#include <fuse3/fuse.h>
#include <sys/stat.h>
#include <zim/archive.h>
#include <zim/item.h>

#include <string>

#include "node.h"
#include "tree.h"

static const Node* getNode(const std::string& nName)
{
Tree* const tree = static_cast<Tree*>(fuse_get_context()->private_data);
auto node = tree->findNode(nName);
return node;
}

void setStat(struct stat* st, const Node* node)
{
Tree* const tree = static_cast<Tree*>(fuse_get_context()->private_data);
if (tree->statCache.count(node->originalPath)) {
*st = tree->statCache[node->originalPath];
return;
}
if (node->isDir) {
st->st_mode = S_IFDIR | 0555;
st->st_nlink = 1;
} else {
st->st_mode = S_IFREG | 0444;
st->st_nlink = 1;
Tree* const tree = static_cast<Tree*>(fuse_get_context()->private_data);
st->st_size = tree->getArchive()
->getEntryByPath(node->originalPath.substr(1))
.getItem(true)
.getSize();
}
tree->statCache[node->originalPath] = *st;
}

static int zimGetAttr(const char* path, struct stat* st, fuse_file_info* fi)
{
const Node* const node = getNode(path);
if (!node)
return -ENOENT;
setStat(st, node);
return 0;
}

static int zimReadDir(const char* path,
void* buf,
fuse_fill_dir_t fill_dir,
off_t offset,
struct fuse_file_info* fi,
enum fuse_readdir_flags flags)
{
const Node* const node = getNode(path);
if (!node)
return -ENOENT;
{
struct stat st;
setStat(&st, node);
fill_dir(buf, ".", &st, 0, (fuse_fill_dir_flags)0);
}

if (const Node* const parent = node->parent) {
struct stat st;
setStat(&st, parent);
fill_dir(buf, "..", &st, 0, (fuse_fill_dir_flags)0);
} else {
fill_dir(buf, "..", nullptr, 0, (fuse_fill_dir_flags)0);
}

for (auto child : node->children) {
struct stat st;
setStat(&st, child);
fill_dir(buf, child->name.c_str(), &st, 0, (fuse_fill_dir_flags)0);
}

return 0;
}

static int zimRead(const char* path,
char* buf,
size_t size,
off_t offset,
struct fuse_file_info* fi)
{
const Node* const node = getNode(path);
Tree* const tree = static_cast<Tree*>(fuse_get_context()->private_data);
if (!node)
return -ENOENT;
const std::string strPath(path);
zim::Entry entry = tree->getArchive()->getEntryByPath(node->originalPath.substr(1));
zim::Item item = entry.getItem(true);

if (offset >= (off_t)item.getSize()) {
return 0;
}

if (offset + size > item.getSize()) {
size = item.getSize() - offset;
}

memcpy(buf, item.getData().data() + offset, size);

return size;
}

static int zimOpen(const char* path, struct fuse_file_info* fi)
{
if ((fi->flags & 3) != O_RDONLY) {
return -EACCES;
}

const Node* const node = getNode(path);
if (!node)
return -ENOENT;
if (node->isDir)
return -EISDIR;
return 0;
}

static struct fuse_operations ops = {
.getattr = zimGetAttr,
.open = zimOpen,
.read = zimRead,
.readdir = zimReadDir,
};

enum {
KEY_HELP,
KEY_VERSION,
};

struct Param {
int str_arg_count = 0;
std::string filename;
std::string mount_point;
~Param() {}
};

void printUsage()
{
fprintf(stderr,
R"(Mounts a ZIM file as a FUSE filesystem

Usage: zimfuse [options] <ZIM-file> [mount-point]

General options:
--help -h print help
--version print version
)");
}

void printVersion()
{
fprintf(stderr, "1.0");
}

static int processArgs(void* data, const char* arg, int key, fuse_args* outargs)
{
Param& param = *static_cast<Param*>(data);

const int KEEP = 1;
const int DISCARD = 0;
const int ERROR = -1;

switch (key) {
case KEY_HELP:
printUsage();
fuse_opt_add_arg(outargs, "-ho");
fuse_main(outargs->argc, outargs->argv, &ops, nullptr);
std::exit(EXIT_SUCCESS);

case KEY_VERSION:
printVersion();
fuse_opt_add_arg(outargs, "--version");
fuse_main(outargs->argc, outargs->argv, &ops, nullptr);
std::exit(EXIT_SUCCESS);

case FUSE_OPT_KEY_NONOPT:
switch (++param.str_arg_count) {
case 1:
param.filename = arg;
return DISCARD;

case 2:
param.mount_point = arg;
return KEEP;

default:
fprintf(
stderr,
"zimfuse: only two arguments allowed: filename and mountpoint\n");
return ERROR;
}

default:
return KEEP;
}
}

int main(int argc, char* argv[])
{
fuse_args args = FUSE_ARGS_INIT(argc, argv);
Param param;

const fuse_opt opts[] = {FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--version", KEY_VERSION),
{nullptr, 0, 0}};

if (fuse_opt_parse(&args, &param, opts, processArgs))
return EXIT_FAILURE;

if (param.filename.empty()) {
printUsage();
return EXIT_FAILURE;
}

auto tree = new Tree(param.filename);
fuse_opt_add_arg(&args, "-s");
// fuse_opt_add_arg(&args, "-d");

auto ret = fuse_main(args.argc, args.argv, &ops, tree);
fuse_opt_free_args(&args);
return ret;
}
11 changes: 11 additions & 0 deletions src/zimfuse/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
sources = [
'main.cpp',
'tree.cpp'
]

deps = [libzim_dep, libfuse_dep]

executable('zimfuse',
sources,
dependencies : deps,
install : true)
25 changes: 25 additions & 0 deletions src/zimfuse/node.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef _ZIM_NODE_H
#define _ZIM_NODE_H

#include <memory>
#include <string>
#include <vector>

struct Node {
using Ptr = std::unique_ptr<Node>;
std::string name;
bool isDir = false;
Node* parent;
std::string fullPath;
std::string originalPath;
int collisionCount = 0;
std::vector<Node*> children;

void addChild(Node* child)
{
if (child)
children.push_back(child);
}
};

#endif
Loading
Loading