File indexing completed on 2024-05-05 05:53:47

0001 /*
0002     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "HistoryFile.h"
0009 
0010 // Konsole
0011 #include "KonsoleSettings.h"
0012 #include "characters/Character.h"
0013 #include "konsoledebug.h"
0014 
0015 // System
0016 #include <cerrno>
0017 #ifndef Q_OS_WIN
0018 #include <unistd.h>
0019 #endif
0020 
0021 // Qt
0022 #include <QDir>
0023 #include <QUrl>
0024 
0025 // KDE
0026 #include <KConfigGroup>
0027 #include <KSharedConfig>
0028 
0029 using namespace Konsole;
0030 
0031 Q_GLOBAL_STATIC(QString, historyFileLocation)
0032 
0033 // History File ///////////////////////////////////////////
0034 HistoryFile::HistoryFile()
0035     : _length(0)
0036     , _fileMap(nullptr)
0037     , _readWriteBalance(0)
0038 {
0039     // Determine the temp directory once
0040     // This class is called 3 times for each "unlimited" scrollback.
0041     // This has the down-side that users must restart to
0042     // load changes.
0043     if (!historyFileLocation.exists()) {
0044         QString fileLocation;
0045         KSharedConfigPtr appConfig = KSharedConfig::openConfig();
0046         if (qApp->applicationName() != QLatin1String("konsole")) {
0047             // Check if "kpart"rc has "FileLocation" group; AFAIK
0048             // only possible if user manually added it. If not
0049             // found, use konsole's config.
0050             if (!appConfig->hasGroup(QStringLiteral("FileLocation"))) {
0051                 appConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc"));
0052             }
0053         }
0054 
0055         KConfigGroup configGroup = appConfig->group(QStringLiteral("FileLocation"));
0056         if (configGroup.readEntry("scrollbackUseCacheLocation", false)) {
0057             fileLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0058         } else if (configGroup.readEntry("scrollbackUseSpecifiedLocation", false)) {
0059             const QUrl specifiedUrl = KonsoleSettings::scrollbackUseSpecifiedLocationDirectory();
0060             fileLocation = specifiedUrl.path();
0061         } else {
0062             fileLocation = QDir::tempPath();
0063         }
0064         // Validate file location
0065         const QFileInfo fi(fileLocation);
0066         if (fileLocation.isEmpty() || !fi.exists() || !fi.isDir() || !fi.isWritable()) {
0067             qCWarning(KonsoleDebug) << "Invalid scrollback folder " << fileLocation << "; using "
0068                                     << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0069             // Per Qt docs, this path is never empty; not sure if that
0070             // means it always exists.
0071             fileLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0072             const QFileInfo fi2(fileLocation);
0073             if (!fi2.exists()) {
0074                 if (!QDir().mkpath(fileLocation)) {
0075                     qCWarning(KonsoleDebug) << "Unable to create scrollback folder " << fileLocation;
0076                 }
0077             }
0078         }
0079         *historyFileLocation() = fileLocation;
0080     }
0081     const QString tmpDir = *historyFileLocation();
0082     const QString tmpFormat = tmpDir + QLatin1Char('/') + QLatin1String("konsole-XXXXXX.history");
0083     _tmpFile.setFileTemplate(tmpFormat);
0084     if (_tmpFile.open()) {
0085 #if defined(Q_OS_LINUX)
0086         qCDebug(KonsoleDebug, "HistoryFile: /proc/%lld/fd/%d", qApp->applicationPid(), _tmpFile.handle());
0087 #endif
0088         // On some systems QTemporaryFile creates unnamed file.
0089         // Do not interfere in such cases.
0090         if (_tmpFile.exists()) {
0091             // Remove file entry from filesystem. Since the file
0092             // is opened, it will still be available for reading
0093             // and writing. This guarantees the file won't remain
0094             // in filesystem after process termination, even when
0095             // there was a crash.
0096 #ifndef Q_OS_WIN
0097             unlink(QFile::encodeName(_tmpFile.fileName()).constData());
0098 #else
0099             // TODO Windows
0100 #endif
0101         }
0102     }
0103 }
0104 
0105 HistoryFile::~HistoryFile()
0106 {
0107     if (_fileMap != nullptr) {
0108         unmap();
0109     }
0110 }
0111 
0112 // TODO:  Mapping the entire file in will cause problems if the history file becomes exceedingly large,
0113 //(ie. larger than available memory).  HistoryFile::map() should only map in sections of the file at a time,
0114 // to avoid this.
0115 void HistoryFile::map()
0116 {
0117     Q_ASSERT(_fileMap == nullptr);
0118 
0119     if (_tmpFile.flush()) {
0120         Q_ASSERT(_tmpFile.size() >= _length);
0121         _fileMap = _tmpFile.map(0, _length);
0122     }
0123 
0124     // if mmap'ing fails, fall back to the read-lseek combination
0125     if (_fileMap == nullptr) {
0126         _readWriteBalance = 0;
0127         qCDebug(KonsoleDebug) << "mmap'ing history failed.  errno = " << errno;
0128     }
0129 }
0130 
0131 void HistoryFile::unmap()
0132 {
0133     Q_ASSERT(_fileMap != nullptr);
0134 
0135     if (_tmpFile.unmap(_fileMap)) {
0136         _fileMap = nullptr;
0137     }
0138 
0139     Q_ASSERT(_fileMap == nullptr);
0140 }
0141 
0142 void HistoryFile::add(const char *buffer, qint64 count)
0143 {
0144     if (_fileMap != nullptr) {
0145         unmap();
0146     }
0147 
0148     if (_readWriteBalance < INT_MAX) {
0149         _readWriteBalance++;
0150     }
0151 
0152     qint64 rc = 0;
0153 
0154     if (!_tmpFile.seek(_length)) {
0155         perror("HistoryFile::add.seek");
0156         return;
0157     }
0158     rc = _tmpFile.write(buffer, count);
0159     if (rc < 0) {
0160         perror("HistoryFile::add.write");
0161         return;
0162     }
0163     _length += rc;
0164 }
0165 
0166 void HistoryFile::get(char *buffer, qint64 size, qint64 loc)
0167 {
0168     if (loc < 0 || size < 0 || loc + size > (qint64)(_length * sizeof(LineProperty))) {
0169         fprintf(stderr, "getHist(...,%lld,%lld): invalid args.\n", size, loc);
0170         return;
0171     }
0172 
0173     // count number of get() calls vs. number of add() calls.
0174     // If there are many more get() calls compared with add()
0175     // calls (decided by using MAP_THRESHOLD) then mmap the log
0176     // file to improve performance.
0177     if (_readWriteBalance > INT_MIN) {
0178         _readWriteBalance--;
0179     }
0180     if ((_fileMap == nullptr) && _readWriteBalance < MAP_THRESHOLD) {
0181         map();
0182     }
0183 
0184     if (_fileMap != nullptr) {
0185         memcpy(buffer, _fileMap + loc, size);
0186     } else {
0187         qint64 rc = 0;
0188 
0189         if (!_tmpFile.seek(loc)) {
0190             perror("HistoryFile::get.seek");
0191             return;
0192         }
0193         rc = _tmpFile.read(buffer, size);
0194         if (rc < 0) {
0195             perror("HistoryFile::get.read");
0196             return;
0197         }
0198     }
0199 }
0200 
0201 void HistoryFile::set(char *buffer, qint64 size, qint64 loc)
0202 {
0203     if (loc < 0 || size < 0 || loc + size > qint64(_length * sizeof(LineProperty))) {
0204         fprintf(stderr, "setHist(...,%lld,%lld): invalid args.\n", size, loc);
0205         return;
0206     }
0207 
0208     // count number of get() calls vs. number of add() calls.
0209     // If there are many more get() calls compared with add()
0210     // calls (decided by using MAP_THRESHOLD) then mmap the log
0211     // file to improve performance.
0212     if (_readWriteBalance > INT_MIN) {
0213         _readWriteBalance--;
0214     }
0215     if ((_fileMap == nullptr) && _readWriteBalance < MAP_THRESHOLD) {
0216         map();
0217     }
0218 
0219     if (_fileMap != nullptr) {
0220         memcpy(_fileMap + loc, buffer, size);
0221     } else {
0222         qint64 rc = 0;
0223 
0224         if (!_tmpFile.seek(loc)) {
0225             perror("HistoryFile::set.seek");
0226             return;
0227         }
0228         rc = _tmpFile.write(buffer, size);
0229         if (rc < 0) {
0230             perror("HistoryFile::set.write");
0231             return;
0232         }
0233     }
0234 }
0235 
0236 void HistoryFile::removeLast(qint64 loc)
0237 {
0238     if (loc < 0 || loc > _length) {
0239         fprintf(stderr, "removeLast(%lld): invalid args.\n", loc);
0240         return;
0241     }
0242     _length = loc;
0243 }
0244 
0245 qint64 HistoryFile::len() const
0246 {
0247     return _length;
0248 }