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"