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 }