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

feature: ZimMount #348

Closed
wants to merge 5 commits into from
Closed
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
5 changes: 3 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ 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'

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)
libfuse_dep = dependency('fuse3', version : '>=3.1')

magic_include_path = ''
magic_prefix_install = get_option('magic-install-prefix')
Expand Down
4 changes: 3 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ executable('zimrecreate', ['zimrecreate.cpp', 'tools.cpp'],

subdir('zimcheck')

if with_writer
if with_writer_and_mount
subdir('zimwriterfs')
subdir('zimmount')
endif

11 changes: 11 additions & 0 deletions src/zimmount/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
sources = [
'zimfs.cpp',
'zimmount.cpp'
]

deps = [libzim_dep, libfuse_dep]

executable('zimmount',
sources,
dependencies : deps,
install : true)
94 changes: 94 additions & 0 deletions src/zimmount/zimfs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "zimfs.h"

#include <sstream>

ZimFSNode::ZimFSNode() : name(""), entry_index(INVALID_INDEX){};
ZimFSNode::ZimFSNode(const std::string& name, zim::entry_index_type index)
: name(name), entry_index(index){};

void ZimFSNode::addEntry(const zim::Entry& entry)
{
std::stringstream ss(entry.getPath());
std::string token;
ZimFSNode* curr = this;
while (getline(ss, token, '/')) {
if (token.empty()) {
continue;
}

if (curr->children.find(token) == curr->children.end()) {
if (ss.eof()) {
curr->children[token] = new ZimFSNode(token, entry.getIndex());
} else {
curr->children[token] = new ZimFSNode(token, INVALID_INDEX);
}
}
curr = curr->children[token];
}
}

ZimFSNode* ZimFSNode::findPath(const std::string& path)
{
std::stringstream ss(path);
std::string token;
ZimFSNode* curr = this;
while (getline(ss, token, '/')) {
if (token.empty()) {
continue;
}

if (curr->children.find(token) == curr->children.end()) {
return nullptr;
}
curr = curr->children[token];
}
return curr;
}

ZimFSNode::~ZimFSNode()
{
for (auto child : children) {
delete child.second;
}
}

void ZimFS::printInfo()
{
std::cout << "count-entries: " << archive.getEntryCount() << "\n";
std::cout << "uuid: " << archive.getUuid() << "\n";
if (archive.hasChecksum()) {
std::cout << "checksum: " << archive.getChecksum() << "\n";
} else {
std::cout <<"no checksum\n";
}

if (archive.hasMainEntry()) {
std::cout << "main page: " << archive.getMainEntry().getItem(true).getPath() << "\n";
} else {
std::cout << "main page: -\n";
}

if (archive.hasIllustration()) {
std::cout << "favicon: " << archive.getIllustrationItem().getPath() << "\n";
} else {
std::cout << "favicon: -\n";
}

std::cout.flush();
}

ZimFS::ZimFS(const std::string& fname, bool check_intergrity) : archive(fname)
{
if (check_intergrity
&& !archive.checkIntegrity(zim::IntegrityCheck::CHECKSUM)) {
throw std::runtime_error("zimfile intergrity check failed");
}

// Cache all entries
size_t counter = 0;
for (auto& en : archive.iterByPath()) {
root.addEntry(en);
counter++;
}
std::cout << "All entries loaded, total: " << counter << std::endl;
}
42 changes: 42 additions & 0 deletions src/zimmount/zimfs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#ifndef OPENZIM_ZIMFS_H
#define OPENZIM_ZIMFS_H

#include <string>
#include <unordered_map>

#include <zim/archive.h>
#include <zim/item.h>

#define INVALID_INDEX 0xffffffff

class ZimFSNode
{
std::string name;

public:
zim::entry_index_type entry_index;
std::unordered_map<std::string, ZimFSNode*> children;

ZimFSNode();
ZimFSNode(const std::string& name, zim::entry_index_type index);

void addEntry(const zim::Entry& entry);
ZimFSNode* findPath(const std::string& path);


~ZimFSNode();
};

class ZimFS
{
public:

zim::Archive archive;
ZimFSNode root;
ZimFS(const std::string& fname, bool check_intergrity);

void printInfo();
};


#endif //OPENZIM_ZIMFS_H
167 changes: 167 additions & 0 deletions src/zimmount/zimmount.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "zimfs.h"

#define FUSE_USE_VERSION 31
#include <fuse3/fuse.h>

#include <iostream>
#include <string>
#include <vector>

ZimFS* fs;

/* callbacks for fuse below */
static int do_getattr(const char* path,
struct stat* stbuf,
struct fuse_file_info* fi)
{
std::cout << "do_getattr: " << path << std::endl;

ZimFSNode* node = fs->root.findPath(path);

if (node == nullptr) {
return -ENOENT;
}

if (node->entry_index == INVALID_INDEX) {
stbuf->st_mode = S_IFDIR | 0555;
stbuf->st_nlink = 1;
} else {
stbuf->st_mode = S_IFREG | 0444;
stbuf->st_nlink = 1;
stbuf->st_size = fs->archive.getEntryByPath(path).getItem(true).getSize();
}

return 0;
}

static int do_readdir(const char* path,
void* buf,
fuse_fill_dir_t fill_dir,
off_t offset,
struct fuse_file_info* fi,
enum fuse_readdir_flags flags)
{
std::cout << "do_readdir: " << path << std::endl;

ZimFSNode* node = fs->root.findPath(path);

if (node == nullptr) {
return -ENOENT;
}

lyc8503 marked this conversation as resolved.
Show resolved Hide resolved
fill_dir(buf, ".", nullptr, 0, (fuse_fill_dir_flags)0);
fill_dir(buf, "..", nullptr, 0, (fuse_fill_dir_flags)0);

for (auto child : node->children) {
fill_dir(buf, child.first.c_str(), nullptr, 0, (fuse_fill_dir_flags)0);
}

return 0;
}

static int do_open(const char *path, struct fuse_file_info *fi)
{
std::cout << "do_open: " << path << std::endl;

if ((fi->flags & 3) != O_RDONLY) {
return -EACCES;
}

ZimFSNode* node = fs->root.findPath(path);

if (node == nullptr) {
return -ENOENT;
}

return 0;
}

static int do_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
std::cout << "do_read: " << path << std::endl;

ZimFSNode* node = fs->root.findPath(path);

if (node == nullptr) {
return -ENOENT;
}

zim::Entry entry = fs->archive.getEntryByPath(path);
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 struct fuse_operations operations
= {.getattr = do_getattr, .open=do_open, .read=do_read , .readdir = do_readdir};

/* Code for processing custom args below */
struct zimfile_opt {
char* path;
};

#define KEY_PATH 1
#define ZIM_OPT(t, m, v) \
{ \
t, offsetof(struct zimfile_opt, m), v \
}

static int zimfile_opt_proc_fd(void* data,
const char* arg,
int key,
struct fuse_args* outargs)
{
struct zimfile_opt* opt = (struct zimfile_opt*)data;

switch (key) {
case KEY_PATH:
opt->path = strdup(arg);
return 0; /* Discard since it has been read */
}

return 1; /* Keep */
}

struct fuse_args parse_args(int argc, char* argv[])
{
/* Custom args (zimfile path) */
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);

struct zimfile_opt opt;
memset(&opt, 0, sizeof(opt));

struct fuse_opt opts[] = {ZIM_OPT("path=%s", path, KEY_PATH), FUSE_OPT_END};
fuse_opt_parse(&args, &opt, opts, zimfile_opt_proc_fd);

if (opt.path == nullptr) {
std::cerr << "error: no zimfile path specified, use -o "
"path=/path/to/zimfile to specify the zimfile to mount."
<< std::endl;
throw std::runtime_error("no zimfile path specified");
}

/* init global var `fs` */
fs = new ZimFS(opt.path, true);
fs->printInfo();

return args;
}

