File indexing completed on 2024-05-05 04:38:10

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "itemrepositoryregistry.h"
0008 
0009 #include <QDir>
0010 #include <QProcessEnvironment>
0011 #include <QCoreApplication>
0012 #include <QDataStream>
0013 #include <QMutexLocker>
0014 #include <QRecursiveMutex>
0015 #include <QStandardPaths>
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <util/shellutils.h>
0020 
0021 #include "abstractitemrepository.h"
0022 #include "debug.h"
0023 
0024 #include <mutex>
0025 #include <set>
0026 #include <utility>
0027 
0028 using namespace KDevelop;
0029 
0030 namespace {
0031 //If KDevelop crashed this many times consecutively, clean up the repository
0032 const int crashesBeforeCleanup = 1;
0033 
0034 void setCrashCounter(QFile& crashesFile, int count)
0035 {
0036     crashesFile.close();
0037     crashesFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
0038     QDataStream writeStream(&crashesFile);
0039     writeStream << count;
0040 }
0041 
0042 bool shouldClear(const QString& path)
0043 {
0044     QDir dir(path);
0045 
0046     if (!dir.exists()) {
0047         return false;
0048     }
0049 
0050     if (getenv("CLEAR_DUCHAIN_DIR")) {
0051         qCDebug(SERIALIZATION) << "clearing duchain directory because CLEAR_DUCHAIN_DIR is set";
0052         return true;
0053     }
0054 
0055     if (dir.exists(QStringLiteral("is_writing"))) {
0056         qCWarning(SERIALIZATION) << "repository" << path << "was write-locked, it probably is inconsistent";
0057         return true;
0058     }
0059 
0060     if (!dir.exists(QStringLiteral("version_%1").arg(staticItemRepositoryVersion()))) {
0061         qCWarning(SERIALIZATION) << "version mismatch or no version hint; expected version:" <<
0062             staticItemRepositoryVersion();
0063         return true;
0064     }
0065 
0066     QFile crashesFile(dir.filePath(QStringLiteral("crash_counter")));
0067     if (crashesFile.open(QIODevice::ReadOnly)) {
0068         int count;
0069         QDataStream stream(&crashesFile);
0070         stream >> count;
0071 
0072         qCDebug(SERIALIZATION) << "current count of crashes: " << count;
0073 
0074         if (count >= crashesBeforeCleanup && !getenv("DONT_CLEAR_DUCHAIN_DIR")) {
0075             bool userAnswer = askUser(i18np("The previous session crashed.", "Session crashed %1 times in a row.",
0076                                             count),
0077                                       i18nc("tty action", "Clear cache"),
0078                                       i18nc("@title", "Session Crashed"),
0079                                       i18n("The crash may be caused by a corruption of cached data.\n\n"
0080                                            "Press Clear if you want KDevelop to clear the cache, otherwise press Continue if you are sure the crash has another origin."),
0081                                       i18nc("@action:button", "Clear Cache"),
0082                                       i18nc("@action:button", "Continue"));
0083             if (userAnswer) {
0084                 qCDebug(SERIALIZATION) << "User chose to clean repository";
0085                 return true;
0086             } else {
0087                 setCrashCounter(crashesFile, 1);
0088                 qCDebug(SERIALIZATION) << "User chose to reset crash counter";
0089             }
0090         } else {
0091             ///Increase the crash-count. It will be reset if kdevelop is shut down cleanly.
0092             setCrashCounter(crashesFile, ++count);
0093         }
0094     } else {
0095         setCrashCounter(crashesFile, 1);
0096     }
0097 
0098     return false;
0099 }
0100 }
0101 
0102 namespace KDevelop {
0103 class ItemRepositoryRegistryPrivate
0104 {
0105 public:
0106     bool m_shallDelete = false;
0107     bool m_wasShutdown = false;
0108     QString m_path;
0109     // TODO: is the order of repositories important? If not, store them in a QSet rather than std::set.
0110     std::set<AbstractItemRepository*> m_repositories;
0111     QMap<QString, QAtomicInt*> m_customCounters;
0112     mutable QRecursiveMutex m_mutex;
0113 
0114     /// @param path  A shared directory-path that the item-repositories are to be loaded from.
0115     /// @note        Currently the given path must reference a hidden directory, just to make sure we're
0116     ///              not accidentally deleting something important.
0117     explicit ItemRepositoryRegistryPrivate(const QString& path);
0118 
0119     QAtomicInt& customCounter(const QString& identity, int initialValue);
0120     void lockForWriting();
0121     void unlockForWriting();
0122     void deleteDataDirectory(bool recreate = true);
0123 };
0124 
0125 //The global item-repository registry
0126 ItemRepositoryRegistry* ItemRepositoryRegistry::m_self = nullptr;
0127 
0128 ItemRepositoryRegistry::ItemRepositoryRegistry(const QString& repositoryPath)
0129     : d_ptr(new ItemRepositoryRegistryPrivate(repositoryPath))
0130 {
0131 }
0132 
0133 void ItemRepositoryRegistry::initialize(const QString& repositoryPath)
0134 {
0135     if (!m_self) {
0136         ///We intentionally leak the registry, to prevent problems in the destruction order, where
0137         ///the actual repositories might get deleted later than the repository registry.
0138         m_self = new ItemRepositoryRegistry(repositoryPath);
0139     }
0140     m_self->d_func()->m_wasShutdown = false;
0141     m_self->d_func()->m_shallDelete = false;
0142 }
0143 
0144 ItemRepositoryRegistry* ItemRepositoryRegistry::self()
0145 {
0146     Q_ASSERT(m_self);
0147     return m_self;
0148 }
0149 
0150 void ItemRepositoryRegistry::deleteRepositoryFromDisk(const QString& repositoryPath)
0151 {
0152     // Now, as we have only the global item-repository registry, assume that if and only if
0153     // the given session is ours, its cache path is used by the said global item-repository registry.
0154     if (m_self && !m_self->d_func()->m_wasShutdown && m_self->d_func()->m_path == repositoryPath) {
0155         // remove later
0156         m_self->d_func()->m_shallDelete = true;
0157     } else {
0158         // Otherwise, given session is not ours.
0159         // remove its item-repository directory directly.
0160         QDir(repositoryPath).removeRecursively();
0161     }
0162 }
0163 
0164 QRecursiveMutex& ItemRepositoryRegistry::mutex()
0165 {
0166     Q_D(ItemRepositoryRegistry);
0167 
0168     return d->m_mutex;
0169 }
0170 
0171 QAtomicInt& ItemRepositoryRegistryPrivate::customCounter(const QString& identity, int initialValue)
0172 {
0173     auto customCounterIt = m_customCounters.find(identity);
0174     if (customCounterIt == m_customCounters.end()) {
0175         customCounterIt = m_customCounters.insert(identity, new QAtomicInt(initialValue));
0176     }
0177     return **customCounterIt;
0178 }
0179 
0180 QAtomicInt& ItemRepositoryRegistry::customCounter(const QString& identity, int initialValue)
0181 {
0182     Q_D(ItemRepositoryRegistry);
0183 
0184     return d->customCounter(identity, initialValue);
0185 }
0186 
0187 ///The global item-repository registry that is used by default
0188 ItemRepositoryRegistry& globalItemRepositoryRegistry()
0189 {
0190     return *ItemRepositoryRegistry::self();
0191 }
0192 
0193 void ItemRepositoryRegistry::registerRepository(AbstractItemRepository* repository)
0194 {
0195     Q_D(ItemRepositoryRegistry);
0196 
0197     QMutexLocker lock(&d->m_mutex);
0198     d->m_repositories.insert(repository);
0199     if (!d->m_path.isEmpty()) {
0200         // Locking the repository is documented as the caller's responsibility.
0201         if (!repository->open(d->m_path)) {
0202             d->deleteDataDirectory();
0203             qCritical() << "failed to open a repository";
0204             abort();
0205         }
0206     }
0207 }
0208 
0209 QString ItemRepositoryRegistry::path() const
0210 {
0211     Q_D(const ItemRepositoryRegistry);
0212 
0213     //We cannot lock the mutex here, since this may be called with one of the repositories locked,
0214     //and that may lead to a deadlock when at the same time a storing is requested
0215     return d->m_path;
0216 }
0217 
0218 void ItemRepositoryRegistryPrivate::lockForWriting()
0219 {
0220     QMutexLocker lock(&m_mutex);
0221     //Create is_writing
0222     QFile f(m_path + QLatin1String("/is_writing"));
0223     f.open(QIODevice::WriteOnly);
0224     f.close();
0225 }
0226 
0227 void ItemRepositoryRegistry::lockForWriting()
0228 {
0229     Q_D(ItemRepositoryRegistry);
0230 
0231     d->lockForWriting();
0232 }
0233 
0234 void ItemRepositoryRegistryPrivate::unlockForWriting()
0235 {
0236     QMutexLocker lock(&m_mutex);
0237     //Delete is_writing
0238     QFile::remove(m_path + QLatin1String("/is_writing"));
0239 }
0240 
0241 void ItemRepositoryRegistry::unlockForWriting()
0242 {
0243     Q_D(ItemRepositoryRegistry);
0244 
0245     d->unlockForWriting();
0246 }
0247 
0248 void ItemRepositoryRegistry::unRegisterRepository(AbstractItemRepository* repository)
0249 {
0250     Q_D(ItemRepositoryRegistry);
0251 
0252     QMutexLocker lock(&d->m_mutex);
0253     Q_ASSERT(d->m_repositories.count(repository) == 1);
0254     d->m_repositories.erase(repository);
0255 }
0256 
0257 //After calling this, the data-directory may be a new one
0258 void ItemRepositoryRegistryPrivate::deleteDataDirectory(bool recreate)
0259 {
0260     QMutexLocker lock(&m_mutex);
0261 
0262     bool result = QDir(m_path).removeRecursively();
0263     Q_ASSERT(result);
0264     Q_UNUSED(result);
0265     // Just recreate the directory then; leave old path (as it is dependent on appname and session only).
0266     if (recreate) {
0267         QDir().mkpath(m_path);
0268     }
0269 }
0270 
0271 ItemRepositoryRegistryPrivate::ItemRepositoryRegistryPrivate(const QString& path)
0272     : m_path(path)
0273 {
0274     Q_ASSERT(!path.isEmpty());
0275 
0276     // Check if the repository shall be cleared
0277     if (shouldClear(path)) {
0278         qCWarning(SERIALIZATION) << QStringLiteral("The data-repository at %1 has to be cleared.").arg(path);
0279         deleteDataDirectory(false);
0280     }
0281 
0282     QDir().mkpath(path);
0283 
0284     QFile f(path + QLatin1String("/Counters"));
0285     if (f.open(QIODevice::ReadOnly)) {
0286         QDataStream stream(&f);
0287 
0288         while (!stream.atEnd()) {
0289             //Read in all custom counter values
0290             QString counterName;
0291             stream >> counterName;
0292             int counterValue;
0293             stream >> counterValue;
0294             customCounter(counterName, 0) = counterValue;
0295         }
0296     }
0297 }
0298 
0299 void ItemRepositoryRegistry::store()
0300 {
0301     Q_D(ItemRepositoryRegistry);
0302 
0303     QMutexLocker lock(&d->m_mutex);
0304     for (auto* repository : std::as_const(d->m_repositories)) {
0305         std::scoped_lock repoLock(*repository);
0306         repository->store();
0307     }
0308 
0309     QFile versionFile(d->m_path + QStringLiteral("/version_%1").arg(staticItemRepositoryVersion()));
0310     if (versionFile.open(QIODevice::WriteOnly)) {
0311         versionFile.close();
0312     } else {
0313         qCWarning(SERIALIZATION) << "Could not open version file for writing";
0314     }
0315 
0316     //Store all custom counter values
0317     QFile f(d->m_path + QLatin1String("/Counters"));
0318     if (f.open(QIODevice::WriteOnly)) {
0319         f.resize(0);
0320         QDataStream stream(&f);
0321         for (QMap<QString, QAtomicInt*>::const_iterator it = d->m_customCounters.constBegin();
0322              it != d->m_customCounters.constEnd();
0323              ++it) {
0324             stream << it.key();
0325             stream << it.value()->fetchAndAddRelaxed(0);
0326         }
0327     } else {
0328         qCWarning(SERIALIZATION) << "Could not open counter file for writing";
0329     }
0330 }
0331 
0332 void ItemRepositoryRegistry::printAllStatistics() const
0333 {
0334     Q_D(const ItemRepositoryRegistry);
0335 
0336     QMutexLocker lock(&d->m_mutex);
0337     for (auto* repository : std::as_const(d->m_repositories)) {
0338         std::scoped_lock repoLock(*repository);
0339         qCDebug(SERIALIZATION) << "statistics in" << repository->repositoryName() << ":";
0340         qCDebug(SERIALIZATION) << repository->printStatistics();
0341     }
0342 }
0343 
0344 int ItemRepositoryRegistry::finalCleanup()
0345 {
0346     Q_D(ItemRepositoryRegistry);
0347 
0348     QMutexLocker lock(&d->m_mutex);
0349     int changed = false;
0350     for (auto* repository : std::as_const(d->m_repositories)) {
0351         std::scoped_lock repoLock(*repository);
0352         int added = repository->finalCleanup();
0353         changed += added;
0354         qCDebug(SERIALIZATION) << "cleaned in" << repository->repositoryName() << ":" << added;
0355     }
0356 
0357     return changed;
0358 }
0359 
0360 ItemRepositoryRegistry::~ItemRepositoryRegistry()
0361 {
0362     Q_D(const ItemRepositoryRegistry);
0363 
0364     for (auto* repository : std::as_const(d->m_repositories)) {
0365         std::scoped_lock repoLock(*repository);
0366         repository->close();
0367     }
0368 
0369     for (QAtomicInt* counter : qAsConst(d->m_customCounters)) {
0370         delete counter;
0371     }
0372 }
0373 
0374 void ItemRepositoryRegistry::shutdown()
0375 {
0376     Q_D(ItemRepositoryRegistry);
0377 
0378     QMutexLocker lock(&d->m_mutex);
0379 
0380     // FIXME: we don't close since this can trigger crashes at shutdown
0381     //        since some items are still referenced, e.g. in static variables
0382     // NOTE: ItemRepositoryRegistryPrivate::close() used to close all contained repositories and clear d->m_path
0383     //       under d->m_mutex lock before its code was moved into ~ItemRepositoryRegistry().
0384     //       If this closing is ever uncommented, d->m_path could be cleared after its use in the code below.
0385 //   d->close();
0386 
0387     if (d->m_shallDelete) {
0388         d->deleteDataDirectory(false);
0389     } else {
0390         QFile::remove(d->m_path + QLatin1String("/crash_counter"));
0391     }
0392 
0393     d->m_wasShutdown = true;
0394 }
0395 }