quickmod/FuseMounter/fsproxy.cpp

533 lines
14 KiB
C++

#include "fsproxy.h"
#include "database.h"
#include <QDir>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#define CACHE_BLOCK_SIZE (1024*1024) // 1MB chunks
FSProxy::FSProxy(QObject *parent)
: QObject{parent}
{
}
ProxyCacheEntry::ProxyCacheEntry(FSProxy *parent) :
QObject(parent),
m_parent{parent},
m_accessCount{0},
m_offset{0},
m_lastAccess{0}
{
}
void ProxyCacheEntry::clear()
{
m_mutex.lock();
for( ProxyCacheEntryChunk *c : m_chunks.values() )
c->deleteLater();
m_chunks.clear();
m_mutex.unlock();
}
void ProxyCacheEntry::clean()
{
time_t now = time(NULL) - 60; // 1 minute?
m_mutex.lock();
for( quint64 slot : m_chunks.keys() )
{
ProxyCacheEntryChunk *c = m_chunks[slot];
if( c->m_lastAccess > now )
continue;
//qDebug() << "ProxyCacheEntry::clean(): Freeing chunk #" << slot;
m_chunks.take(slot)->deleteLater();
}
m_mutex.unlock();
}
FSProxyElement::FSProxyElement(const QString &path, QObject *parent)
: QObject{parent},
m_path{path}
{
}
QString FSProxyElement::resolvePath(const QString &qpath, int *matchlength=nullptr)
{
QStringList parts = qpath.split(QDir::separator());
//QString filename = parts.takeLast();
int mlen = 0;
QString bothparts = m_path;
for( QString part : parts ) {
QDir cur( bothparts );
QStringList entries = cur.entryList(QDir::AllEntries|QDir::NoDotAndDotDot);
QString thisPiece = part;
for( QString edir : entries )
{
if( 0 == part.compare(edir, Qt::CaseInsensitive) )
{
thisPiece = edir;
mlen++;
break;
}
}
bothparts.append( QDir::separator() );
bothparts.append( thisPiece );
}
//qDebug() << "FSProxy::resolvePath:" << qpath << "=>" << bothparts;
if( nullptr != matchlength )
*matchlength = mlen;
return bothparts;
}
/*
void FSProxy::setRoot(const QString &path)
{
qDebug() << "FSProxy::setRoot: Game root set to" << path;
m_path = path;
}
*/
void FSProxy::addRoot(const QString &path)
{
qDebug() << "FSProxy::setRoot: Adding proxy to" << path << "at position" << m_proxies.length();
FSProxyElement *e = new FSProxyElement(path, this);
//m_proxies.prepend( e );
m_proxies << e;
}
void FSProxy::clear()
{
qDebug() << "FSProxy: Resetting all...";
m_filesMutex.lock();
QList< FSProxyElement * > proxies = m_proxies;
m_proxies.clear();
for( FSProxyElement *e : proxies )
e->deleteLater();
QList<ProxyCacheEntry *> cache = m_cache.values();
m_cache.clear();
for( ProxyCacheEntry *e : cache )
e->deleteLater();
m_filesMutex.unlock();
}
void FSProxy::setup(Database *db, int profileId, const QString &gamename, const QString &gamedir, const QString &createPath)
{
m_db = db;
m_profileId = profileId;
m_gamename = gamename;
m_gamedir = gamedir;
m_createPath = createPath;
reload();
}
void FSProxy::reload() {
clear();
// Order is important here:
addRoot( m_createPath );
QStringList others = m_db->proxyList(m_profileId);
for( const QString &e : others )
addRoot(e);
addRoot(m_gamedir);
}
void FSProxy::cleanCache()
{
//m_filesMutex.lock();
QList< ProxyCacheEntry * > ents = m_cache.values();
for( ProxyCacheEntry *e : ents )
e->clean();
//m_filesMutex.unlock();
}
ProxyCacheEntry *FSProxy::cacheFile(const QString &qpath)
{
int ret;
struct stat st;
for( FSProxyElement *e : m_proxies )
{
QString fullpath = e->resolvePath(qpath);
ret = ::stat(fullpath.toStdString().c_str(), &st);
if( 0 == ret ) {
//qDebug() << "FSProxy::getattr: Caching" << qpath << "to" << fullpath;
ProxyCacheEntry *ne = new ProxyCacheEntry(this);
ne->m_logicalpath = qpath;
ne->m_filepath = fullpath;
memcpy( &ne->m_stat, &st, sizeof(struct stat) );
//m_filesMutex.lock();
m_cache.insert( qpath, ne );
//m_filesMutex.unlock();
return ne;
}
}
return NULL;
}
QStringList FSProxy::readdir(const QString &qpath)
{
QStringList results;
for( FSProxyElement *e : m_proxies )
{
QString fullpath = e->resolvePath(qpath);
//qDebug() << "FSProxy::readdir(" << path << fullpath << ")";
QDir dir(fullpath);
QStringList ret = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot);
for( const QString &qs : ret ) {
if( !results.contains(qs, Qt::CaseInsensitive) )
results << qs;
}
//qDebug() << ret;
}
return results;
}
bool FSProxy::getattr(const QString &qpath, struct stat *st)
{
if( m_cache.contains(qpath) ) {
ProxyCacheEntry *ent = m_cache[qpath];
//qDebug() << "FSProxy::getattr: Using cached entry of" << qpath << "to" << ent->m_filepath;
memcpy( st, &ent->m_stat, sizeof(struct stat) );
return true;
}
ProxyCacheEntry *nent = cacheFile(qpath);
if( !nent )
return false;
memcpy( st, &nent->m_stat, sizeof(struct stat) );
return true;
}
int FSProxy::open(const QString &qpath, bool createIfNeeded)
{
if( m_cache.contains(qpath) )
{
ProxyCacheEntry *ent = m_cache[qpath];
QString fullpath = ent->m_filepath;
QFile *f = new QFile(fullpath);
//qDebug() << "FSProxy::open: (cached)" << fullpath;
if( !f->open(QIODevice::ReadWrite) )
{
qDebug() << "FSProxy::open: " << qpath << " failed:" << f->errorString();
f->deleteLater();
return -ENODEV;
}
int fd = f->handle();
OpenFile *o = new OpenFile();
o->m_logicalpath = qpath;
o->m_filepath = fullpath;
o->m_file = f;
o->m_fd = fd;
m_filesMutex.lock();
m_openFiles.insert( fd, o );
m_filesMutex.unlock();
ent->m_accessCount++;
return fd;
}
ProxyCacheEntry *cent = cacheFile( qpath );
if( !cent && !createIfNeeded )
{
qDebug() << "FSProxy::open: 'createIfNeeded' not specified, file not found:" << qpath;
return -EACCES;
}
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
QFile *f = new QFile(fullpath);
if( createIfNeeded ) {
QStringList parts = fullpath.split(QDir::separator());
parts.takeLast();
QDir here;
if( !here.mkpath(parts.join(QDir::separator())) )
return -EACCES;
}
//qDebug() << "FSProxy::open:" << fullpath;
if( !f->open(QIODevice::ReadWrite) )
{
qDebug() << "FSProxy::open: failed:" << f->errorString();
f->deleteLater();
return -ENODEV;
}
int fd = f->handle();
OpenFile *o = new OpenFile();
o->m_logicalpath = qpath;
o->m_filepath = fullpath;
o->m_file = f;
o->m_fd = fd;
m_filesMutex.lock();
m_openFiles.insert( fd, o );
m_filesMutex.unlock();
return fd;
}
bool FSProxy::close(int fd)
{
m_filesMutex.lock();
if( !m_openFiles.contains(fd) ) {
m_filesMutex.unlock();
return false;
}
OpenFile *f = m_openFiles.take(fd);
m_filesMutex.unlock();
f->m_mutex.lock();
f->m_file->close();
f->m_file->deleteLater();
f->m_fd = -1;
f->m_file = NULL;
f->m_mutex.unlock();
f->deleteLater();
cleanCache();
return true;
}
qint64 ProxyCacheEntry::sparseRead(int fd, char *buf, size_t size, off_t offset)
{
m_accessCount++;
m_lastAccess = time(NULL);
// chunk cache is aligned on CACHE_BLOCK_SIZE, and m_chunks is indexed on multiples therein.
quint64 ipos = offset % CACHE_BLOCK_SIZE;
quint64 cstart = (offset - ipos);
quint64 cslot = cstart / CACHE_BLOCK_SIZE;
//fprintf(stderr, "ProxyCacheEntry::sparseRead: Calling, ipos=%llu / cstart=%llu / cslot=%llu / offset=%lu / size=%lu\n",
// ipos, cstart, cslot, offset, size);
quint64 rlen = CACHE_BLOCK_SIZE - ipos;
quint64 opos = 0, rtotal = 0;
while( size > 0 )
{
ProxyCacheEntryChunk *chunk;
if( m_chunks.contains(cslot) ) {
//qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "already cached. Yay!";
chunk = m_chunks[cslot];
} else {
if( !m_parent->m_openFiles.contains(fd) )
{
qDebug() << "ProxyCacheEntry::sparseRead: Attempt to read on an unknown FD!";
errno = -EBADF;
return -1;
}
OpenFile *f = m_parent->m_openFiles[fd];
if( !f->m_file->seek(cstart) )
{
qDebug() << "ProxyCacheEntry::sparseRead: Seek within file failed!";
if( 0 == opos )
{
errno = -EINVAL;
return -1;
}
return rtotal;
}
chunk = new ProxyCacheEntryChunk(cstart, this);
m_chunks[cslot] = chunk;
chunk->m_data = f->m_file->read(CACHE_BLOCK_SIZE);
//qDebug() << "ProxyCacheEntry::sparseRead: Chunk #" << cslot << "cached.";
}
if( size < rlen )
rlen = size;
//fprintf(stderr, " + sparseRead(size=%lu, offset=%lu)\n", size, offset);
//fprintf(stderr, " - Copying %llu bytes of chunk+%llu to buf+%llu...\n", rlen, ipos, opos);
memcpy( buf + opos, chunk->m_data.constData() + ipos, rlen );
rtotal += rlen;
opos += rlen;
size -= rlen;
rlen = size > CACHE_BLOCK_SIZE ? CACHE_BLOCK_SIZE : size;
ipos = 0;
cstart += CACHE_BLOCK_SIZE;
cslot++;
chunk->m_accessCount++;
chunk->m_lastAccess = time(NULL);
}
//fprintf(stderr, " ~ sparseRead returning %llu bytes read.\n", rtotal);
return rtotal;
}
size_t FSProxy::read(int fd, char *buf, size_t size, off_t offset)
{
m_filesMutex.lock();
if( !m_openFiles.contains(fd) ) {
m_filesMutex.unlock();
return -EINVAL;
}
OpenFile *f = m_openFiles[fd];
f->m_mutex.lock();
m_filesMutex.unlock();
/*
ProxyCacheEntry *ent;
if( !m_cache.contains(f->m_logicalpath) )
ent = cacheFile(f->m_logicalpath);
else
ent = m_cache[f->m_logicalpath];
//size_t ret = ent->sparseRead(fd, buf, size, offset);
*/
if( !f->m_file->seek(offset) )
{
errno = -EBADF;
return -1;
}
size_t ret = f->m_file->read(buf, size);
f->m_mutex.unlock();
return ret;
}
int FSProxy::mknod(const QString &qpath, mode_t mode, dev_t dev)
{
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
QStringList parts = fullpath.split(QDir::separator());
parts.takeLast();
QDir here;
QString parentdirs = parts.join(QDir::separator());
qDebug() << "FSProxy::mknod: Creating parent directories:" << parentdirs;
if( !here.mkpath(parentdirs) )
return -EACCES;
qDebug() << "FSProxy::mknod: mknod:" << fullpath;
return ::mknod(fullpath.toStdString().c_str(), mode, dev);
}
int FSProxy::mkdir(const QString &qpath, mode_t mode)
{
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
return ::mkdir(fullpath.toStdString().c_str(), mode);
}
int FSProxy::unlink(const QString &qpath)
{
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
if( m_cache.contains(qpath) )
m_cache.take(qpath)->deleteLater();
return ::unlink(fullpath.toStdString().c_str());
}
int FSProxy::rmdir(const QString &qpath)
{
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
if( m_cache.contains(qpath) )
m_cache.take(qpath)->deleteLater();
return ::rmdir(fullpath.toStdString().c_str());
}
int FSProxy::rename(const QString &qpath, const QString &qnewname, unsigned int flags)
{
Q_UNUSED(flags)
FSProxyElement *e = m_proxies.first();
QString fullpathA = e->resolvePath(qpath);
QString fullpathB = e->resolvePath(qnewname);
if( flags & RENAME_EXCHANGE )
return -ENOTSUP;
if( flags & RENAME_NOREPLACE )
return -ENOTSUP;
if( m_cache.contains(qpath) )
m_cache.take(qpath)->deleteLater();
return ::rename(fullpathA.toStdString().c_str(), fullpathB.toStdString().c_str());
}
int FSProxy::truncate(const QString &qpath, off_t offset)
{
FSProxyElement *e = m_proxies.first();
QString fullpath = e->resolvePath(qpath);
QFileInfo fi(fullpath);
QDir dir = fi.absoluteDir();
dir.mkdir( dir.path() );
return ::truncate(fullpath.toStdString().c_str(), offset);
}
int FSProxy::write(int fd, const char *buf, size_t bufsz, off_t offset)
{
m_filesMutex.lock();
if( !m_openFiles.contains(fd) ) {
m_filesMutex.unlock();
return -EINVAL;
}
OpenFile *f = m_openFiles[fd];
f->m_mutex.lock();
m_filesMutex.unlock();
if( f->m_fd <= 0 )
{
f->m_mutex.unlock();
return -EINVAL;
}
f->m_file->seek(offset);
int ret = f->m_file->write(buf, bufsz);
if( m_cache.contains(f->m_logicalpath) )
m_cache.take(f->m_logicalpath)->deleteLater();
f->m_mutex.unlock();
return ret;
}
off_t FSProxy::lseek(int fd, off_t off, int whence)
{
m_filesMutex.lock();
if( !m_openFiles.contains(fd) ) {
m_filesMutex.unlock();
return -EINVAL;
}
OpenFile *f = m_openFiles[fd];
f->m_mutex.lock();
m_filesMutex.unlock();
if( f->m_fd <= 0 )
{
f->m_mutex.unlock();
return -EINVAL;
}
off_t ret;
if( SEEK_CUR == whence )
ret = f->m_file->seek( f->m_file->pos() + off );
else if( SEEK_END == whence )
ret = f->m_file->seek( f->m_file->size() + off );
else if( SEEK_SET == whence )
// Otherwise assume SEEK_SET
ret = f->m_file->seek(off);
else
ret = -EINVAL;
f->m_mutex.unlock();
return ret;
}