quickmod/FuseMounter/fusestuff.cpp

690 lines
19 KiB
C++

#include "fusestuff.h"
#include "archivemanager.h"
#include "database.h"
#include "fsproxy.h"
#include "database.h"
#include <QDir>
#include <QSocketNotifier>
#include <QDateTime>
extern "C" {
#define FUSE_USE_VERSION 31
#include <fuse3/fuse.h>
#include <fuse3/fuse_lowlevel.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unistd.h>
}
static FILE *logfd = NULL;
QMap< QString, QStringList > g_dircache;
QMap<QString, struct stat *> g_shortcuts;
QSocketNotifier *g_notifier;
void dolog(const char *fmt, ...)
{
return;
char ostr[8192];
va_list ap;
va_start(ap, fmt);
vsnprintf(ostr, sizeof(ostr), fmt, ap);
va_end(ap);
QDateTime now = QDateTime::currentDateTime();
QString tstr = now.toString(Qt::ISODateWithMs);
QString dstamp = QString("[%1] ").arg( tstr );
fwrite(dstamp.toStdString().c_str(), dstamp.length(), 1, stdout);
fwrite(dstamp.toStdString().c_str(), dstamp.length(), 1, logfd);
fwrite(ostr, strlen(ostr), 1, stdout);
fwrite(ostr, strlen(ostr), 1, logfd);
fflush(logfd);
}
static QString cleanup_path(const char *path)
{
QString result = QString(path+1).replace("//", "/");
return result;
}
static ArchiveManager *g_archive = NULL;
void qmfuse_set_archive(ArchiveManager *archive)
{
g_archive = archive;
}
static FSProxy *g_proxy = NULL;
void qmfuse_set_proxy(FSProxy *proxy)
{
g_proxy = proxy;
}
static Database *g_database = NULL;
void qmfuse_set_database(Database *db)
{
g_database = db;
}
void qmfuse_reset()
{
QList< struct stat * > gsvals = g_shortcuts.values();
g_dircache.clear();
g_shortcuts.clear();
for( struct stat *st : gsvals )
delete st;
}
static bool qmfuse_dircache_remove(const QString &qpath)
{
QStringList parts = qpath.split(QDir::separator());
parts.takeLast();
QString npath = parts.join(QDir::separator());
if( !g_dircache.contains(npath) )
return false;
g_dircache.remove(npath);
return true;
}
static void *qmfuse_init(struct fuse_conn_info *conn,
struct fuse_config *cfg)
{
(void) conn;
cfg->kernel_cache = 0;
logfd = fopen("/tmp/quickmod.txt", "w");
return NULL;
}
static int qmfuse_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
Q_UNUSED(fi)
QString spath = path;
if( g_shortcuts.contains(spath) )
{
if( NULL == g_shortcuts[spath] ) {
//dolog("getattr: already know %s doesn't exist!\n", path);
return -ENOENT;
} else {
struct stat *st = g_shortcuts[spath];
memcpy( stbuf, st, sizeof(struct stat));
return 0;
}
}
dolog("getattr: %s\n", path);
QString qpath = cleanup_path(path);
memset(stbuf, 0, sizeof(struct stat));
QStringList qparts = qpath.split(QDir::separator());
if( 0 == qpath.length() )
qparts.clear();
/*
if( qpath.endsWith(".ciopfs") )
{
stbuf->st_mode = S_IFREG | 0755;
stbuf->st_nlink = 1;
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
return 0;
}
*/
if( g_database->isProxyOverride(qpath.toLower()) )
{
dolog("getattr(1): override exists for %s!\n", path+1);
if( g_proxy->getattr(qpath, stbuf) )
return 0;
}
/* First the mods: */
ModLibrary *library;
ModEntry *mod;
Archive *archive;
if( g_archive->findFile(qpath, &library, &mod, &archive) )
{
if( !mod ) {
dolog("getattr(4): returning that we're a directory\n");
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
time_t t = time(NULL);
stbuf->st_atim.tv_sec = t;
stbuf->st_ctim.tv_sec = t;
stbuf->st_mtim.tv_sec = t;
struct stat *st = new struct stat;
memcpy(st, stbuf, sizeof(struct stat));
g_shortcuts[spath] = st;
return 0;
}
if( !archive ) {
dolog("getattr(4): No archive entry for \"%s\"!\n", qpath.toStdString().c_str());
return -ENOENT;
}
ArchiveCacheEntry *cacheEntry = archive->cache(mod->m_archivePath);
if( !cacheEntry )
{
dolog("getattr(4): No cache entry for \"%s\"!\n", qpath.toStdString().c_str());
return -ENOENT;
}
memcpy( stbuf, &cacheEntry->m_stat, sizeof(struct stat) );
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
struct stat *st = new struct stat;
memcpy(st, stbuf, sizeof(struct stat));
g_shortcuts[spath] = st;
dolog("getattr(4): returning that we're a file (\"%s\" => \"%s\")\n",
mod->m_archivePath.toLower().toStdString().c_str(), mod->m_installedPath.toStdString().c_str() );
return 0;
}
/* Now check overlay (after): */
if( g_proxy->getattr(qpath, stbuf) )
return 0;
dolog("getattr (4xxx): !!! Couldn't find \"%s\" anywhere in our libraries!\n", path+1);
g_shortcuts[spath] = NULL;
return -ENOENT;
}
static int qmfuse_mknod(const char *path, mode_t mode, dev_t dev)
{
QString qpath = cleanup_path(path);
dolog("mknod: \"%s\" %d %d\n", qpath.toStdString().c_str(), mode, dev);
int ret = g_proxy->mknod(qpath, mode, dev);
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
return ret;
}
static int qmfuse_mkdir(const char *path, mode_t mode)
{
QString qpath = cleanup_path(path);
dolog("mkdir: %s\n", qpath.toStdString().c_str());
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
return g_proxy->mkdir(qpath, mode);
}
static int qmfuse_unlink(const char *path)
{
QString qpath = cleanup_path(path);
dolog("unlink: %s\n", qpath.toStdString().c_str());
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
g_database->unregisterProxyOverride(qpath);
return g_proxy->unlink(qpath.toStdString().c_str());
}
static int qmfuse_rmdir(const char *path)
{
QString qpath = cleanup_path(path);
dolog("rmdir: %s\n", qpath.toStdString().c_str());
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
return g_proxy->rmdir(qpath);
}
static int qmfuse_rename(const char *path, const char *newname, unsigned int flags)
{
QString qpathA = cleanup_path(path);
QString qpathB = cleanup_path(newname);
dolog("rename: %s => %s\n",
qpathA.toStdString().c_str(),
qpathB.toStdString().c_str()
);
g_shortcuts.remove(QString(path));
g_shortcuts.remove(QString(newname));
qmfuse_dircache_remove(qpathA);
qmfuse_dircache_remove(qpathB);
return g_proxy->rename(qpathA, qpathB, flags);
}
static int qmfuse_truncate(const char *path, off_t offset, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
QString qpath = cleanup_path(path);
dolog("truncate: %s\n", qpath.toStdString().c_str());
int ret = g_proxy->truncate(qpath, offset);
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
return ret;
}
#ifndef FUSE_FILL_DIR_DEFAULTS
# define FUSE_FILL_DIR_DEFAULTS (enum fuse_fill_dir_flags)0
#endif
static int qmfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
(void) offset;
(void) fi;
(void) flags;
QString qpath = cleanup_path(path);
if( g_dircache.contains(qpath) )
{
QStringList ents = g_dircache[qpath];
for( const QString &e : ents )
filler(buf, e.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
return 0;
}
dolog("readdir: %s\n", qpath.toStdString().c_str());
QStringList filesshared;
/* First the overlay: */
QStringList proxyentries = g_proxy->readdir(qpath);
for( const QString &ent : proxyentries )
{
QStringList parts = ent.split(QDir::separator());
QString nent = parts.last();
//dolog("readdir(3): proxy: %s\n", nent.toStdString().c_str());
filesshared.append(nent);
filler(buf, nent.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
}
/* Now the mods: */
QStringList qparts = qpath.split(QDir::separator());
if( 0 == qpath.length() )
qparts.clear();
QHash< ModLibrary *, Archive * > entries = g_archive->getArchives();
const QList< ModLibrary * > &ekeys = entries.keys();
for( ModLibrary *library : ekeys )
{
//QMap<QString, ArchiveCacheEntry> arc = entries[library].getEntries();
for( ModEntry *mod : library->m_entries )
{
/*
dolog("readdir(2): \"%s\" startsWith \"%s\"?\n",
mod.m_installedPath.toStdString().c_str(),
qpath.toStdString().c_str());
*/
QStringList parts = mod->m_installedPath.split(QDir::separator());
/*
dolog("readdir(4): Comparing: qpath=\"%s\" vs. path=\"%s\"...\n",
qparts.join(QDir::separator()).toStdString().c_str(),
parts.join(QDir::separator()).toStdString().c_str()
);
*/
if( parts.length() <= qparts.length() )
continue;
bool skipme = false;
for( int x=0; x < qparts.length(); x++ )
{
if( 0 == parts.at(x).length() )
continue;
if( 0 == qparts.at(x).length() )
continue;
if( 0 != qparts.at(x).compare(parts.at(x), Qt::CaseInsensitive) )
{
skipme = true;
break;
}
}
if( skipme )
continue;
QString target = parts.at(qparts.length());
if( filesshared.contains(target, Qt::CaseInsensitive) )
continue;
//dolog("readdir(4b): \"%s\" => \"%s\"\n", mod.m_installedPath.toStdString().c_str(), qpath.toStdString().c_str());
filesshared.append(target);
filler(buf, target.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_PLUS);
}
}
g_dircache[qpath] = filesshared;
return 0;
}
static int qmfuse_write(const char *path, const char *buf, size_t bufsz, off_t offset,
struct fuse_file_info *fi)
{
QString qpath = cleanup_path(path);
dolog("write: \"%s\" %d bytes, %d offset\n", qpath.toStdString().c_str(), bufsz, offset);
int ret = g_proxy->write(fi->fh, buf, bufsz, offset);
//if( g_shortcuts.contains(qpath) )
g_shortcuts.remove(QString(path));
qmfuse_dircache_remove(qpath);
return ret;
}
static int qmfuse_open(const char *path, struct fuse_file_info *fi)
{
QString qpath = cleanup_path(path);
dolog("open: %s\n", qpath.toStdString().c_str());
if( g_database->isProxyOverride(qpath.toLower()) )
{
dolog("open(1): override exists for %s!\n", qpath.toStdString().c_str());
int fd = g_proxy->open(qpath);
if( fd > 0 ) {
fi->fh = fd;
return 0;
}
}
/* First the mods: */
ModLibrary *library;
ModEntry *mod;
Archive *archive;
if( g_archive->findFile(qpath, &library, &mod, &archive) )
{
if( !archive ) {
dolog("open(1): attempt to open a directory? iunno: %s\n", qpath.toStdString().c_str());
return -EINVAL;
}
/*
if( !archive->contains(mod->m_archivePath) )
{
dolog("open(3): Archive does not contain \"%s\"!\n", qpath.toStdString().c_str());
return -ENOENT;
}
*/
int fam = (fi->flags & O_ACCMODE);
if( fam == O_RDWR || fam == O_WRONLY )
{
// Make a proxy override...
dolog("open(2): trying to open for write: %s\n", qpath.toStdString().c_str());
/* FIXME: We need to make the "created" a format override, check that first, then arcs, extracted, then vanilla.
* !!!!!!!!!!!!!
*
QByteArray *src = archive->getEntry(mod->m_archivePath);
QFile f(qpath);
f.open(QIODeviceBase::WriteOnly);
f.write(*src);
f.close();
*/
int fd = g_proxy->open(qpath, true);
if( fd <= 0 ) {
dolog("open(2): no dice, for some reason.\n");
return -EACCES;
}
g_database->registerProxyOverride(qpath.toLower());
g_shortcuts.remove(qpath);
fi->fh = fd;
return 0;
}
dolog("open(2): opened \"%s\"\n", mod->m_installedPath.toStdString().c_str());
fi->fh = 0;
return 0;
}
/* Now check overlay (after): */
int fd = g_proxy->open(qpath);
if( fd > 0 ) {
fi->fh = fd;
return 0;
}
if( fi->flags & O_CREAT ) {
fd = g_proxy->open(qpath, true);
g_shortcuts.remove(qpath);
fi->fh = fd;
return 0;
}
return -EACCES;
}
static int qmfuse_release(const char *path, struct fuse_file_info *fi)
{
QString qpath = cleanup_path(path);
dolog("release: %s\n", qpath.toStdString().c_str());
if( fi->fh <= 0 ) {
//g_archive->cleanCache();
return 0;
}
g_proxy->close(fi->fh);
return 0;
}
static int qmfuse_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
QString qpath = cleanup_path(path);
/* check overlay: */
if( fi->fh > 0 ) {
int ret = g_proxy->read(fi->fh, buf, size, offset);
//if( ret != -EACCES )
return ret;
}
/* mods after: */
ModLibrary *library;
ModEntry *mod;
Archive *archive;
if( g_archive->findFile(qpath, &library, &mod, &archive) )
{
/*
if( !archive->contains(mod->m_archivePath) )
{
dolog("read(3): Archive does not contain \"%s\"!\n", qpath.toStdString().c_str());
return -ENOENT;
}
*/
if( !archive ) {
dolog("open(1): attempt to read a directory? iunno: %s\n", qpath.toStdString().c_str());
return -EINVAL;
}
ArchiveCacheEntry *cacheEntry = archive->cache(mod->m_archivePath);
if( cacheEntry && cacheEntry->m_loading ) {
qDebug() << QString("getEntry(%1) -> (Still Loading...)\n").arg(qpath);
return -EAGAIN;
}
QByteArray *src = archive->getEntry(mod->m_archivePath);
if( !src || 0 == src->length() )
{
dolog("read(2): got 0-byte length while trying to read \"%s\"\n", qpath.toStdString().c_str());
return 0;
}
if( offset >= src->length() )
{
archive->releaseEntry(mod->m_archivePath);
return -EOF;
}
if (offset + size > (size_t)src->length())
size = src->length() - offset;
if( size > 0 )
memcpy(buf, src->constData() + offset, size);
int ret = size;
if( ret < 0 )
ret = 0;
archive->releaseEntry(mod->m_archivePath);
return ret;
}
return -ENOENT;
}
static off_t qmfuse_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
{
/* check overlay: */
if( fi->fh > 0 ) {
int ret = g_proxy->lseek(fi->fh, off, whence);
if( -EINVAL == ret )
dolog("lseek(1): g_proxy->lseek on fd resulted in -EINVAL (whence is %d)\n", whence);
return ret;
}
QString qpath = cleanup_path(path);
/* mods after: */
ModLibrary *library;
ModEntry *mod;
Archive *archive;
if( g_archive->findFile(qpath, &library, &mod, &archive) )
{
if( !archive )
//if( !archive->contains(qpath) )
{
dolog("lseek(3): Archive does not contain \"%s\"!\n", qpath.toStdString().c_str());
return -ENOENT;
}
ArchiveCacheEntry *cacheEntry = archive->cache(mod->m_archivePath);
if( cacheEntry && cacheEntry->m_loading ) {
qDebug() << QString("getEntry(%1) -> (Still Loading...)\n").arg(qpath);
return -EAGAIN;
}
if( SEEK_SET != whence )
return -EINVAL;
return off;
/*
if( cacheEntry->m_data.length() >= off )
return off;
return -EACCES;
*/
}
dolog("lseek: %s\n", path+1);
return g_proxy->lseek(fi->fh, off, whence);
}
static const struct fuse_operations qmfuse_oper = {
.getattr = qmfuse_getattr,
.readlink = NULL,
.mknod = qmfuse_mknod,
.mkdir = qmfuse_mkdir,
.unlink = qmfuse_unlink,
.rmdir = qmfuse_rmdir,
.symlink = NULL,
.rename = qmfuse_rename,
.link = NULL,
.chmod = NULL,
.chown = NULL,
.truncate = qmfuse_truncate,
.open = qmfuse_open,
.read = qmfuse_read,
.write = qmfuse_write,
.statfs = NULL,
.flush = NULL,
.release = qmfuse_release,
.fsync = NULL,
.setxattr = NULL,
.getxattr = NULL,
.listxattr = NULL,
.removexattr = NULL,
.opendir = NULL,
.readdir = qmfuse_readdir,
.releasedir = NULL,
.fsyncdir = NULL,
.init = qmfuse_init,
.destroy = NULL,
.access = NULL,
.create = NULL,
.lock = NULL,
.utimens = NULL,
.bmap = NULL,
.ioctl = NULL,
.poll = NULL,
.write_buf = NULL,
.read_buf = NULL,
.flock = NULL,
.fallocate = NULL,
.copy_file_range = NULL,
.lseek = qmfuse_lseek,
};
struct fuse *g_fuse;
struct fuse_session *g_fuse_session;
int qmfuse_main(const char *mountpoint)
{
/*
char *argv[4];
argv[0] = "quickmod";
argv[1] = (char*)mountpoint;
argv[2] = "-f";
argv[3] = "-s";
return fuse_main(3, argv, &qmfuse_oper, NULL);
*/
char *argv[4];
argv[0] = "quickmod";
argv[1] = (char*)mountpoint;
argv[2] = "-f";
//argv[3] = "-s";
//int argc = sizeof(*argv) / sizeof(argv[0]);
//struct fuse_args args = FUSE_ARGS_INIT(4, argv);
struct fuse_args args = FUSE_ARGS_INIT(3, argv);
struct fuse_cmdline_opts opts;
if( 0 != fuse_parse_cmdline(&args, &opts) ) {
fprintf(stderr, "Failed to parse options.\n");
return -1;
}
size_t op_size = sizeof(*(&qmfuse_oper));
g_fuse = fuse_new(&args, &qmfuse_oper, op_size, NULL);
if( !g_fuse ) {
fprintf(stderr, "Failed to create new FUSE object.\n");
return -2;
}
if( 0 != fuse_mount(g_fuse, mountpoint) ) {
fprintf(stderr, "Failed at fuse_mount!\n");
return -3;
}
g_fuse_session = fuse_get_session(g_fuse);
if( 0 != fuse_set_signal_handlers(g_fuse_session) ) {
fprintf(stderr, "Failed to set FUSE signal handlers!\n");
return -4;
}
//g_notifier = new QSocketNotifier(fuse_session_fd(g_fuse_session), QSocketNotifier::Read);
return fuse_session_fd(g_fuse_session);
}
int qmfuse_pump()
{
struct fuse_buf fbuf = { .mem = NULL };
if( fuse_session_exited(g_fuse_session) ) {
fprintf(stderr, "Feh. Session closed!\n");
return -1;
}
int res = fuse_session_receive_buf(g_fuse_session, &fbuf);
if( -EINTR == res )
return 0; // Nothing to do.
if( 0 >= res )
return res;
fuse_session_process_buf(g_fuse_session, &fbuf);
return 0;
}
void qmfuse_unmount()
{
fuse_session_unmount(g_fuse_session);
}