mirror of
https://github.com/mhx/dwarfs.git
synced 2025-08-04 10:16:34 -04:00
1212 lines
31 KiB
C++
1212 lines
31 KiB
C++
/* vim:set ts=2 sw=2 sts=2 et: */
|
|
/**
|
|
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
|
* \copyright Copyright (c) Marcus Holland-Moritz
|
|
*
|
|
* This file is part of dwarfs.
|
|
*
|
|
* dwarfs is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* dwarfs is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <array>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <folly/Conv.h>
|
|
#include <folly/experimental/symbolizer/SignalHandler.h>
|
|
|
|
#ifndef DWARFS_FUSE_LOWLEVEL
|
|
#define DWARFS_FUSE_LOWLEVEL 1
|
|
#endif
|
|
|
|
#if FUSE_USE_VERSION >= 30
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
#include <fuse3/fuse_lowlevel.h>
|
|
#else
|
|
#include <fuse3/fuse.h>
|
|
#endif
|
|
#else
|
|
#include <fuse.h>
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
#include <fuse/fuse_lowlevel.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include <fuse3/winfsp_fuse.h>
|
|
#define st_atime st_atim.tv_sec
|
|
#define st_ctime st_ctim.tv_sec
|
|
#define st_mtime st_mtim.tv_sec
|
|
#define DWARFS_FSP_COMPAT
|
|
#endif
|
|
|
|
#include "dwarfs/error.h"
|
|
#include "dwarfs/file_stat.h"
|
|
#include "dwarfs/filesystem_v2.h"
|
|
#include "dwarfs/fstypes.h"
|
|
#include "dwarfs/logger.h"
|
|
#include "dwarfs/metadata_v2.h"
|
|
#include "dwarfs/mmap.h"
|
|
#include "dwarfs/options.h"
|
|
#include "dwarfs/tool.h"
|
|
#include "dwarfs/util.h"
|
|
#include "dwarfs/version.h"
|
|
#include "dwarfs/vfs_stat.h"
|
|
#include "dwarfs_tool_main.h"
|
|
|
|
namespace {
|
|
#ifdef DWARFS_FSP_COMPAT
|
|
using native_stat = struct ::fuse_stat;
|
|
using native_statvfs = struct ::fuse_statvfs;
|
|
using native_off_t = ::fuse_off_t;
|
|
#else
|
|
using native_stat = struct ::stat;
|
|
using native_statvfs = struct ::statvfs;
|
|
using native_off_t = ::off_t;
|
|
#endif
|
|
} // namespace
|
|
|
|
namespace dwarfs {
|
|
|
|
struct options {
|
|
char const* progname{nullptr};
|
|
std::string fsimage;
|
|
int seen_mountpoint{0};
|
|
char const* cachesize_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* debuglevel_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* workers_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* mlock_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* decompress_ratio_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* image_offset_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* cache_tidy_strategy_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* cache_tidy_interval_str{nullptr}; // TODO: const?? -> use string?
|
|
char const* cache_tidy_max_age_str{nullptr}; // TODO: const?? -> use string?
|
|
int enable_nlink{0};
|
|
int readonly{0};
|
|
int cache_image{0};
|
|
int cache_files{0};
|
|
size_t cachesize{0};
|
|
size_t workers{0};
|
|
mlock_mode lock_mode{mlock_mode::NONE};
|
|
double decompress_ratio{0.0};
|
|
logger::level_type debuglevel{logger::level_type::ERROR};
|
|
cache_tidy_strategy block_cache_tidy_strategy{cache_tidy_strategy::NONE};
|
|
std::chrono::milliseconds block_cache_tidy_interval{std::chrono::minutes(5)};
|
|
std::chrono::milliseconds block_cache_tidy_max_age{std::chrono::minutes{10}};
|
|
};
|
|
|
|
struct dwarfs_userdata {
|
|
explicit dwarfs_userdata(std::ostream& os)
|
|
: lgr{os} {}
|
|
|
|
options opts;
|
|
stream_logger lgr;
|
|
filesystem_v2 fs;
|
|
};
|
|
|
|
// TODO: better error handling
|
|
|
|
#define DWARFS_OPT(t, p, v) \
|
|
{ t, offsetof(struct options, p), v }
|
|
|
|
constexpr struct ::fuse_opt dwarfs_opts[] = {
|
|
// TODO: user, group, atime, mtime, ctime for those fs who don't have it?
|
|
DWARFS_OPT("cachesize=%s", cachesize_str, 0),
|
|
DWARFS_OPT("debuglevel=%s", debuglevel_str, 0),
|
|
DWARFS_OPT("workers=%s", workers_str, 0),
|
|
DWARFS_OPT("mlock=%s", mlock_str, 0),
|
|
DWARFS_OPT("decratio=%s", decompress_ratio_str, 0),
|
|
DWARFS_OPT("offset=%s", image_offset_str, 0),
|
|
DWARFS_OPT("tidy_strategy=%s", cache_tidy_strategy_str, 0),
|
|
DWARFS_OPT("tidy_interval=%s", cache_tidy_interval_str, 0),
|
|
DWARFS_OPT("tidy_max_age=%s", cache_tidy_max_age_str, 0),
|
|
DWARFS_OPT("enable_nlink", enable_nlink, 1),
|
|
DWARFS_OPT("readonly", readonly, 1),
|
|
DWARFS_OPT("cache_image", cache_image, 1),
|
|
DWARFS_OPT("no_cache_image", cache_image, 0),
|
|
DWARFS_OPT("cache_files", cache_files, 1),
|
|
DWARFS_OPT("no_cache_files", cache_files, 0),
|
|
FUSE_OPT_END};
|
|
|
|
std::unordered_map<std::string_view, cache_tidy_strategy> const
|
|
cache_tidy_strategy_map{
|
|
{"none", cache_tidy_strategy::NONE},
|
|
{"time", cache_tidy_strategy::EXPIRY_TIME},
|
|
{"swap", cache_tidy_strategy::BLOCK_SWAPPED_OUT},
|
|
};
|
|
|
|
namespace {
|
|
|
|
constexpr std::string_view pid_xattr{"user.dwarfs.driver.pid"};
|
|
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
#define dUSERDATA \
|
|
auto userdata = reinterpret_cast<dwarfs_userdata*>(fuse_req_userdata(req))
|
|
#else
|
|
#define dUSERDATA \
|
|
auto userdata = \
|
|
reinterpret_cast<dwarfs_userdata*>(fuse_get_context()->private_data)
|
|
#endif
|
|
|
|
template <typename LoggerPolicy>
|
|
void op_init_common(void* data) {
|
|
auto userdata = reinterpret_cast<dwarfs_userdata*>(data);
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
// we must do this *after* the fuse driver has forked into background
|
|
userdata->fs.set_num_workers(userdata->opts.workers);
|
|
|
|
cache_tidy_config tidy;
|
|
tidy.strategy = userdata->opts.block_cache_tidy_strategy;
|
|
tidy.interval = userdata->opts.block_cache_tidy_interval;
|
|
tidy.expiry_time = userdata->opts.block_cache_tidy_max_age;
|
|
|
|
// we must do this *after* the fuse driver has forked into background
|
|
userdata->fs.set_cache_tidy_config(tidy);
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_init(void* data, struct fuse_conn_info* /*conn*/) {
|
|
op_init_common<LoggerPolicy>(data);
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
void* op_init(struct fuse_conn_info* /*conn*/, struct fuse_config* /*cfg*/) {
|
|
// TODO: config?
|
|
auto userdata = fuse_get_context()->private_data;
|
|
op_init_common<LoggerPolicy>(userdata);
|
|
return userdata;
|
|
}
|
|
#endif
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_lookup(fuse_req_t req, fuse_ino_t parent, char const* name) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << parent << ", " << name << ")";
|
|
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
auto entry = userdata->fs.find(parent, name);
|
|
|
|
if (entry) {
|
|
file_stat stbuf;
|
|
|
|
err = userdata->fs.getattr(*entry, &stbuf);
|
|
|
|
if (err == 0) {
|
|
struct ::fuse_entry_param e;
|
|
|
|
copy_file_stat(&e.attr, stbuf);
|
|
e.generation = 1;
|
|
e.ino = e.attr.st_ino;
|
|
e.attr_timeout = std::numeric_limits<double>::max();
|
|
e.entry_timeout = std::numeric_limits<double>::max();
|
|
|
|
fuse_reply_entry(req, &e);
|
|
|
|
return;
|
|
}
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
fuse_reply_err(req, err);
|
|
}
|
|
#endif
|
|
|
|
template <typename LogProxy, typename Find>
|
|
int op_getattr_common(LogProxy& log_, dwarfs_userdata* userdata,
|
|
native_stat* st, Find const& find) {
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
auto entry = find();
|
|
|
|
if (entry) {
|
|
file_stat stbuf;
|
|
|
|
err = userdata->fs.getattr(*entry, &stbuf);
|
|
|
|
if (err == 0) {
|
|
::memset(st, 0, sizeof(*st));
|
|
copy_file_stat(st, stbuf);
|
|
}
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info*) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << ino << ")";
|
|
|
|
native_stat st;
|
|
|
|
int err = op_getattr_common(
|
|
log_, userdata, &st, [userdata, ino] { return userdata->fs.find(ino); });
|
|
|
|
if (err == 0) {
|
|
fuse_reply_attr(req, &st, std::numeric_limits<double>::max());
|
|
} else {
|
|
fuse_reply_err(req, err);
|
|
}
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_getattr(char const* path, native_stat* st, struct fuse_file_info*) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ")";
|
|
|
|
return -op_getattr_common(
|
|
log_, userdata, st, [userdata, path] { return userdata->fs.find(path); });
|
|
}
|
|
#endif
|
|
|
|
template <typename LogProxy, typename Find>
|
|
int op_access_common(LogProxy& log_, dwarfs_userdata* userdata, int mode,
|
|
uid_t uid, gid_t gid, Find const& find) {
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
if (auto entry = find()) {
|
|
err = userdata->fs.access(*entry, mode, uid, gid);
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_access(fuse_req_t req, fuse_ino_t ino, int mode) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << ino << ")";
|
|
|
|
auto ctx = fuse_req_ctx(req);
|
|
|
|
int err =
|
|
op_access_common(log_, userdata, mode, ctx->uid, ctx->gid,
|
|
[userdata, ino] { return userdata->fs.find(ino); });
|
|
|
|
fuse_reply_err(req, err);
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_access(char const* path, int mode) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ")";
|
|
|
|
auto ctx = fuse_get_context();
|
|
|
|
return -op_access_common(
|
|
log_, userdata, mode, ctx->uid, ctx->gid,
|
|
[userdata, path] { return userdata->fs.find(path); });
|
|
}
|
|
#endif
|
|
|
|
template <typename LogProxy, typename Find>
|
|
int op_readlink_common(LogProxy& log_, dwarfs_userdata* userdata,
|
|
std::string* str, Find const& find) {
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
if (auto entry = find()) {
|
|
err = userdata->fs.readlink(*entry, str);
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_readlink(fuse_req_t req, fuse_ino_t ino) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
std::string symlink;
|
|
|
|
auto err = op_readlink_common(log_, userdata, &symlink, [userdata, ino] {
|
|
return userdata->fs.find(ino);
|
|
});
|
|
|
|
if (err == 0) {
|
|
fuse_reply_readlink(req, symlink.c_str());
|
|
} else {
|
|
fuse_reply_err(req, err);
|
|
}
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_readlink(char const* path, char* buf, size_t buflen) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
std::string symlink;
|
|
|
|
auto err = op_readlink_common(log_, userdata, &symlink, [userdata, path] {
|
|
return userdata->fs.find(path);
|
|
});
|
|
|
|
if (err == 0) {
|
|
::strncpy_s(buf, buflen, symlink.data(), symlink.size());
|
|
}
|
|
|
|
return -err;
|
|
}
|
|
#endif
|
|
|
|
template <typename LogProxy, typename Find>
|
|
int op_open_common(LogProxy& log_, dwarfs_userdata* userdata,
|
|
struct fuse_file_info* fi, Find const& find) {
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
auto entry = find();
|
|
|
|
if (entry) {
|
|
if (entry->is_directory()) {
|
|
err = EISDIR;
|
|
} else if (fi->flags & (O_APPEND | O_CREAT | O_TRUNC)) {
|
|
err = EACCES;
|
|
} else {
|
|
fi->fh = entry->inode_num();
|
|
fi->direct_io = !userdata->opts.cache_files;
|
|
fi->keep_cache = userdata->opts.cache_files;
|
|
return 0;
|
|
}
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
auto err = op_open_common(log_, userdata, fi,
|
|
[userdata, ino] { return userdata->fs.find(ino); });
|
|
|
|
if (err == 0) {
|
|
fuse_reply_open(req, fi);
|
|
} else {
|
|
fuse_reply_err(req, err);
|
|
}
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_open(char const* path, struct fuse_file_info* fi) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
return -op_open_common(log_, userdata, fi,
|
|
[userdata, path] { return userdata->fs.find(path); });
|
|
}
|
|
#endif
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_read(fuse_req_t req, fuse_ino_t ino, size_t size, file_off_t off,
|
|
struct fuse_file_info* fi) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
if (FUSE_ROOT_ID + fi->fh == ino) {
|
|
iovec_read_buf buf;
|
|
ssize_t rv = userdata->fs.readv(ino, buf, size, off);
|
|
|
|
LOG_DEBUG << "readv(" << ino << ", " << size << ", " << off << ") -> "
|
|
<< rv << " [size = " << buf.buf.size() << "]";
|
|
|
|
if (rv >= 0) {
|
|
int frv = fuse_reply_iov(req, buf.buf.empty() ? nullptr : &buf.buf[0],
|
|
buf.buf.size());
|
|
|
|
if (frv == 0) {
|
|
return;
|
|
}
|
|
|
|
err = -frv;
|
|
} else {
|
|
err = -rv;
|
|
}
|
|
} else {
|
|
err = EIO;
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
fuse_reply_err(req, err);
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_read(char const* path, char* buf, size_t size, native_off_t off,
|
|
struct fuse_file_info* fi) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
int err = -ENOENT;
|
|
|
|
try {
|
|
ssize_t rv = userdata->fs.read(fi->fh, buf, size, off);
|
|
|
|
LOG_DEBUG << "read(" << path << " [" << fi->fh << "], " << size << ", "
|
|
<< off << ") -> " << rv;
|
|
|
|
if (rv >= 0) {
|
|
return rv;
|
|
}
|
|
|
|
err = rv;
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, file_off_t off,
|
|
struct fuse_file_info* /*fi*/) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << ino << ", " << size << ", " << off << ")";
|
|
|
|
int err = ENOENT;
|
|
|
|
try {
|
|
auto dirent = userdata->fs.find(ino);
|
|
|
|
if (dirent) {
|
|
auto dir = userdata->fs.opendir(*dirent);
|
|
|
|
if (dir) {
|
|
file_off_t lastoff = userdata->fs.dirsize(*dir);
|
|
file_stat stbuf;
|
|
native_stat st;
|
|
std::vector<char> buf(size);
|
|
size_t written = 0;
|
|
|
|
::memset(&st, 0, sizeof(st));
|
|
|
|
while (off < lastoff && written < size) {
|
|
auto res = userdata->fs.readdir(*dir, off);
|
|
assert(res);
|
|
|
|
auto [entry, name_view] = *res;
|
|
std::string name(name_view);
|
|
|
|
userdata->fs.getattr(entry, &stbuf);
|
|
copy_file_stat(&st, stbuf);
|
|
|
|
assert(written < buf.size());
|
|
|
|
size_t needed =
|
|
fuse_add_direntry(req, &buf[written], buf.size() - written,
|
|
name.c_str(), &st, off + 1);
|
|
|
|
if (written + needed > buf.size()) {
|
|
break;
|
|
}
|
|
|
|
written += needed;
|
|
++off;
|
|
}
|
|
|
|
fuse_reply_buf(req, written > 0 ? &buf[0] : nullptr, written);
|
|
|
|
return;
|
|
}
|
|
|
|
err = ENOTDIR;
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
fuse_reply_err(req, err);
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_readdir(char const* path, void* buf, fuse_fill_dir_t filler,
|
|
native_off_t off, struct fuse_file_info* /*fi*/,
|
|
enum fuse_readdir_flags /*flags*/) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ")";
|
|
|
|
int err = -ENOENT;
|
|
|
|
try {
|
|
auto dirent = userdata->fs.find(path);
|
|
|
|
if (dirent) {
|
|
auto dir = userdata->fs.opendir(*dirent);
|
|
|
|
if (dir) {
|
|
file_off_t lastoff = userdata->fs.dirsize(*dir);
|
|
file_stat stbuf;
|
|
native_stat st;
|
|
|
|
::memset(&st, 0, sizeof(st));
|
|
|
|
while (off < lastoff) {
|
|
auto res = userdata->fs.readdir(*dir, off);
|
|
assert(res);
|
|
|
|
auto [entry, name_view] = *res;
|
|
std::string name(name_view);
|
|
|
|
userdata->fs.getattr(entry, &stbuf);
|
|
copy_file_stat(&st, stbuf);
|
|
|
|
if (filler(buf, name.c_str(), &st, off + 1, FUSE_FILL_DIR_PLUS) !=
|
|
0) {
|
|
break;
|
|
}
|
|
|
|
++off;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
err = -ENOTDIR;
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
template <typename LogProxy>
|
|
int op_statfs_common(LogProxy& log_, dwarfs_userdata* userdata,
|
|
native_statvfs* st) {
|
|
int err = EIO;
|
|
|
|
try {
|
|
vfs_stat stbuf;
|
|
|
|
err = userdata->fs.statvfs(&stbuf);
|
|
|
|
if (err == 0) {
|
|
::memset(st, 0, sizeof(*st));
|
|
copy_vfs_stat(st, stbuf);
|
|
|
|
#ifndef _WIN32
|
|
if (stbuf.readonly) {
|
|
st->f_flag |= ST_RDONLY;
|
|
}
|
|
#endif
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_statfs(fuse_req_t req, fuse_ino_t /*ino*/) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__;
|
|
|
|
struct ::statvfs st;
|
|
|
|
int err = op_statfs_common(log_, userdata, &st);
|
|
|
|
if (err == 0) {
|
|
fuse_reply_statfs(req, &st);
|
|
} else {
|
|
fuse_reply_err(req, err);
|
|
}
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_statfs(char const* path, native_statvfs* st) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ")";
|
|
|
|
return -op_statfs_common(log_, userdata, st);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void op_getxattr(fuse_req_t req, fuse_ino_t ino, char const* name,
|
|
size_t size) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << ino << ", " << name << ", " << size << ")";
|
|
|
|
int err = ENODATA;
|
|
|
|
try {
|
|
if (ino == FUSE_ROOT_ID && name == pid_xattr) {
|
|
auto pidstr = std::to_string(::getpid());
|
|
if (size > 0) {
|
|
fuse_reply_buf(req, pidstr.data(), pidstr.size());
|
|
} else {
|
|
fuse_reply_xattr(req, pidstr.size());
|
|
}
|
|
return;
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = EIO;
|
|
}
|
|
|
|
fuse_reply_err(req, err);
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
int op_getxattr(char const* path, char const* name, char* value, size_t size) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ", " << name << ", " << size << ")";
|
|
|
|
int err = -ENODATA;
|
|
|
|
try {
|
|
std::string_view pv(path);
|
|
|
|
if ((pv == "" || pv == "/") && name == pid_xattr) {
|
|
auto pidstr = std::to_string(::_getpid());
|
|
if (size == 0) {
|
|
return pidstr.size();
|
|
} else if (size < pidstr.size()) {
|
|
return -ERANGE;
|
|
}
|
|
::strncpy_s(value, size, pidstr.data(), pidstr.size());
|
|
return pidstr.size();
|
|
}
|
|
} catch (dwarfs::system_error const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -e.get_errno();
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << e.what();
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
template <typename LoggerPolicy>
|
|
int op_listxattr(char const* path, char* list, size_t size) {
|
|
dUSERDATA;
|
|
LOG_PROXY(LoggerPolicy, userdata->lgr);
|
|
|
|
LOG_DEBUG << __func__ << "(" << path << ", " << size << ")";
|
|
|
|
const std::string all_xattr = std::string(pid_xattr) + '\0';
|
|
|
|
if (size > 0) {
|
|
if (size < all_xattr.size()) {
|
|
return -ERANGE;
|
|
}
|
|
|
|
::memcpy(list, all_xattr.data(), all_xattr.size());
|
|
}
|
|
|
|
return all_xattr.size();
|
|
}
|
|
#endif
|
|
|
|
void usage(char const* progname) {
|
|
std::cerr
|
|
<< tool_header("dwarfs",
|
|
fmt::format(", fuse version {}", FUSE_USE_VERSION))
|
|
#if !DWARFS_FUSE_LOWLEVEL
|
|
<< "USING HIGH-LEVEL FUSE API\n\n"
|
|
#endif
|
|
<< "usage: " << progname << " image mountpoint [options]\n\n"
|
|
<< "DWARFS options:\n"
|
|
<< " -o cachesize=SIZE set size of block cache (512M)\n"
|
|
<< " -o workers=NUM number of worker threads (2)\n"
|
|
<< " -o mlock=NAME mlock mode: (none), try, must\n"
|
|
<< " -o decratio=NUM ratio for full decompression (0.8)\n"
|
|
<< " -o offset=NUM|auto filesystem image offset in bytes (0)\n"
|
|
<< " -o enable_nlink show correct hardlink numbers\n"
|
|
<< " -o readonly show read-only file system\n"
|
|
<< " -o (no_)cache_image (don't) keep image in kernel cache\n"
|
|
<< " -o (no_)cache_files (don't) keep files in kernel cache\n"
|
|
<< " -o debuglevel=NAME error, warn, info, debug, trace\n"
|
|
<< " -o tidy_strategy=NAME (none)|time|swap\n"
|
|
<< " -o tidy_interval=TIME interval for cache tidying (5m)\n"
|
|
<< " -o tidy_max_age=TIME tidy blocks after this time (10m)\n"
|
|
<< std::endl;
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL && FUSE_USE_VERSION >= 30
|
|
fuse_cmdline_help();
|
|
#else
|
|
struct fuse_args args = FUSE_ARGS_INIT(0, nullptr);
|
|
fuse_opt_add_arg(&args, "");
|
|
fuse_opt_add_arg(&args, "-h");
|
|
struct fuse_operations fsops;
|
|
::memset(&fsops, 0, sizeof(fsops));
|
|
fuse_main(args.argc, args.argv, &fsops, nullptr);
|
|
fuse_opt_free_args(&args);
|
|
#endif
|
|
|
|
::exit(1);
|
|
}
|
|
|
|
int option_hdl(void* data, char const* arg, int key,
|
|
struct fuse_args* /*outargs*/) {
|
|
auto* opts = reinterpret_cast<options*>(data);
|
|
|
|
switch (key) {
|
|
case FUSE_OPT_KEY_NONOPT:
|
|
if (opts->seen_mountpoint) {
|
|
return -1;
|
|
}
|
|
|
|
if (!opts->fsimage.empty()) {
|
|
opts->seen_mountpoint = 1;
|
|
return 1;
|
|
}
|
|
|
|
opts->fsimage = arg;
|
|
|
|
return 0;
|
|
|
|
case FUSE_OPT_KEY_OPT:
|
|
if (::strncmp(arg, "-h", 2) == 0 || ::strncmp(arg, "--help", 6) == 0) {
|
|
usage(opts->progname);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
template <typename LoggerPolicy>
|
|
void init_fuse_ops(struct fuse_lowlevel_ops& ops) {
|
|
ops.init = &op_init<LoggerPolicy>;
|
|
ops.lookup = &op_lookup<LoggerPolicy>;
|
|
ops.getattr = &op_getattr<LoggerPolicy>;
|
|
ops.access = &op_access<LoggerPolicy>;
|
|
ops.readlink = &op_readlink<LoggerPolicy>;
|
|
ops.open = &op_open<LoggerPolicy>;
|
|
ops.read = &op_read<LoggerPolicy>;
|
|
ops.readdir = &op_readdir<LoggerPolicy>;
|
|
ops.statfs = &op_statfs<LoggerPolicy>;
|
|
ops.getxattr = &op_getxattr<LoggerPolicy>;
|
|
// ops.listxattr = &op_listxattr<LoggerPolicy>;
|
|
}
|
|
#else
|
|
template <typename LoggerPolicy>
|
|
void init_fuse_ops(struct fuse_operations& ops) {
|
|
ops.init = &op_init<LoggerPolicy>;
|
|
ops.getattr = &op_getattr<LoggerPolicy>;
|
|
ops.access = &op_access<LoggerPolicy>;
|
|
ops.readlink = &op_readlink<LoggerPolicy>;
|
|
ops.open = &op_open<LoggerPolicy>;
|
|
ops.read = &op_read<LoggerPolicy>;
|
|
ops.readdir = &op_readdir<LoggerPolicy>;
|
|
ops.statfs = &op_statfs<LoggerPolicy>;
|
|
ops.getxattr = &op_getxattr<LoggerPolicy>;
|
|
ops.listxattr = &op_listxattr<LoggerPolicy>;
|
|
}
|
|
#endif
|
|
|
|
#if FUSE_USE_VERSION > 30
|
|
|
|
int run_fuse(struct fuse_args& args,
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
struct fuse_cmdline_opts const& fuse_opts,
|
|
#endif
|
|
dwarfs_userdata& userdata) {
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
struct fuse_lowlevel_ops fsops;
|
|
#else
|
|
struct fuse_operations fsops;
|
|
#endif
|
|
|
|
::memset(&fsops, 0, sizeof(fsops));
|
|
|
|
if (userdata.opts.debuglevel >= logger::DEBUG) {
|
|
init_fuse_ops<debug_logger_policy>(fsops);
|
|
} else {
|
|
init_fuse_ops<prod_logger_policy>(fsops);
|
|
}
|
|
|
|
int err = 1;
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
if (auto session =
|
|
fuse_session_new(&args, &fsops, sizeof(fsops), &userdata)) {
|
|
if (fuse_set_signal_handlers(session) == 0) {
|
|
if (fuse_session_mount(session, fuse_opts.mountpoint) == 0) {
|
|
if (fuse_daemonize(fuse_opts.foreground) == 0) {
|
|
if (fuse_opts.singlethread) {
|
|
err = fuse_session_loop(session);
|
|
} else {
|
|
struct fuse_loop_config config;
|
|
config.clone_fd = fuse_opts.clone_fd;
|
|
config.max_idle_threads = fuse_opts.max_idle_threads;
|
|
err = fuse_session_loop_mt(session, &config);
|
|
}
|
|
}
|
|
fuse_session_unmount(session);
|
|
}
|
|
fuse_remove_signal_handlers(session);
|
|
}
|
|
fuse_session_destroy(session);
|
|
}
|
|
|
|
::free(fuse_opts.mountpoint);
|
|
#else
|
|
err = fuse_main(args.argc, args.argv, &fsops, &userdata);
|
|
#endif
|
|
|
|
fuse_opt_free_args(&args);
|
|
|
|
return err;
|
|
}
|
|
|
|
#else
|
|
|
|
int run_fuse(struct fuse_args& args, char* mountpoint, int mt, int fg,
|
|
dwarfs_userdata& userdata) {
|
|
struct fuse_lowlevel_ops fsops;
|
|
|
|
::memset(&fsops, 0, sizeof(fsops));
|
|
|
|
if (userdata.opts.debuglevel >= logger::DEBUG) {
|
|
init_fuse_ops<debug_logger_policy>(fsops);
|
|
} else {
|
|
init_fuse_ops<prod_logger_policy>(fsops);
|
|
}
|
|
|
|
int err = 1;
|
|
|
|
if (auto ch = fuse_mount(mountpoint, &args)) {
|
|
if (auto se = fuse_lowlevel_new(&args, &fsops, sizeof(fsops), &userdata)) {
|
|
if (fuse_daemonize(fg) != -1) {
|
|
if (fuse_set_signal_handlers(se) != -1) {
|
|
fuse_session_add_chan(se, ch);
|
|
err = mt ? fuse_session_loop_mt(se) : fuse_session_loop(se);
|
|
fuse_remove_signal_handlers(se);
|
|
fuse_session_remove_chan(ch);
|
|
}
|
|
}
|
|
fuse_session_destroy(se);
|
|
}
|
|
fuse_unmount(mountpoint, ch);
|
|
}
|
|
|
|
::free(mountpoint);
|
|
fuse_opt_free_args(&args);
|
|
|
|
return err;
|
|
}
|
|
|
|
#endif
|
|
|
|
template <typename LoggerPolicy>
|
|
void load_filesystem(dwarfs_userdata& userdata) {
|
|
LOG_PROXY(LoggerPolicy, userdata.lgr);
|
|
auto ti = LOG_TIMED_INFO;
|
|
auto& opts = userdata.opts;
|
|
|
|
filesystem_options fsopts;
|
|
fsopts.lock_mode = opts.lock_mode;
|
|
fsopts.block_cache.max_bytes = opts.cachesize;
|
|
fsopts.block_cache.num_workers = opts.workers;
|
|
fsopts.block_cache.decompress_ratio = opts.decompress_ratio;
|
|
fsopts.block_cache.mm_release = !opts.cache_image;
|
|
fsopts.block_cache.init_workers = false;
|
|
fsopts.metadata.enable_nlink = bool(opts.enable_nlink);
|
|
fsopts.metadata.readonly = bool(opts.readonly);
|
|
|
|
if (opts.image_offset_str) {
|
|
std::string image_offset{opts.image_offset_str};
|
|
|
|
try {
|
|
fsopts.image_offset = image_offset == "auto"
|
|
? filesystem_options::IMAGE_OFFSET_AUTO
|
|
: folly::to<file_off_t>(image_offset);
|
|
} catch (...) {
|
|
DWARFS_THROW(runtime_error, "failed to parse offset: " + image_offset);
|
|
}
|
|
}
|
|
|
|
constexpr int inode_offset =
|
|
#ifdef FUSE_ROOT_ID
|
|
FUSE_ROOT_ID
|
|
#else
|
|
0
|
|
#endif
|
|
;
|
|
|
|
userdata.fs = filesystem_v2(
|
|
userdata.lgr, std::make_shared<mmap>(opts.fsimage), fsopts, inode_offset);
|
|
|
|
ti << "file system initialized";
|
|
}
|
|
|
|
int dwarfs_main(int argc, char** argv) {
|
|
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
|
dwarfs_userdata userdata(std::cerr);
|
|
auto& opts = userdata.opts;
|
|
|
|
opts.progname = argv[0];
|
|
opts.cache_image = 0;
|
|
opts.cache_files = 1;
|
|
|
|
fuse_opt_parse(&args, &opts, dwarfs_opts, option_hdl);
|
|
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
#if FUSE_USE_VERSION >= 30
|
|
struct fuse_cmdline_opts fuse_opts;
|
|
|
|
if (fuse_parse_cmdline(&args, &fuse_opts) == -1 || !fuse_opts.mountpoint) {
|
|
usage(opts.progname);
|
|
}
|
|
|
|
if (fuse_opts.foreground) {
|
|
folly::symbolizer::installFatalSignalHandler();
|
|
}
|
|
|
|
bool foreground = fuse_opts.foreground;
|
|
#else
|
|
char* mountpoint = nullptr;
|
|
int mt, fg;
|
|
|
|
if (fuse_parse_cmdline(&args, &mountpoint, &mt, &fg) == -1 || !mountpoint) {
|
|
usage(opts.progname);
|
|
}
|
|
|
|
if (fg) {
|
|
folly::symbolizer::installFatalSignalHandler();
|
|
}
|
|
|
|
bool foreground = fg;
|
|
#endif
|
|
#endif
|
|
|
|
try {
|
|
// TODO: foreground mode, stderr vs. syslog?
|
|
|
|
opts.fsimage = std::filesystem::canonical(opts.fsimage).string();
|
|
|
|
if (opts.debuglevel_str) {
|
|
opts.debuglevel = logger::parse_level(opts.debuglevel_str);
|
|
} else {
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
opts.debuglevel = foreground ? logger::INFO : logger::WARN;
|
|
#else
|
|
opts.debuglevel = logger::WARN;
|
|
#endif
|
|
}
|
|
|
|
userdata.lgr.set_threshold(opts.debuglevel);
|
|
userdata.lgr.set_with_context(opts.debuglevel >= logger::DEBUG);
|
|
|
|
opts.cachesize = opts.cachesize_str
|
|
? parse_size_with_unit(opts.cachesize_str)
|
|
: (static_cast<size_t>(512) << 20);
|
|
opts.workers = opts.workers_str ? folly::to<size_t>(opts.workers_str) : 2;
|
|
opts.lock_mode =
|
|
opts.mlock_str ? parse_mlock_mode(opts.mlock_str) : mlock_mode::NONE;
|
|
opts.decompress_ratio = opts.decompress_ratio_str
|
|
? folly::to<double>(opts.decompress_ratio_str)
|
|
: 0.8;
|
|
|
|
if (opts.cache_tidy_strategy_str) {
|
|
if (auto it = cache_tidy_strategy_map.find(opts.cache_tidy_strategy_str);
|
|
it != cache_tidy_strategy_map.end()) {
|
|
opts.block_cache_tidy_strategy = it->second;
|
|
} else {
|
|
std::cerr << "error: no such cache tidy strategy: "
|
|
<< opts.cache_tidy_strategy_str << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if (opts.cache_tidy_interval_str) {
|
|
opts.block_cache_tidy_interval =
|
|
parse_time_with_unit(opts.cache_tidy_interval_str);
|
|
}
|
|
|
|
if (opts.cache_tidy_max_age_str) {
|
|
opts.block_cache_tidy_max_age =
|
|
parse_time_with_unit(opts.cache_tidy_max_age_str);
|
|
}
|
|
}
|
|
} catch (runtime_error const& e) {
|
|
std::cerr << "error: " << e.what() << std::endl;
|
|
return 1;
|
|
} catch (std::filesystem::filesystem_error const& e) {
|
|
std::cerr << e.what() << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if (opts.decompress_ratio < 0.0 || opts.decompress_ratio > 1.0) {
|
|
std::cerr << "error: decratio must be between 0.0 and 1.0" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if (!opts.seen_mountpoint) {
|
|
usage(opts.progname);
|
|
}
|
|
|
|
LOG_PROXY(debug_logger_policy, userdata.lgr);
|
|
|
|
LOG_INFO << "dwarfs (" << PRJ_GIT_ID << ", fuse version " << FUSE_USE_VERSION
|
|
<< ")";
|
|
|
|
try {
|
|
if (userdata.opts.debuglevel >= logger::DEBUG) {
|
|
load_filesystem<debug_logger_policy>(userdata);
|
|
} else {
|
|
load_filesystem<prod_logger_policy>(userdata);
|
|
}
|
|
} catch (std::exception const& e) {
|
|
LOG_ERROR << "error initializing file system: " << e.what();
|
|
return 1;
|
|
}
|
|
|
|
#if FUSE_USE_VERSION >= 30
|
|
#if DWARFS_FUSE_LOWLEVEL
|
|
return run_fuse(args, fuse_opts, userdata);
|
|
#else
|
|
return run_fuse(args, userdata);
|
|
#endif
|
|
#else
|
|
return run_fuse(args, mountpoint, mt, fg, userdata);
|
|
#endif
|
|
}
|
|
|
|
} // namespace dwarfs
|