533 lines
14 KiB
C++
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;
|
|
}
|