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 }