#include "fsproxy.h" #include "database.h" #include #include #include #include #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 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; }