File indexing completed on 2024-11-24 04:31:14

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "cachefile.h"
0007 
0008 #include <config-ktorrent.h>
0009 
0010 #include "cache.h"
0011 #include "preallocationthread.h"
0012 #include <errno.h>
0013 #include <fcntl.h>
0014 #include <kfileitem.h>
0015 #include <klocalizedstring.h>
0016 #include <qfile.h>
0017 #include <sys/mman.h>
0018 #include <sys/stat.h>
0019 #include <sys/types.h>
0020 #include <unistd.h>
0021 #include <util/array.h>
0022 #include <util/error.h>
0023 #include <util/fileops.h>
0024 #include <util/functions.h>
0025 #include <util/log.h>
0026 
0027 #include <QStorageInfo>
0028 
0029 // Not all systems have an O_LARGEFILE - Solaris depending
0030 // on command-line defines, FreeBSD never - so in those cases,
0031 // make it a zero bitmask. As long as it's only OR'ed into
0032 // open(2) flags, that's fine.
0033 //
0034 #ifndef O_LARGEFILE
0035 #define O_LARGEFILE (0)
0036 #endif
0037 
0038 namespace bt
0039 {
0040 CacheFile::CacheFile()
0041     : fptr(nullptr)
0042     , max_size(0)
0043     , file_size(0)
0044     , mutex()
0045 {
0046     read_only = false;
0047     manual_close = false;
0048 }
0049 
0050 CacheFile::~CacheFile()
0051 {
0052     if (fptr)
0053         close();
0054 }
0055 
0056 void CacheFile::changePath(const QString &npath)
0057 {
0058     path = npath;
0059 }
0060 
0061 void CacheFile::openFile(Mode mode)
0062 {
0063     // by default always try read write
0064     fptr = new QFile(path);
0065     connect(fptr, &QFile::aboutToClose, this, &CacheFile::aboutToClose);
0066     bool ok = false;
0067     if (!(ok = fptr->open(QIODevice::ReadWrite))) {
0068         // in case RDWR fails, try readonly if possible
0069         if (mode == READ && (ok = fptr->open(QIODevice::ReadOnly)))
0070             read_only = true;
0071     }
0072 
0073     if (!ok) {
0074         delete fptr;
0075         fptr = nullptr;
0076         throw Error(i18n("Cannot open %1: %2", path, strerror(errno)));
0077     }
0078 
0079     file_size = fptr->size();
0080 }
0081 
0082 void CacheFile::open(const QString &path, Uint64 size)
0083 {
0084     QMutexLocker lock(&mutex);
0085     // only set the path and the max size, we only open the file when it is needed
0086     this->path = path;
0087     max_size = size;
0088 }
0089 
0090 void *CacheFile::map(MMappeable *thing, Uint64 off, Uint32 size, Mode mode)
0091 {
0092     QMutexLocker lock(&mutex);
0093     // reopen the file if necessary
0094     if (!fptr) {
0095         //  Out(SYS_DIO|LOG_DEBUG) << "Reopening " << path << endl;
0096         QStorageInfo mount(path); // ntfs cannot handle mmap properly
0097         if (!OpenFileAllowed() || mount.fileSystemType() == "fuseblk" || mount.fileSystemType().startsWith("ntfs"))
0098             return nullptr; // Running out of file descriptors, force buffered mode
0099         openFile(mode);
0100     }
0101 
0102     if (read_only && mode != READ) {
0103         throw Error(i18n("Cannot open %1 for writing: readonly filesystem", path));
0104     }
0105 
0106     if (off + size > max_size) {
0107         Out(SYS_DIO | LOG_DEBUG) << "Warning : writing past the end of " << path << endl;
0108         Out(SYS_DIO | LOG_DEBUG) << (off + size) << " " << max_size << endl;
0109         throw Error(i18n("Attempting to write beyond the maximum size of %1", path));
0110     }
0111 
0112     /*
0113     if (!read_only && (mode == WRITE || mode == RW) && !allocateBytes(off,size))
0114         throw Error(i18n("Not enough free disk space for %1",path));
0115     */
0116 
0117     int mmap_flag = 0;
0118     switch (mode) {
0119     case READ:
0120         mmap_flag = PROT_READ;
0121         break;
0122     case WRITE:
0123         mmap_flag = PROT_WRITE;
0124         break;
0125     case RW:
0126         mmap_flag = PROT_READ | PROT_WRITE;
0127         break;
0128     }
0129 
0130     if (off + size > file_size) {
0131         Uint64 to_write = (off + size) - file_size;
0132         //  Out(SYS_DIO|LOG_DEBUG) << "Growing file with " << to_write << " bytes" << endl;
0133         growFile(to_write);
0134     }
0135 
0136 #ifndef Q_WS_WIN
0137     int fd = fptr->handle();
0138     Uint32 page_size = sysconf(_SC_PAGESIZE);
0139     if (off % page_size > 0) {
0140         // off is not a multiple of the page_size
0141         // so we play around a bit
0142         Uint32 diff = (off % page_size);
0143         Uint64 noff = off - diff;
0144         //  Out(SYS_DIO|LOG_DEBUG) << "Offsetted mmap : " << diff << endl;
0145 #ifdef HAVE_MMAP64
0146         char *ptr = (char *)mmap64(nullptr, size + diff, mmap_flag, MAP_SHARED, fd, noff);
0147 #else
0148         char *ptr = (char *)mmap(0, size + diff, mmap_flag, MAP_SHARED, fd, noff);
0149 #endif
0150         if (ptr == MAP_FAILED) {
0151             Out(SYS_DIO | LOG_DEBUG) << "mmap failed : " << QString(strerror(errno)) << endl;
0152             return nullptr;
0153         } else {
0154             CacheFile::Entry e;
0155             e.thing = thing;
0156             e.offset = off;
0157             e.diff = diff;
0158             e.ptr = ptr;
0159             e.size = size + diff;
0160             e.mode = mode;
0161             mappings.insert((void *)(ptr + diff), e);
0162             return ptr + diff;
0163         }
0164     } else {
0165 #ifdef HAVE_MMAP64
0166         void *ptr = mmap64(nullptr, size, mmap_flag, MAP_SHARED, fd, off);
0167 #else
0168         void *ptr = mmap(0, size, mmap_flag, MAP_SHARED, fd, off);
0169 #endif
0170         if (ptr == MAP_FAILED) {
0171             Out(SYS_DIO | LOG_DEBUG) << "mmap failed : " << QString(strerror(errno)) << endl;
0172             return nullptr;
0173         } else {
0174             CacheFile::Entry e;
0175             e.thing = thing;
0176             e.offset = off;
0177             e.ptr = ptr;
0178             e.diff = 0;
0179             e.size = size;
0180             e.mode = mode;
0181             mappings.insert(ptr, e);
0182             return ptr;
0183         }
0184     }
0185 #else // Q_WS_WIN
0186     char *ptr = (char *)fptr->map(off, size);
0187 
0188     if (!ptr) {
0189         Out(SYS_DIO | LOG_DEBUG) << "mmap failed3 : " << fptr->handle() << " " << QString(strerror(errno)) << endl;
0190         Out(SYS_DIO | LOG_DEBUG) << off << " " << size << endl;
0191         return 0;
0192     } else {
0193         CacheFile::Entry e;
0194         e.thing = thing;
0195         e.offset = off;
0196         e.ptr = ptr;
0197         e.diff = 0;
0198         e.size = size;
0199         e.mode = mode;
0200         mappings.insert(ptr, e);
0201         return ptr;
0202     }
0203 #endif
0204 }
0205 
0206 void CacheFile::growFile(Uint64 to_write)
0207 {
0208     // reopen the file if necessary
0209     if (!fptr) {
0210         //  Out(SYS_DIO|LOG_DEBUG) << "Reopening " << path << endl;
0211         openFile(RW);
0212     }
0213 
0214     if (read_only)
0215         throw Error(i18n("Cannot open %1 for writing: readonly filesystem", path));
0216 
0217     if (file_size + to_write > max_size) {
0218         Out(SYS_DIO | LOG_DEBUG) << "Warning : writing past the end of " << path << endl;
0219         Out(SYS_DIO | LOG_DEBUG) << (file_size + to_write) << " " << max_size << endl;
0220         throw Error(i18n("Cannot expand file %1: attempting to grow the file beyond the maximum size", path));
0221     }
0222 
0223     if (!fptr->resize(file_size + to_write))
0224         throw Error(i18n("Cannot expand file %1: %2", path, fptr->errorString()));
0225 
0226     file_size = fptr->size();
0227 }
0228 
0229 void CacheFile::unmap(void *ptr, Uint32 size)
0230 {
0231     int ret = 0;
0232     QMutexLocker lock(&mutex);
0233 #ifdef Q_OS_WIN
0234     if (!fptr)
0235         return;
0236 
0237     if (mappings.contains(ptr)) {
0238         CacheFile::Entry &e = mappings[ptr];
0239         if (!fptr->unmap((uchar *)e.ptr))
0240             Out(SYS_DIO | LOG_IMPORTANT) << QString("Unmap failed : %1").arg(fptr->errorString()) << endl;
0241 
0242         mappings.remove(ptr);
0243         // no mappings, close temporary
0244         if (mappings.count() == 0)
0245             closeTemporary();
0246     }
0247 #else
0248     // see if it wasn't an offsetted mapping
0249     if (mappings.contains(ptr)) {
0250         CacheFile::Entry &e = mappings[ptr];
0251 #ifdef HAVE_MUNMAP64
0252         ret = munmap64(e.ptr, e.size);
0253 #else
0254         ret = munmap(e.ptr, e.size);
0255 #endif
0256         if (ret < 0)
0257             Out(SYS_DIO | LOG_IMPORTANT) << QString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl;
0258 
0259         mappings.remove(ptr);
0260         // no mappings, close temporary
0261         if (mappings.count() == 0)
0262             closeTemporary();
0263     }
0264 #endif
0265 }
0266 
0267 void CacheFile::aboutToClose()
0268 {
0269     QMutexLocker lock(&mutex);
0270     if (!fptr)
0271         return;
0272     // Out(SYS_DIO|LOG_NOTICE) << "CacheFile " << path << " : about to be closed" << endl;
0273     unmapAll();
0274     if (!manual_close) {
0275         manual_close = true;
0276         fptr->deleteLater();
0277         fptr = nullptr;
0278         manual_close = false;
0279     }
0280 }
0281 
0282 void CacheFile::unmapAll()
0283 {
0284     QMap<void *, Entry>::iterator i = mappings.begin();
0285     while (i != mappings.end()) {
0286         int ret = 0;
0287         CacheFile::Entry &e = i.value();
0288 #ifdef Q_OS_WIN
0289         fptr->unmap((uchar *)e.ptr);
0290 #else
0291 #ifdef HAVE_MUNMAP64
0292         ret = munmap64(e.ptr, e.size);
0293 #else
0294         ret = munmap(e.ptr, e.size);
0295 #endif // HAVE_MUNMAP64
0296 #endif // Q_OS_WIN
0297         e.thing->unmapped();
0298         // if it will be reopenend, we will not remove all mappings
0299         // so that they will be redone on reopening
0300         ++i;
0301         mappings.remove(e.ptr);
0302         if (ret < 0) {
0303             Out(SYS_DIO | LOG_IMPORTANT) << QString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl;
0304         }
0305     }
0306 }
0307 
0308 void CacheFile::close()
0309 {
0310     QMutexLocker lock(&mutex);
0311 
0312     if (!fptr)
0313         return;
0314 
0315     unmapAll();
0316     manual_close = true;
0317     fptr->close();
0318     delete fptr;
0319     fptr = nullptr;
0320     manual_close = false;
0321 }
0322 
0323 void CacheFile::read(Uint8 *buf, Uint32 size, Uint64 off)
0324 {
0325     QMutexLocker lock(&mutex);
0326     bool close_again = false;
0327 
0328     // reopen the file if necessary
0329     if (!fptr) {
0330         //  Out(SYS_DIO|LOG_DEBUG) << "Reopening " << path << endl;
0331         openFile(READ);
0332         close_again = true;
0333     }
0334 
0335     if (off >= file_size || off >= max_size) {
0336         throw Error(i18n("Error: Reading past the end of the file %1", path));
0337     }
0338 
0339     // jump to right position
0340     if (!fptr->seek(off))
0341         throw Error(i18n("Failed to seek file %1: %2", path, fptr->errorString()));
0342 
0343     Uint32 sz = 0;
0344     if ((sz = fptr->read((char *)buf, size)) != size) {
0345         if (close_again)
0346             closeTemporary();
0347 
0348         throw Error(i18n("Error reading from %1", path));
0349     }
0350 
0351     if (close_again)
0352         closeTemporary();
0353 }
0354 
0355 void CacheFile::write(const Uint8 *buf, Uint32 size, Uint64 off)
0356 {
0357     QMutexLocker lock(&mutex);
0358     bool close_again = false;
0359 
0360     // reopen the file if necessary
0361     if (!fptr) {
0362         //  Out(SYS_DIO|LOG_DEBUG) << "Reopening " << path << endl;
0363         openFile(RW);
0364         close_again = true;
0365     }
0366 
0367     if (read_only)
0368         throw Error(i18n("Cannot open %1 for writing: readonly filesystem", path));
0369 
0370     if (off + size > max_size) {
0371         Out(SYS_DIO | LOG_DEBUG) << "Warning : writing past the end of " << path << endl;
0372         Out(SYS_DIO | LOG_DEBUG) << (off + size) << " " << max_size << endl;
0373         throw Error(i18n("Attempting to write beyond the maximum size of %1", path));
0374     }
0375 
0376     if (file_size < off) {
0377         // Out(SYS_DIO|LOG_DEBUG) << QString("Writing %1 bytes at %2").arg(size).arg(off) << endl;
0378         growFile(off - file_size);
0379     }
0380 
0381     // jump to right position
0382     if (!fptr->seek(off))
0383         throw Error(i18n("Failed to seek file %1: %2", path, fptr->errorString()));
0384 
0385     if (fptr->write((const char *)buf, size) != size) {
0386         throw Error(i18n("Failed to write to file %1: %2", path, fptr->errorString()));
0387     }
0388 
0389     if (close_again)
0390         closeTemporary();
0391 
0392     if (off + size > file_size)
0393         file_size = off + size;
0394 }
0395 
0396 void CacheFile::closeTemporary()
0397 {
0398     if (!fptr || mappings.count() > 0)
0399         return;
0400 
0401     delete fptr;
0402     fptr = nullptr;
0403 }
0404 
0405 void CacheFile::preallocate(PreallocationThread *prealloc)
0406 {
0407     QMutexLocker lock(&mutex);
0408 
0409     if (FileSize(path) == max_size) {
0410         Out(SYS_GEN | LOG_NOTICE) << "File " << path << " already big enough" << endl;
0411         return;
0412     }
0413 
0414     Out(SYS_GEN | LOG_NOTICE) << "Preallocating file " << path << " (" << max_size << " bytes)" << endl;
0415     bool close_again = false;
0416     if (!fptr) {
0417         openFile(RW);
0418         close_again = true;
0419     }
0420 
0421     int fd = fptr->handle();
0422 
0423     if (read_only) {
0424         if (close_again)
0425             closeTemporary();
0426 
0427         throw Error(i18n("Cannot open %1 for writing: readonly filesystem", path));
0428     }
0429 
0430     try {
0431         bool res = false;
0432 
0433 #ifdef HAVE_XFS_XFS_H
0434         if (Cache::preallocateFully()) {
0435             res = XfsPreallocate(fd, max_size);
0436         }
0437 #endif
0438 
0439         if (!res) {
0440             bt::TruncateFile(fd, max_size, !Cache::preallocateFully());
0441         }
0442     } catch (bt::Error &e) {
0443         throw Error(i18n("Cannot preallocate diskspace: %1", strerror(errno)));
0444     }
0445 
0446     file_size = FileSize(fd);
0447     prealloc->written(file_size);
0448     Out(SYS_GEN | LOG_DEBUG) << "file_size = " << file_size << endl;
0449     if (close_again)
0450         closeTemporary();
0451 }
0452 
0453 Uint64 CacheFile::diskUsage()
0454 {
0455     if (!fptr)
0456         return DiskUsage(path);
0457     else
0458         return DiskUsage(fptr->handle());
0459 }
0460 
0461 bool CacheFile::allocateBytes(Uint64 off, Uint64 size)
0462 {
0463 #ifdef HAVE_POSIX_FALLOCATE64
0464     return posix_fallocate64(fptr->handle(), off, size) != ENOSPC;
0465 #elif HAVE_POSIX_FALLOCATE
0466     return posix_fallocate(fptr->handle(), off, size) != ENOSPC;
0467 #else
0468     return true;
0469 #endif
0470 }
0471 
0472 }
0473 
0474 #include "moc_cachefile.cpp"