gzextfs/gzextfs.cxx

522 lines
11 KiB
C++

/* gsextfs.cxx */
#include "gzextdef.h"
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <fuse/fuse.h>
extern "C" {
#include <ext2fs/ext2_err.h>
#include <ext2fs/ext2fs.h>
}
#ifndef HAVE_EXT2FS_DIRENT_NAME_LEN
static inline int
ext2fs_dirent_name_len(const struct ext2_dir_entry *entry)
{
return entry->name_len & 0xff;
}
#endif
#include "gzextio.h"
#include <string>
#include <list>
#include <map>
using namespace std;
typedef list<string> stringlist;
typedef map<string, ext2_ino_t> pathcache;
typedef map<ext2_ino_t, ext2_inode> inodecache;
typedef map<ext2_ino_t, stringlist> dircache;
struct file_state {
pthread_mutex_t lock;
};
typedef map<ext2_file_t, file_state*> filemap;
static struct options {
const char* filename;
int show_help;
} options;
struct gzfs_priv {
char* filename;
ext2_filsys fs;
pthread_mutex_t path_lock;
pathcache path_cache;
pthread_mutex_t inode_lock;
inodecache inode_cache;
pthread_mutex_t dir_lock;
dircache dir_cache;
pthread_mutex_t file_lock;
filemap file_table;
};
#define GZFS_PRIV(p) ((struct gzfs_priv*)(p->private_data))
/*** ext2 helpers ***/
static bool
lookup_path(struct gzfs_priv* priv, const char* pathname, ext2_ino_t& inum)
{
const char* p;
const char* q;
ext2_ino_t dir;
ext2_ino_t subdir;
if (!*pathname) {
inum = EXT2_ROOT_INO;
return true;
}
pthread_mutex_lock(&priv->path_lock);
pathcache::iterator itr = priv->path_cache.find(pathname);
if (itr != priv->path_cache.end()) {
inum = itr->second;
pthread_mutex_unlock(&priv->path_lock);
return true;
}
dir = EXT2_ROOT_INO;
p = pathname;
q = strchr(p, '/');
while (q) {
if (ext2fs_lookup(priv->fs, dir, p, q - p, NULL, &subdir) != 0) {
pthread_mutex_unlock(&priv->path_lock);
return false;
}
dir = subdir;
p = q + 1;
q = strchr(p, '/');
}
if (ext2fs_lookup(priv->fs, dir, p, strlen(p), NULL, &inum) != 0) {
pthread_mutex_unlock(&priv->path_lock);
return false;
}
priv->path_cache[pathname] = inum;
pthread_mutex_unlock(&priv->path_lock);
return true;
}
static bool
lookup_inode(struct gzfs_priv* priv, ext2_ino_t inum, ext2_inode& inode)
{
pthread_mutex_lock(&priv->inode_lock);
inodecache::iterator itr = priv->inode_cache.find(inum);
if (itr != priv->inode_cache.end()) {
inode = itr->second;
pthread_mutex_unlock(&priv->inode_lock);
return true;
}
if (ext2fs_read_inode(priv->fs, inum, &inode) != 0) {
pthread_mutex_unlock(&priv->inode_lock);
return false;
}
priv->inode_cache[inum] = inode;
pthread_mutex_unlock(&priv->inode_lock);
return true;
}
static int
dir_iterator(struct ext2_dir_entry* dirent, int offset,
int blocksize, char* buf, void* priv)
{
stringlist* filenames = (stringlist*)priv;
filenames->push_back(string(dirent->name, ext2fs_dirent_name_len(dirent)));
return 0;
}
static bool
lookup_dir(struct gzfs_priv* priv, ext2_ino_t inum, stringlist& filenames)
{
pthread_mutex_lock(&priv->dir_lock);
dircache::iterator itr = priv->dir_cache.find(inum);
if (itr != priv->dir_cache.end()) {
filenames = itr->second;
pthread_mutex_unlock(&priv->dir_lock);
return true;
}
if (ext2fs_dir_iterate(priv->fs, inum, 0, NULL, dir_iterator, &filenames) != 0) {
pthread_mutex_unlock(&priv->dir_lock);
return false;
}
priv->dir_cache[inum] = filenames;
pthread_mutex_unlock(&priv->dir_lock);
return true;
}
/*** FUSE operations ***/
static int
gzfs_getattr(const char* path, struct stat* st)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2_ino_t inum;
struct ext2_inode inode;
if (*path != '/') {
return -1;
}
if (!lookup_path(priv, path + 1, inum)) {
return -1;
}
if (!lookup_inode(priv, inum, inode)) {
return -1;
}
st->st_ino = inum;
st->st_mode = inode.i_mode;
st->st_nlink = inode.i_links_count;
st->st_uid = inode.i_uid;
st->st_gid = inode.i_gid;
st->st_size = inode.i_size;
st->st_blksize = 4096 /* XXX */;
st->st_blocks = inode.i_blocks;
st->st_atime = inode.i_atime;
st->st_mtime = inode.i_mtime;
st->st_ctime = inode.i_ctime;
return 0;
}
static int
gzfs_readlink(const char* path, char* buf, size_t size)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2_ino_t inum;
struct ext2_inode inode;
if (*path != '/') {
return -1;
}
if (!lookup_path(priv, path+1, inum)) {
return -1;
}
if (!lookup_inode(priv, inum, inode)) {
return -1;
}
/* XXX: The below needs tested thoroughly. */
char linkbuf[PATH_MAX + 1];
size_t linksize;
if (ext2fs_inode_has_valid_blocks(&inode)) {
ext2_file_t file;
unsigned int nread;
if (ext2fs_file_open(priv->fs, inum, 0, &file) != 0) {
return -1;
}
if (ext2fs_file_read(file, linkbuf, sizeof(linkbuf), &nread) != 0) {
ext2fs_file_close(file);
return -1;
}
linksize = nread;
ext2fs_file_close(file);
}
else {
memcpy(linkbuf, (const char*)inode.i_block, sizeof(inode.i_block));
linksize = sizeof(inode.i_block);
}
if (size > linksize) {
size = linksize;
}
memcpy(buf, linkbuf, size);
return 0;
}
static int
gzfs_open(const char* path, struct fuse_file_info* ffi)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2_ino_t inum;
ext2_file_t file;
if (*path != '/') {
return -1;
}
if (!lookup_path(priv, path + 1, inum)) {
return -1;
}
if (ext2fs_file_open(priv->fs, inum, 0, &file) != 0) {
return -1;
}
file_state* state = new file_state;
pthread_mutex_init(&state->lock, NULL);
pthread_mutex_lock(&priv->file_lock);
priv->file_table[file] = state;
pthread_mutex_unlock(&priv->file_lock);
ffi->fh = (uint64_t)file;
return 0;
}
static int
gzfs_read(const char* path, char* buf, size_t size, off_t off, struct fuse_file_info* ffi)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
__u64 pos;
unsigned int nread;
ext2_file_t file = (ext2_file_t)ffi->fh;
pthread_mutex_lock(&priv->file_lock);
file_state* state = priv->file_table.find(file)->second;
pthread_mutex_unlock(&priv->file_lock);
pthread_mutex_lock(&state->lock);
if (ext2fs_file_llseek(file, off, SEEK_SET, &pos) != 0) {
pthread_mutex_unlock(&state->lock);
return -1;
}
if (ext2fs_file_read(file, buf, size, &nread) != 0) {
pthread_mutex_unlock(&state->lock);
return -1;
}
pthread_mutex_unlock(&state->lock);
return nread;
}
static int
gzfs_statfs(const char* param1, struct statvfs* param2)
{
return -1;
}
static int
gzfs_flush(const char* param1, struct fuse_file_info* ffi)
{
return 0;
}
static int
gzfs_release(const char* param1, struct fuse_file_info* ffi)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2_file_t file = (ext2_file_t)ffi->fh;
pthread_mutex_lock(&priv->file_lock);
filemap::iterator itr = priv->file_table.find(file);
file_state* state = itr->second;
priv->file_table.erase(itr);
pthread_mutex_unlock(&priv->file_lock);
pthread_mutex_destroy(&state->lock);
delete state;
ext2fs_file_close(file);
return 0;
}
static int
gzfs_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t off, struct fuse_file_info* ffi)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2_ino_t inum;
if (*path != '/') {
return -1;
}
if (!lookup_path(priv, path+1, inum)) {
return -1;
}
stringlist filenames;
if (!lookup_dir(priv, inum, filenames)) {
return -1;
}
stringlist::iterator itr;
for (itr = filenames.begin(); itr != filenames.end(); ++itr) {
const string& name = *itr;
if (filler(buf, name.c_str(), NULL, 0) != 0) {
break;
}
}
return 0;
}
static void*
gzfs_init(struct fuse_conn_info *conn)
{
errcode_t rc;
struct gzfs_priv* priv = new gzfs_priv;
priv->filename = strdup(options.filename);
pthread_mutex_init(&priv->path_lock, NULL);
pthread_mutex_init(&priv->inode_lock, NULL);
pthread_mutex_init(&priv->dir_lock, NULL);
pthread_mutex_init(&priv->file_lock, NULL);
rc = ext2fs_open(priv->filename,
0 /* flags */,
0 /* superblock */,
4096 /* block_size */,
const_cast<io_manager>(gzext_io_manager),
&priv->fs);
if (rc != 0) {
fprintf(stderr, "Failed to open ext2 filesystem on %s\n", priv->filename);
exit(1);
}
return priv;
}
static void
gzfs_destroy(void* param)
{
struct gzfs_priv* priv = GZFS_PRIV(fuse_get_context());
ext2fs_close(priv->fs);
delete priv;
}
static const struct fuse_operations gzfs_oper = {
.getattr = gzfs_getattr,
.readlink = gzfs_readlink,
.getdir = NULL,
.mknod = NULL,
.mkdir = NULL,
.unlink = NULL,
.rmdir = NULL,
.symlink = NULL,
.rename = NULL,
.link = NULL,
.chmod = NULL,
.chown = NULL,
.truncate = NULL,
.utime = NULL,
.open = gzfs_open,
.read = gzfs_read,
.write = NULL,
.statfs = gzfs_statfs,
.flush = gzfs_flush,
.release = gzfs_release,
.fsync = NULL,
.setxattr = NULL,
.getxattr = NULL,
.listxattr = NULL,
.removexattr = NULL,
.opendir = NULL,
.readdir = gzfs_readdir,
.releasedir = NULL,
.fsyncdir = NULL,
.init = gzfs_init,
.destroy = gzfs_destroy,
.access = NULL,
.create = NULL,
.ftruncate = NULL,
.fgetattr = NULL,
.lock = NULL,
.utimens = NULL,
.bmap = NULL,
.flag_nullpath_ok = 0,
.flag_nopath = 0,
.flag_utime_omit_ok = 0,
.flag_reserved = 0,
.ioctl = NULL,
.poll = NULL,
.write_buf = NULL,
.read_buf = NULL,
.flock = NULL,
.fallocate = NULL,
};
#define OPTION(t,p) \
{ t, offsetof(struct options, p), 1 }
static const struct fuse_opt option_spec[] = {
OPTION("--filename=%s", filename),
OPTION("-h", show_help),
OPTION("--help", show_help),
FUSE_OPT_END
};
static void
usage(const char* argv0)
{
fprintf(stderr, "Usage: %s --filename=<image.gz> ... <mount>\n", argv0);
exit(1);
}
int
main(int argc, char** argv)
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
if ((getuid() == 0) || (geteuid() == 0)) {
fprintf(stderr, "Refusing to run as root.\n");
exit(1);
}
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) {
return 1;
}
if (options.show_help) {
usage(argv[0]);
}
if (!options.filename) {
usage(argv[0]);
}
char* real_filename = NULL;
if (options.filename[0] == '/') {
real_filename = strdup(options.filename);
}
else {
real_filename = (char*)malloc(PATH_MAX + 1);
if (realpath(options.filename, real_filename) == NULL) {
fprintf(stderr, "Failed to resolve %s\n", options.filename);
exit(1);
}
options.filename = real_filename;
}
return fuse_main(args.argc, args.argv, &gzfs_oper, NULL);
}