commit 8f2747f04a99ae45c9210d1ef9d2478d48309ec8
parent 2d6f8badda43275bb23f9a76765fa702085d8ea2
Author: KEINOS <github+fork-qiita-news@keinos.com>
Date: Mon, 22 Sep 2025 16:33:53 +0000
Merge remote-tracking branch 'upstream/master'
Diffstat:
6 files changed, 141 insertions(+), 7 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
@@ -582,6 +582,7 @@ vns
vsconfig
vstest
waitable
+wal
wcex
WDAG
webpages
diff --git a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp
@@ -233,3 +233,49 @@ TEST_CASE("TrackingCatalog_Overlapping_ARP_Range", "[tracking_catalog]")
REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) ==
manifest.Version);
}
+
+TEST_CASE("TrackingCatalog_Corrupt", "[tracking_catalog]")
+{
+ TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
+ INFO("Using temporary file named: " << tempFile.GetPath());
+
+ SourceDetails details;
+ Manifest manifest;
+ std::string relativePath;
+ auto source = SimpleTestSetup(tempFile, details, manifest, relativePath);
+
+ SearchRequest request;
+ request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id);
+
+ std::filesystem::path catalogFile;
+
+ {
+ // Add data to initial database
+ PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source);
+
+ SearchResult resultBefore = catalog.Search(request);
+ REQUIRE(resultBefore.Matches.size() == 0);
+
+ catalog.RecordInstall(manifest, manifest.Installers[0], false);
+
+ SearchResult resultAfter = catalog.Search(request);
+ REQUIRE(resultAfter.Matches.size() == 1);
+ REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1);
+
+ catalogFile = catalog.GetFilePath();
+ }
+
+ {
+ std::ofstream file{ catalogFile, std::ios_base::trunc };
+ file << "Corrupted!";
+ }
+
+ {
+ // Open database again after "corruption"
+ PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source);
+
+ // Should not find anything in new database
+ SearchResult resultBefore = catalog.Search(request);
+ REQUIRE(resultBefore.Matches.size() == 0);
+ }
+}
diff --git a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp
@@ -17,6 +17,7 @@ namespace AppInstaller::Repository
namespace
{
constexpr std::string_view c_PackageTrackingFileName = "installed.db";
+ constexpr std::string_view c_PackageTrackingCorruptedFileName = "installed-corrupted.db";
std::string CreateNameForCPL(const std::string& pathName)
{
@@ -32,18 +33,38 @@ namespace AppInstaller::Repository
}
// Call while holding the CrossProcessLock
+ SQLiteIndex CreateOnlyTrackingIndex(const std::filesystem::path& trackingDB)
+ {
+ return SQLiteIndex::CreateNew(trackingDB.u8string(), SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport);
+ }
+
+ // Call while holding the CrossProcessLock
SQLiteIndex CreateOrOpenTrackingIndex(const std::filesystem::path& trackingDB)
{
if (!std::filesystem::exists(trackingDB))
{
std::filesystem::create_directories(trackingDB.parent_path());
- return SQLiteIndex::CreateNew(trackingDB.u8string(), SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport);
+ return CreateOnlyTrackingIndex(trackingDB);
}
else
{
- // TODO: Check schema version and upgrade as necessary when there is a relevant new schema.
- // Could write this all now but it will be better tested when there is a new schema.
- return SQLiteIndex::Open(trackingDB.u8string(), SQLiteIndex::OpenDisposition::ReadWrite);
+ try
+ {
+ // TODO: Check schema version and upgrade as necessary when there is a relevant new schema.
+ // Could write this all now but it will be better tested when there is a new schema.
+ return SQLiteIndex::Open(trackingDB.u8string(), SQLiteIndex::OpenDisposition::ReadWrite);
+ }
+ catch(...)
+ {
+ LOG_CAUGHT_EXCEPTION_MSG("Exception opening tracking catalog");
+ }
+
+ // Move existing database and create a new one
+ std::filesystem::path destination{ trackingDB };
+ destination.replace_filename(c_PackageTrackingCorruptedFileName);
+ SQLite::SQLiteStorageBase::RenameSQLiteDatabase(trackingDB, destination, true);
+
+ return CreateOnlyTrackingIndex(trackingDB);
}
}
@@ -205,11 +226,11 @@ namespace AppInstaller::Repository
PackageTrackingCatalog::Version::~Version() = default;
PackageTrackingCatalog::Version::Version(PackageTrackingCatalog& catalog, std::shared_ptr<implementation>&& value) :
- m_catalog(catalog), m_implementation(std::move(value)) {}
+ m_catalog(&catalog), m_implementation(std::move(value)) {}
void PackageTrackingCatalog::Version::SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value)
{
- auto& index = m_catalog.m_implementation->Source->GetIndex();
+ auto& index = m_catalog->m_implementation->Source->GetIndex();
index.SetMetadataByManifestId(m_implementation->Id, metadata, value);
}
@@ -287,6 +308,13 @@ namespace AppInstaller::Repository
}
}
+#ifndef AICLI_DISABLE_TEST_HOOKS
+ std::filesystem::path PackageTrackingCatalog::GetFilePath() const
+ {
+ return m_implementation->Source->GetIndex().GetContextData().Get<Schema::Property::DatabaseFilePath>();
+ }
+#endif
+
std::unique_ptr<ISourceFactory> PackageTrackingCatalogSourceFactory::Create()
{
return std::make_unique<PackageTrackingCatalogSourceFactoryImpl>();
diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h b/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h
@@ -6,6 +6,9 @@
#include <memory>
+#ifndef AICLI_DISABLE_TEST_HOOKS
+#include <filesystem>
+#endif
namespace AppInstaller::Repository
{
@@ -55,7 +58,7 @@ namespace AppInstaller::Repository
struct implementation;
Version(PackageTrackingCatalog& catalog, std::shared_ptr<implementation>&& value);
std::shared_ptr<implementation> m_implementation;
- PackageTrackingCatalog& m_catalog;
+ PackageTrackingCatalog* m_catalog;
};
// Records an installation of the given package.
@@ -64,6 +67,11 @@ namespace AppInstaller::Repository
// Records an uninstall of the given package.
void RecordUninstall(const Utility::LocIndString& packageIdentifier);
+#ifndef AICLI_DISABLE_TEST_HOOKS
+ // Gets the path to the database file.
+ std::filesystem::path GetFilePath() const;
+#endif
+
protected:
// Creates or opens the tracking catalog for the given source.
static PackageTrackingCatalog CreateForSource(const Source& source);
diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h
@@ -5,6 +5,7 @@
#include <winget/SQLiteVersion.h>
#include <winget/ManagedFile.h>
+#include <filesystem>
#include <mutex>
namespace AppInstaller::SQLite
@@ -32,6 +33,11 @@ namespace AppInstaller::SQLite
// Gets the schema version of the database.
const Version& GetVersion() const { return m_version; }
+ // Renames the database file and any auxiliary files given the inputs.
+ // Should only be used on an inactive database.
+ // If overwrite is given, existing destination files will be removed first.
+ static void RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite = false);
+
protected:
SQLiteStorageBase(const std::string& target, const Version& version);
diff --git a/src/AppInstallerSharedLib/SQLiteStorageBase.cpp b/src/AppInstallerSharedLib/SQLiteStorageBase.cpp
@@ -23,6 +23,20 @@ namespace AppInstaller::SQLite
return "Unknown";
}
}
+
+ std::filesystem::path AddSuffix(const std::filesystem::path& source, std::wstring_view suffix)
+ {
+ std::filesystem::path result{ source };
+
+ if (!suffix.empty())
+ {
+ std::wstring filename = result.filename().wstring();
+ filename += suffix;
+ result.replace_filename(std::move(filename));
+ }
+
+ return result;
+ }
}
// One method for converting open disposition to proper open disposition
@@ -45,6 +59,37 @@ namespace AppInstaller::SQLite
return MetadataTable::TryGetNamedValue<std::string>(m_dbconn, s_MetadataValueName_DatabaseIdentifier).value_or(std::string{});
}
+ void SQLiteStorageBase::RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite)
+ {
+ auto fileSuffixes = { L"", L"-journal", L"-wal" };
+
+ THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, !std::filesystem::exists(source));
+ THROW_WIN32_IF(ERROR_DIRECTORY, std::filesystem::is_directory(source));
+
+ if (overwrite)
+ {
+ for (const auto& suffix : fileSuffixes)
+ {
+ std::filesystem::path target = AddSuffix(destination, suffix);
+
+ if (std::filesystem::exists(target))
+ {
+ std::filesystem::remove_all(target);
+ }
+ }
+ }
+
+ for (const auto& suffix : fileSuffixes)
+ {
+ std::filesystem::path target = AddSuffix(source, suffix);
+
+ if (std::filesystem::exists(target))
+ {
+ std::filesystem::rename(target, AddSuffix(destination, suffix));
+ }
+ }
+ }
+
SQLiteStorageBase::SQLiteStorageBase(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& file) :
m_indexFile(std::move(file))
{