blob: 5552480aa4a3dd2eef1da00e4f2a1a8a911fe5a6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* erofs-utils/fuse/main.c
*
* Created by Li Guifu <blucerlee@gmail.com>
*/
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <libgen.h>
#include <fuse.h>
#include <fuse_opt.h>
#include "macosx.h"
#include "erofs/config.h"
#include "erofs/print.h"
#include "erofs/io.h"
int erofsfuse_readdir(const char *path, void *buffer, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi);
static void *erofsfuse_init(struct fuse_conn_info *info)
{
erofs_info("Using FUSE protocol %d.%d", info->proto_major, info->proto_minor);
return NULL;
}
static int erofsfuse_open(const char *path, struct fuse_file_info *fi)
{
erofs_dbg("open path=%s", path);
if ((fi->flags & O_ACCMODE) != O_RDONLY)
return -EACCES;
return 0;
}
static int erofsfuse_getattr(const char *path, struct stat *stbuf)
{
struct erofs_inode vi = {};
int ret;
erofs_dbg("getattr(%s)", path);
ret = erofs_ilookup(path, &vi);
if (ret)
return -ENOENT;
stbuf->st_mode = vi.i_mode;
stbuf->st_nlink = vi.i_nlink;
stbuf->st_size = vi.i_size;
stbuf->st_blocks = roundup(vi.i_size, EROFS_BLKSIZ) >> 9;
stbuf->st_uid = vi.i_uid;
stbuf->st_gid = vi.i_gid;
if (S_ISBLK(vi.i_mode) || S_ISCHR(vi.i_mode))
stbuf->st_rdev = vi.u.i_rdev;
stbuf->st_ctime = vi.i_ctime;
stbuf->st_mtime = stbuf->st_ctime;
stbuf->st_atime = stbuf->st_ctime;
return 0;
}
static int erofsfuse_read(const char *path, char *buffer,
size_t size, off_t offset,
struct fuse_file_info *fi)
{
int ret;
struct erofs_inode vi;
erofs_dbg("path:%s size=%zd offset=%llu", path, size, (long long)offset);
ret = erofs_ilookup(path, &vi);
if (ret)
return ret;
ret = erofs_pread(&vi, buffer, size, offset);
if (ret)
return ret;
if (offset >= vi.i_size)
return 0;
if (offset + size > vi.i_size)
return vi.i_size - offset;
return size;
}
static int erofsfuse_readlink(const char *path, char *buffer, size_t size)
{
int ret = erofsfuse_read(path, buffer, size, 0, NULL);
if (ret < 0)
return ret;
DBG_BUGON(ret > size);
if (ret == size)
buffer[size - 1] = '\0';
erofs_dbg("readlink(%s): %s", path, buffer);
return 0;
}
static struct fuse_operations erofs_ops = {
.readlink = erofsfuse_readlink,
.getattr = erofsfuse_getattr,
.readdir = erofsfuse_readdir,
.open = erofsfuse_open,
.read = erofsfuse_read,
.init = erofsfuse_init,
};
static struct options {
const char *disk;
const char *mountpoint;
unsigned int debug_lvl;
bool show_help;
bool odebug;
} fusecfg;
#define OPTION(t, p) \
{ t, offsetof(struct options, p), 1 }
static const struct fuse_opt option_spec[] = {
OPTION("--dbglevel=%u", debug_lvl),
OPTION("--help", show_help),
FUSE_OPT_END
};
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
static void usage(void)
{
struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
fputs("usage: [options] IMAGE MOUNTPOINT\n\n"
"Options:\n"
" --dbglevel=# set output message level to # (maximum 9)\n"
#if FUSE_MAJOR_VERSION < 3
" --help display this help and exit\n"
#endif
"\n", stderr);
#if FUSE_MAJOR_VERSION >= 3
fuse_cmdline_help();
#else
fuse_opt_add_arg(&args, ""); /* progname */
fuse_opt_add_arg(&args, "-ho"); /* progname */
fuse_parse_cmdline(&args, NULL, NULL, NULL);
#endif
exit(EXIT_FAILURE);
}
static void erofsfuse_dumpcfg(void)
{
erofs_dump("disk: %s\n", fusecfg.disk);
erofs_dump("mountpoint: %s\n", fusecfg.mountpoint);
erofs_dump("dbglevel: %u\n", cfg.c_dbg_lvl);
}
static int optional_opt_func(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
switch (key) {
case FUSE_OPT_KEY_NONOPT:
if (fusecfg.mountpoint)
return -1; /* Too many args */
if (!fusecfg.disk) {
fusecfg.disk = strdup(arg);
return 0;
}
if (!fusecfg.mountpoint)
fusecfg.mountpoint = strdup(arg);
case FUSE_OPT_KEY_OPT:
if (!strcmp(arg, "-d"))
fusecfg.odebug = true;
break;
default:
DBG_BUGON(1);
break;
}
return 1;
}
#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
#include <execinfo.h>
static void signal_handle_sigsegv(int signal)
{
void *array[10];
size_t nptrs;
char **strings;
size_t i;
erofs_dump("========================================\n");
erofs_dump("Segmentation Fault. Starting backtrace:\n");
nptrs = backtrace(array, 10);
strings = backtrace_symbols(array, nptrs);
if (strings) {
for (i = 0; i < nptrs; i++)
erofs_dump("%s\n", strings[i]);
free(strings);
}
erofs_dump("========================================\n");
abort();
}
#endif
int main(int argc, char *argv[])
{
int ret;
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
erofs_init_configure();
fprintf(stderr, "%s %s\n", basename(argv[0]), cfg.c_version);
#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
if (signal(SIGSEGV, signal_handle_sigsegv) == SIG_ERR) {
fprintf(stderr, "failed to initialize signals\n");
ret = -errno;
goto err;
}
#endif
/* parse options */
ret = fuse_opt_parse(&args, &fusecfg, option_spec, optional_opt_func);
if (ret)
goto err;
if (fusecfg.show_help || !fusecfg.mountpoint)
usage();
cfg.c_dbg_lvl = fusecfg.debug_lvl;
if (fusecfg.odebug && cfg.c_dbg_lvl < EROFS_DBG)
cfg.c_dbg_lvl = EROFS_DBG;
erofsfuse_dumpcfg();
ret = dev_open_ro(fusecfg.disk);
if (ret) {
fprintf(stderr, "failed to open: %s\n", fusecfg.disk);
goto err_fuse_free_args;
}
ret = erofs_read_superblock();
if (ret) {
fprintf(stderr, "failed to read erofs super block\n");
goto err_dev_close;
}
ret = fuse_main(args.argc, args.argv, &erofs_ops, NULL);
err_dev_close:
dev_close();
err_fuse_free_args:
fuse_opt_free_args(&args);
err:
erofs_exit_configure();
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
}