winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ConfigurationDatabase.cpp (17968B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Database/ConfigurationDatabase.h"
      5 #include "Database/Schema/IConfigurationDatabase.h"
      6 #include "ConfigurationUnitResultInformation.h"
      7 #include <AppInstallerStrings.h>
      8 #include <winget/Filesystem.h>
      9 #include "Filesystem.h"
     10 
     11 using namespace AppInstaller::SQLite;
     12 using namespace AppInstaller::Utility;
     13 
     14 namespace winrt::Microsoft::Management::Configuration::implementation
     15 {
     16     namespace
     17     {
     18         // Use an alternate location for the dev build history.
     19 #ifdef AICLI_DISABLE_TEST_HOOKS
     20         constexpr std::string_view s_Database_DirectoryName = "History"sv;
     21 #else
     22         constexpr std::string_view s_Database_DirectoryName = "DevHistory"sv;
     23 #endif
     24 
     25         constexpr std::string_view s_Database_FileName = "config.db"sv;
     26 
     27         #define s_Database_MutexName L"WindowsPackageManager_Configuration_DatabaseMutex"
     28 
     29         std::vector<ConfigurationDatabase::StatusItem> ConvertStatusItems(const std::vector<IConfigurationDatabase::StatusItemTuple>& input)
     30         {
     31             std::vector<ConfigurationDatabase::StatusItem> result;
     32 
     33             for (const auto& item : input)
     34             {
     35                 ConfigurationDatabase::StatusItem statusItem{};
     36                 std::tie(
     37                     statusItem.ChangeIdentifier,
     38                     statusItem.ChangeTime,
     39                     statusItem.SetInstanceIdentifier,
     40                     statusItem.InQueue,
     41                     statusItem.UnitInstanceIdentifier,
     42                     statusItem.State,
     43                     statusItem.ResultCode,
     44                     statusItem.ResultDescription,
     45                     statusItem.ResultDetails,
     46                     statusItem.ResultSource) = item;
     47                 result.emplace_back(std::move(statusItem));
     48             }
     49 
     50             return result;
     51         }
     52     }
     53 
     54     ConfigurationDatabase::ConfigurationDatabase() = default;
     55 
     56     ConfigurationDatabase::ConfigurationDatabase(ConfigurationDatabase&&) = default;
     57     ConfigurationDatabase& ConfigurationDatabase::operator=(ConfigurationDatabase&&) = default;
     58 
     59     ConfigurationDatabase::~ConfigurationDatabase() = default;
     60 
     61     void ConfigurationDatabase::EnsureOpened(bool createIfNeeded)
     62     {
     63 #ifdef AICLI_DISABLE_TEST_HOOKS
     64         // While under development, treat errors escaping this function as a test hook.
     65         try
     66         {
     67 #endif
     68         if (!std::atomic_load(&m_database))
     69         {
     70             std::filesystem::path databaseDirectory = AppInstaller::Filesystem::GetPathTo(PathName::LocalState) / s_Database_DirectoryName;
     71             std::filesystem::path databaseFile = databaseDirectory / s_Database_FileName;
     72 
     73             {
     74                 wil::unique_mutex databaseMutex;
     75                 databaseMutex.create(s_Database_MutexName);
     76                 auto databaseLock = databaseMutex.acquire();
     77 
     78                 if (!std::filesystem::is_regular_file(databaseFile) && createIfNeeded)
     79                 {
     80                     if (std::filesystem::exists(databaseFile))
     81                     {
     82                         std::filesystem::remove_all(databaseDirectory);
     83                     }
     84 
     85                     std::filesystem::create_directories(databaseDirectory);
     86 
     87                     auto connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, IConfigurationDatabase::GetLatestVersion());
     88                     auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) };
     89                     database->InitializeDatabase();
     90 
     91                     std::atomic_store(&m_connection, connection);
     92                     std::atomic_store(&m_database, database);
     93                 }
     94             }
     95 
     96             if (!std::atomic_load(&m_connection))
     97             {
     98                 std::shared_ptr<SQLiteDynamicStorage> empty;
     99                 auto connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
    100                 std::atomic_compare_exchange_strong(&m_connection, &empty, connection);
    101             }
    102 
    103             if (!std::atomic_load(&m_database))
    104             {
    105                 std::shared_ptr<IConfigurationDatabase> empty;
    106                 auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(std::atomic_load(&m_connection), true) };
    107                 std::atomic_compare_exchange_strong(&m_database, &empty, database);
    108             }
    109         }
    110 #ifdef AICLI_DISABLE_TEST_HOOKS
    111         }
    112         CATCH_LOG();
    113 #endif
    114     }
    115 
    116     template <typename OperationT>
    117     auto ConfigurationDatabase::ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase) const
    118     {
    119         using ResultT = decltype(operation(std::declval<std::shared_ptr<IConfigurationDatabase>&>()));
    120         ResultT result{};
    121 
    122 #ifdef AICLI_DISABLE_TEST_HOOKS
    123         // While under development, treat errors escaping this function as a test hook.
    124         try
    125         {
    126 #endif
    127             auto database = std::atomic_load(&m_database);
    128 
    129             if (database)
    130             {
    131                 auto transaction = BeginTransaction(operationName, false, database);
    132                 result = operation(database);
    133             }
    134             else if (requireDatabase)
    135             {
    136                 THROW_HR(E_NOT_VALID_STATE);
    137             }
    138 #ifdef AICLI_DISABLE_TEST_HOOKS
    139         }
    140         CATCH_LOG();
    141 #endif
    142 
    143         return result;
    144     }
    145 
    146     template <typename OperationT>
    147     void ConfigurationDatabase::ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase)
    148     {
    149 #ifdef AICLI_DISABLE_TEST_HOOKS
    150         // While under development, treat errors escaping this function as a test hook.
    151         try
    152         {
    153 #endif
    154             auto database = std::atomic_load(&m_database);
    155 
    156             if (!database)
    157             {
    158                 THROW_HR_IF(E_NOT_VALID_STATE, !silentlyIgnoreNoDatabase);
    159                 return;
    160             }
    161 
    162             auto transaction = BeginTransaction(operationName, true, database);
    163             operation(database);
    164             std::atomic_load(&m_connection)->SetLastWriteTime();
    165             transaction->Commit();
    166 #ifdef AICLI_DISABLE_TEST_HOOKS
    167         }
    168         CATCH_LOG();
    169 #endif
    170     }
    171 
    172     std::vector<ConfigurationDatabase::ConfigurationSetPtr> ConfigurationDatabase::GetSetHistory() const
    173     {
    174         return ExecuteReadOperation("GetSetHistory",
    175             [&](std::shared_ptr<IConfigurationDatabase>& database)
    176             {
    177                 return database->GetSets();
    178             });
    179     }
    180 
    181     ConfigurationDatabase::ConfigurationSetPtr ConfigurationDatabase::GetSet(const GUID& instanceIdentifier) const
    182     {
    183         return ExecuteReadOperation("GetSet",
    184             [&](std::shared_ptr<IConfigurationDatabase>& database)
    185             {
    186                 return database->GetSet(instanceIdentifier);
    187             });
    188     }
    189 
    190     void ConfigurationDatabase::WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory)
    191     {
    192         THROW_HR_IF_NULL(E_POINTER, configurationSet);
    193 
    194         ExecuteWriteOperation("WriteSetHistory",
    195             [&](std::shared_ptr<IConfigurationDatabase>& database)
    196             {
    197                 std::optional<rowid_t> setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier());
    198 
    199                 if (!setRowId && !preferNewHistory)
    200                 {
    201                     // TODO: Use conflict detection code to check for a matching set
    202                 }
    203 
    204                 if (setRowId)
    205                 {
    206                     database->UpdateSet(setRowId.value(), configurationSet);
    207                 }
    208                 else
    209                 {
    210                     database->AddSet(configurationSet);
    211                 }
    212             });
    213     }
    214 
    215     void ConfigurationDatabase::RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet)
    216     {
    217         THROW_HR_IF_NULL(E_POINTER, configurationSet);
    218 
    219         ExecuteWriteOperation("RemoveSetHistory",
    220             [&](std::shared_ptr<IConfigurationDatabase>& database)
    221             {
    222                 std::optional<rowid_t> setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier());
    223 
    224                 if (!setRowId)
    225                 {
    226                     // TODO: Use conflict detection code to check for a matching set
    227                 }
    228 
    229                 if (setRowId)
    230                 {
    231                     database->RemoveSet(setRowId.value());
    232                     std::atomic_load(&m_connection)->SetLastWriteTime();
    233                 }
    234             }, true);
    235     }
    236 
    237 
    238     void ConfigurationDatabase::AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName)
    239     {
    240         THROW_HR_IF_NULL(E_POINTER, configurationSet);
    241 
    242         ExecuteWriteOperation("AddQueueItem",
    243             [&](std::shared_ptr<IConfigurationDatabase>& database)
    244             {
    245                 database->AddQueueItem(configurationSet.InstanceIdentifier(), objectName);
    246             });
    247     }
    248 
    249     void ConfigurationDatabase::SetActiveQueueItem(const std::string& objectName)
    250     {
    251         ExecuteWriteOperation("SetActiveQueueItem",
    252             [&](std::shared_ptr<IConfigurationDatabase>& database)
    253             {
    254                 database->SetActiveQueueItem(objectName);
    255             });
    256     }
    257 
    258     std::vector<ConfigurationDatabase::QueueItem> ConfigurationDatabase::GetQueueItems() const
    259     {
    260         return ExecuteReadOperation("GetQueueItems",
    261             [&](std::shared_ptr<IConfigurationDatabase>& database)
    262             {
    263                 std::vector<QueueItem> result;
    264                 auto queueItems = database->GetQueueItems();
    265                 result.reserve(queueItems.size());
    266 
    267                 for (const auto& item : queueItems)
    268                 {
    269                     QueueItem resultItem;
    270                     std::tie(resultItem.SetInstanceIdentifier, resultItem.ObjectName, resultItem.QueuedAt, resultItem.ProcessId, resultItem.Active) = item;
    271                     result.emplace_back(std::move(resultItem));
    272                 }
    273 
    274                 return result;
    275             }, true);
    276     }
    277 
    278     void ConfigurationDatabase::RemoveQueueItem(const std::string& objectName)
    279     {
    280         ExecuteWriteOperation("RemoveQueueItem",
    281             [&](std::shared_ptr<IConfigurationDatabase>& database)
    282             {
    283                 database->RemoveQueueItem(objectName);
    284             });
    285     }
    286 
    287     std::vector<ConfigurationDatabase::StatusItem> ConfigurationDatabase::GetStatusSince(int64_t changeIdentifier) const
    288     {
    289         return ExecuteReadOperation("GetStatusSince",
    290             [&](std::shared_ptr<IConfigurationDatabase>& database)
    291             {
    292                 return ConvertStatusItems(database->GetStatusSince(changeIdentifier));
    293             });
    294     }
    295 
    296     ConfigurationDatabase::StatusBaseline ConfigurationDatabase::GetStatusBaseline() const
    297     {
    298         return ExecuteReadOperation("GetStatusBaseline",
    299             [&](std::shared_ptr<IConfigurationDatabase>& database)
    300             {
    301                 auto [changeIdentifier, setStatus] = database->GetStatusBaseline();
    302 
    303                 StatusBaseline result{};
    304                 result.ChangeIdentifier = changeIdentifier;
    305                 result.SetStatus = ConvertStatusItems(setStatus);
    306                 return result;
    307             });
    308     }
    309 
    310     void ConfigurationDatabase::AddListener(const std::string& objectName)
    311     {
    312         ExecuteWriteOperation("AddListener",
    313             [&](std::shared_ptr<IConfigurationDatabase>& database)
    314             {
    315                 database->AddListener(objectName);
    316             });
    317     }
    318 
    319     void ConfigurationDatabase::RemoveListener(const std::string& objectName)
    320     {
    321         ExecuteWriteOperation("RemoveListener",
    322             [&](std::shared_ptr<IConfigurationDatabase>& database)
    323             {
    324                 database->RemoveListener(objectName);
    325             });
    326     }
    327 
    328     std::vector<ConfigurationDatabase::StatusChangeListener> ConfigurationDatabase::GetChangeListeners() const
    329     {
    330         return ExecuteReadOperation("GetChangeListeners",
    331             [&](std::shared_ptr<IConfigurationDatabase>& database)
    332             {
    333                 std::vector<StatusChangeListener> result;
    334 
    335                 for (const auto& item : database->GetChangeListeners())
    336                 {
    337                     StatusChangeListener listener{};
    338                     std::tie(listener.ObjectName, listener.Started, listener.ProcessId) = item;
    339                     result.emplace_back(std::move(listener));
    340                 }
    341 
    342                 return result;
    343             });
    344     }
    345 
    346     void ConfigurationDatabase::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state)
    347     {
    348         ExecuteWriteOperation("UpdateSetState",
    349             [&](std::shared_ptr<IConfigurationDatabase>& database)
    350             {
    351                 database->UpdateSetState(setInstanceIdentifier, state);
    352             });
    353     }
    354 
    355     void ConfigurationDatabase::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue)
    356     {
    357         ExecuteWriteOperation("UpdateSetInQueue",
    358             [&](std::shared_ptr<IConfigurationDatabase>& database)
    359             {
    360                 database->UpdateSetInQueue(setInstanceIdentifier, inQueue);
    361             });
    362     }
    363 
    364     void ConfigurationDatabase::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr<implementation::ConfigurationSetChangeData>& changeData)
    365     {
    366         ExecuteWriteOperation("UpdateUnitState",
    367             [&](std::shared_ptr<IConfigurationDatabase>& database)
    368             {
    369                 database->UpdateUnitState(setInstanceIdentifier, changeData);
    370             });
    371     }
    372 
    373     ConfigurationSetState ConfigurationDatabase::GetSetState(const guid& instanceIdentifier)
    374     {
    375         return ExecuteReadOperation("GetSetState",
    376             [&](std::shared_ptr<IConfigurationDatabase>& database)
    377             {
    378                 return database->GetSetState(instanceIdentifier);
    379             });
    380     }
    381 
    382     std::chrono::system_clock::time_point ConfigurationDatabase::GetSetFirstApply(const guid& instanceIdentifier)
    383     {
    384         return ExecuteReadOperation("GetSetFirstApply",
    385             [&](std::shared_ptr<IConfigurationDatabase>& database)
    386             {
    387                 return database->GetSetFirstApply(instanceIdentifier);
    388             });
    389     }
    390 
    391     std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyBegun(const guid& instanceIdentifier)
    392     {
    393         return ExecuteReadOperation("GetSetApplyBegun",
    394             [&](std::shared_ptr<IConfigurationDatabase>& database)
    395             {
    396                 return database->GetSetApplyBegun(instanceIdentifier);
    397             });
    398     }
    399 
    400     std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyEnded(const guid& instanceIdentifier)
    401     {
    402         return ExecuteReadOperation("GetSetApplyEnded",
    403             [&](std::shared_ptr<IConfigurationDatabase>& database)
    404             {
    405                 return database->GetSetApplyEnded(instanceIdentifier);
    406             });
    407     }
    408 
    409     ConfigurationUnitState ConfigurationDatabase::GetUnitState(const guid& instanceIdentifier)
    410     {
    411         return ExecuteReadOperation("GetUnitState",
    412             [&](std::shared_ptr<IConfigurationDatabase>& database)
    413             {
    414                 return database->GetUnitState(instanceIdentifier);
    415             });
    416     }
    417 
    418     IConfigurationUnitResultInformation ConfigurationDatabase::GetUnitResultInformation(const guid& instanceIdentifier)
    419     {
    420         return ExecuteReadOperation("GetUnitResultInformation",
    421             [&](std::shared_ptr<IConfigurationDatabase>& database)
    422             {
    423                 com_ptr<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>> result;
    424 
    425                 auto resultInformation = database->GetUnitResultInformation(instanceIdentifier);
    426 
    427                 if (resultInformation)
    428                 {
    429                     result = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>();
    430                     result->Initialize(
    431                         std::get<0>(resultInformation.value()),
    432                         ConvertToUTF16(std::get<1>(resultInformation.value())),
    433                         ConvertToUTF16(std::get<2>(resultInformation.value())),
    434                         std::get<3>(resultInformation.value()));
    435                 }
    436 
    437                 IConfigurationUnitResultInformation actualResult;
    438                 if (result)
    439                 {
    440                     actualResult = *result;
    441                 }
    442 
    443                 return actualResult;
    444             });
    445     }
    446 
    447     ConfigurationDatabase::TransactionLock ConfigurationDatabase::BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr<IConfigurationDatabase>& database) const
    448     {
    449         auto connection = std::atomic_load(&m_connection);
    450         THROW_HR_IF_NULL(E_NOT_VALID_STATE, connection);
    451 
    452         TransactionLock result = connection->TryBeginTransaction(name, forWrite);
    453 
    454         while (!result)
    455         {
    456             {
    457                 auto connectionLock = connection->LockConnection();
    458                 auto newDatabase = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) };
    459                 if (std::atomic_compare_exchange_strong(&m_database, &database, newDatabase))
    460                 {
    461                     database = newDatabase;
    462                 }
    463             }
    464 
    465             result = connection->TryBeginTransaction(name, forWrite);
    466         }
    467 
    468         return result;
    469     }
    470 }