int main(int argc, char* argv[])
{
struct fuse_args args = parse_args(argc, argv);

/* direct call to fuse_main with custom callbacks */
return fuse_main(args.argc, args.argv, &operations, nullptr);
}
8 changes: 5 additions & 3 deletions test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ tests = [
'metadata-test',
'tools-test',
'zimwriterfs-zimcreatorfs',
'zimcheck-test'
'zimcheck-test',
'zimmount-test'
]

zimwriter_srcs = [ '../src/zimwriterfs/tools.cpp',
Expand All @@ -14,14 +15,15 @@ zimwriter_srcs = [ '../src/zimwriterfs/tools.cpp',
tests_src_map = { 'zimcheck-test' : ['../src/zimcheck/zimcheck.cpp', '../src/zimcheck/checks.cpp', '../src/zimcheck/json_tools.cpp', '../src/tools.cpp', '../src/metadata.cpp'],
'tools-test' : zimwriter_srcs,
'metadata-test' : ['../src/metadata.cpp'],
'zimwriterfs-zimcreatorfs' : zimwriter_srcs }
'zimwriterfs-zimcreatorfs' : zimwriter_srcs,
'zimmount-test': ['../src/zimmount/zimmount.cpp', '../src/zimmount/zimfs.cpp'] }

if gtest_dep.found() and not meson.is_cross_build()

foreach test_name : tests

test_exe = executable(test_name, [test_name+'.cpp'] + tests_src_map[test_name],
dependencies : [gtest_dep, libzim_dep, gumbo_dep, magic_dep, zlib_dep],
dependencies : [gtest_dep, libzim_dep, gumbo_dep, magic_dep, zlib_dep, libfuse_dep],
include_directories: inc,
build_rpath : '$ORIGIN')

Expand Down
11 changes: 11 additions & 0 deletions test/zimmount-test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <sstream>

#include "gtest/gtest.h"

TEST(ZimMountTest, basic)
{
std::stringstream ss;
ss << "Hello, world!";
EXPECT_EQ(ss.str(), "Hello, world!");

}