ActorsParent.cpp (347056B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ActorsParent.h" 8 9 // Local includes 10 #include "CanonicalQuotaObject.h" 11 #include "ClientUsageArray.h" 12 #include "DirectoryMetadata.h" 13 #include "FirstInitializationAttemptsImpl.h" 14 #include "Flatten.h" 15 #include "GroupInfo.h" 16 #include "GroupInfoPair.h" 17 #include "GroupInfoPairImpl.h" 18 #include "NormalOriginOperationBase.h" 19 #include "OpenClientDirectoryUtils.h" 20 #include "OriginInfo.h" 21 #include "OriginOperationBase.h" 22 #include "OriginOperations.h" 23 #include "OriginParser.h" 24 #include "OriginScope.h" 25 #include "QuotaCommon.h" 26 #include "QuotaManager.h" 27 #include "QuotaPrefs.h" 28 #include "ResolvableNormalOriginOp.h" 29 #include "SanitizationUtils.h" 30 #include "ScopedLogExtraInfo.h" 31 #include "UsageInfo.h" 32 33 // Global includes 34 #include <algorithm> 35 #include <cinttypes> 36 #include <cstdint> 37 #include <cstdlib> 38 #include <cstring> 39 #include <functional> 40 #include <new> 41 #include <numeric> 42 #include <tuple> 43 #include <type_traits> 44 #include <utility> 45 46 #include "DirectoryLockImpl.h" 47 #include "ErrorList.h" 48 #include "MainThreadUtils.h" 49 #include "mozIStorageAsyncConnection.h" 50 #include "mozIStorageConnection.h" 51 #include "mozIStorageService.h" 52 #include "mozIStorageStatement.h" 53 #include "mozStorageCID.h" 54 #include "mozStorageHelper.h" 55 #include "mozilla/AlreadyAddRefed.h" 56 #include "mozilla/AppShutdown.h" 57 #include "mozilla/Assertions.h" 58 #include "mozilla/Atomics.h" 59 #include "mozilla/Attributes.h" 60 #include "mozilla/AutoRestore.h" 61 #include "mozilla/BasePrincipal.h" 62 #include "mozilla/CheckedInt.h" 63 #include "mozilla/CondVar.h" 64 #include "mozilla/GeckoTrace.h" 65 #include "mozilla/InitializedOnce.h" 66 #include "mozilla/Logging.h" 67 #include "mozilla/Maybe.h" 68 #include "mozilla/Mutex.h" 69 #include "mozilla/NotNull.h" 70 #include "mozilla/Now.h" 71 #include "mozilla/OriginAttributes.h" 72 #include "mozilla/Preferences.h" 73 #include "mozilla/RefPtr.h" 74 #include "mozilla/Result.h" 75 #include "mozilla/ResultExtensions.h" 76 #include "mozilla/ScopeExit.h" 77 #include "mozilla/SpinEventLoopUntil.h" 78 #include "mozilla/StaticPrefs_dom.h" 79 #include "mozilla/StaticPtr.h" 80 #include "mozilla/StorageOriginAttributes.h" 81 #include "mozilla/SystemPrincipal.h" 82 #include "mozilla/TimeStamp.h" 83 #include "mozilla/dom/FileSystemQuotaClientFactory.h" 84 #include "mozilla/dom/FlippedOnce.h" 85 #include "mozilla/dom/IndexedDatabaseManager.h" 86 #include "mozilla/dom/LocalStorageCommon.h" 87 #include "mozilla/dom/StorageDBUpdater.h" 88 #include "mozilla/dom/cache/QuotaClient.h" 89 #include "mozilla/dom/indexedDB/ActorsParent.h" 90 #include "mozilla/dom/ipc/IdType.h" 91 #include "mozilla/dom/localstorage/ActorsParent.h" 92 #include "mozilla/dom/quota/ArtificialFailure.h" 93 #include "mozilla/dom/quota/AssertionsImpl.h" 94 #include "mozilla/dom/quota/CheckedUnsafePtr.h" 95 #include "mozilla/dom/quota/Client.h" 96 #include "mozilla/dom/quota/ClientDirectoryLock.h" 97 #include "mozilla/dom/quota/ClientDirectoryLockHandle.h" 98 #include "mozilla/dom/quota/Config.h" 99 #include "mozilla/dom/quota/Constants.h" 100 #include "mozilla/dom/quota/Date.h" 101 #include "mozilla/dom/quota/DirectoryLockInlines.h" 102 #include "mozilla/dom/quota/FileUtils.h" 103 #include "mozilla/dom/quota/MozPromiseUtils.h" 104 #include "mozilla/dom/quota/OriginDirectoryLock.h" 105 #include "mozilla/dom/quota/PersistenceType.h" 106 #include "mozilla/dom/quota/PrincipalUtils.h" 107 #include "mozilla/dom/quota/QuotaManagerImpl.h" 108 #include "mozilla/dom/quota/QuotaManagerService.h" 109 #include "mozilla/dom/quota/ResultExtensions.h" 110 #include "mozilla/dom/quota/ScopedLogExtraInfo.h" 111 #include "mozilla/dom/quota/StreamUtils.h" 112 #include "mozilla/dom/quota/ThreadUtils.h" 113 #include "mozilla/dom/quota/UniversalDirectoryLock.h" 114 #include "mozilla/dom/simpledb/ActorsParent.h" 115 #include "mozilla/fallible.h" 116 #include "mozilla/glean/DomQuotaMetrics.h" 117 #include "mozilla/ipc/BackgroundChild.h" 118 #include "mozilla/ipc/BackgroundParent.h" 119 #include "mozilla/ipc/PBackgroundChild.h" 120 #include "mozilla/ipc/ProtocolUtils.h" 121 #include "mozilla/net/ExtensionProtocolHandler.h" 122 #include "nsAppDirectoryServiceDefs.h" 123 #include "nsBaseHashtable.h" 124 #include "nsCOMPtr.h" 125 #include "nsCRTGlue.h" 126 #include "nsClassHashtable.h" 127 #include "nsComponentManagerUtils.h" 128 #include "nsContentUtils.h" 129 #include "nsDebug.h" 130 #include "nsDirectoryServiceUtils.h" 131 #include "nsError.h" 132 #include "nsIBinaryInputStream.h" 133 #include "nsIBinaryOutputStream.h" 134 #include "nsIConsoleService.h" 135 #include "nsIDUtils.h" 136 #include "nsIDirectoryEnumerator.h" 137 #include "nsIEventTarget.h" 138 #include "nsIFile.h" 139 #include "nsIFileStreams.h" 140 #include "nsIInputStream.h" 141 #include "nsIObjectInputStream.h" 142 #include "nsIObjectOutputStream.h" 143 #include "nsIObserver.h" 144 #include "nsIObserverService.h" 145 #include "nsIOutputStream.h" 146 #include "nsIPlatformInfo.h" 147 #include "nsIPrincipal.h" 148 #include "nsIQuotaManagerServiceInternal.h" 149 #include "nsIQuotaRequests.h" 150 #include "nsIQuotaUtilsService.h" 151 #include "nsIRunnable.h" 152 #include "nsISupports.h" 153 #include "nsIThread.h" 154 #include "nsITimer.h" 155 #include "nsIURI.h" 156 #include "nsIWidget.h" 157 #include "nsLiteralString.h" 158 #include "nsNetUtil.h" 159 #include "nsPrintfCString.h" 160 #include "nsServiceManagerUtils.h" 161 #include "nsStandardURL.h" 162 #include "nsString.h" 163 #include "nsStringFlags.h" 164 #include "nsStringFwd.h" 165 #include "nsTArray.h" 166 #include "nsTHashtable.h" 167 #include "nsTLiteralString.h" 168 #include "nsTPromiseFlatString.h" 169 #include "nsTStringRepr.h" 170 #include "nsThreadUtils.h" 171 #include "nsURLHelper.h" 172 #include "nsXPCOM.h" 173 #include "nsXPCOMCID.h" 174 #include "nsXULAppAPI.h" 175 #include "prinrval.h" 176 #include "prio.h" 177 #include "prtime.h" 178 179 // The amount of time, in milliseconds, that our IO thread will stay alive 180 // after the last event it processes. 181 #define DEFAULT_THREAD_TIMEOUT_MS 30000 182 183 /** 184 * If shutdown takes this long, kill actors of a quota client, to avoid reaching 185 * the crash timeout. 186 */ 187 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000 188 189 /** 190 * Automatically crash the browser if shutdown of a quota client takes this 191 * long. We've chosen a value that is long enough that it is unlikely for the 192 * problem to be falsely triggered by slow system I/O. We've also chosen a 193 * value long enough so that automated tests should time out and fail if 194 * shutdown of a quota client takes too long. Also, this value is long enough 195 * so that testers can notice the timeout; we want to know about the timeouts, 196 * not hide them. On the other hand this value is less than 60 seconds which is 197 * used by nsTerminator to crash a hung main process. 198 */ 199 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000 200 201 static_assert( 202 SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS, 203 "The kill actors timeout must be shorter than the crash browser one."); 204 205 // profile-before-change, when we need to shut down quota manager 206 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm" 207 208 #define KB *1024ULL 209 #define MB *1024ULL KB 210 #define GB *1024ULL MB 211 212 namespace mozilla::dom::quota { 213 214 using namespace mozilla::ipc; 215 216 // We want profiles to be platform-independent so we always need to replace 217 // the same characters on every platform. Windows has the most extensive set 218 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and 219 // FILE_PATH_SEPARATOR. 220 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; 221 const char16_t QuotaManager::kReplaceChars16[] = 222 u"" CONTROL_CHARACTERS "/:*?\"<>|\\"; 223 224 namespace { 225 226 /******************************************************************************* 227 * Constants 228 ******************************************************************************/ 229 230 const uint32_t kSQLitePageSizeOverride = 512; 231 232 // Important version history: 233 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57 234 // which caused Firefox 57 release concerns because the major schema upgrade 235 // means anyone downgrading to Firefox 56 will experience a non-operational 236 // QuotaManager and all of its clients. 237 // - Bug 1404344 got very concerned about that and so we decided to effectively 238 // rename 3.0 to 2.1, effective in Firefox 57. This works because post 239 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version 240 // increases. It also works because all the upgrade did was give the DOM 241 // Cache API QuotaClient an opportunity to create its newly added .padding 242 // files during initialization/upgrade, which isn't functionally necessary as 243 // that can be done on demand. 244 245 // Major storage version. Bump for backwards-incompatible changes. 246 // (The next major version should be 4 to distinguish from the Bug 1290481 247 // downgrade snafu.) 248 const uint32_t kMajorStorageVersion = 2; 249 250 // Minor storage version. Bump for backwards-compatible changes. 251 const uint32_t kMinorStorageVersion = 3; 252 253 // The storage version we store in the SQLite database is a (signed) 32-bit 254 // integer. The major version is left-shifted 16 bits so the max value is 255 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF. 256 static_assert(kMajorStorageVersion <= 0xFFFF, 257 "Major version needs to fit in 16 bits."); 258 static_assert(kMinorStorageVersion <= 0xFFFF, 259 "Minor version needs to fit in 16 bits."); 260 261 const int32_t kStorageVersion = 262 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion); 263 264 // See comments above about why these are a thing. 265 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0); 266 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1); 267 268 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home"; 269 const char kIndexedDBOriginPrefix[] = "indexeddb://"; 270 const char kResourceOriginPrefix[] = "resource://"; 271 272 constexpr auto kStorageName = u"storage"_ns; 273 274 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB" 275 #define ARCHIVES_DIRECTORY_NAME u"archives" 276 #define PERSISTENT_DIRECTORY_NAME u"persistent" 277 #define PERMANENT_DIRECTORY_NAME u"permanent" 278 #define TEMPORARY_DIRECTORY_NAME u"temporary" 279 #define DEFAULT_DIRECTORY_NAME u"default" 280 #define PRIVATE_DIRECTORY_NAME u"private" 281 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed" 282 283 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite" 284 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite" 285 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite" 286 287 const int32_t kLocalStorageArchiveVersion = 4; 288 289 const char kProfileDoChangeTopic[] = "profile-do-change"; 290 const char kContextualIdentityServiceLoadFinishedTopic[] = 291 "contextual-identity-service-load-finished"; 292 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited"; 293 294 const int32_t kCacheVersion = 3; 295 296 // Sentinel value written at the end of the metadata file to indicate that the 297 // file includes the extended origin metadata format. The sentinel allows us to 298 // safely distinguish upgraded files from older versions and maintain backward 299 // compatibility without requiring a full storage migration. Older files that 300 // do not contain the sentinel will be handled using a separate code path to 301 // fill the new fields with default or safe values. 302 const uint32_t kMetadataSentinel = 0xABCD1234; 303 304 /****************************************************************************** 305 * SQLite functions 306 ******************************************************************************/ 307 308 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion, 309 uint32_t aMinorStorageVersion) { 310 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion); 311 } 312 313 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) { 314 return uint32_t(aStorageVersion >> 16); 315 } 316 317 nsresult CreateTables(mozIStorageConnection* aConnection) { 318 AssertIsOnIOThread(); 319 MOZ_ASSERT(aConnection); 320 321 // Table `database` 322 QM_TRY(MOZ_TO_RESULT( 323 aConnection->ExecuteSimpleSQL("CREATE TABLE database" 324 "( cache_version INTEGER NOT NULL DEFAULT 0" 325 ");"_ns))); 326 327 #ifdef DEBUG 328 { 329 QM_TRY_INSPECT(const int32_t& storageVersion, 330 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion)); 331 332 MOZ_ASSERT(storageVersion == 0); 333 } 334 #endif 335 336 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion))); 337 338 return NS_OK; 339 } 340 341 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) { 342 AssertIsOnIOThread(); 343 344 QM_TRY_INSPECT(const auto& stmt, 345 CreateAndExecuteSingleStepStatement< 346 SingleStepResult::ReturnNullIfNoResult>( 347 aConnection, "SELECT cache_version FROM database"_ns)); 348 349 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED)); 350 351 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0)); 352 } 353 354 nsresult SaveCacheVersion(mozIStorageConnection& aConnection, 355 int32_t aVersion) { 356 AssertIsOnIOThread(); 357 358 QM_TRY_INSPECT( 359 const auto& stmt, 360 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 361 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 362 "UPDATE database SET cache_version = :version;"_ns)); 363 364 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion))); 365 366 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 367 368 return NS_OK; 369 } 370 371 nsresult CreateCacheTables(mozIStorageConnection& aConnection) { 372 AssertIsOnIOThread(); 373 374 // Table `cache` 375 QM_TRY(MOZ_TO_RESULT( 376 aConnection.ExecuteSimpleSQL("CREATE TABLE cache" 377 "( valid INTEGER NOT NULL DEFAULT 0" 378 ", build_id TEXT NOT NULL DEFAULT ''" 379 ");"_ns))); 380 381 // Table `repository` 382 QM_TRY( 383 MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository" 384 "( id INTEGER PRIMARY KEY" 385 ", name TEXT NOT NULL" 386 ");"_ns))); 387 388 // Table `origin` 389 QM_TRY(MOZ_TO_RESULT( 390 aConnection.ExecuteSimpleSQL("CREATE TABLE origin" 391 "( repository_id INTEGER NOT NULL" 392 ", suffix TEXT" 393 ", group_ TEXT NOT NULL" 394 ", origin TEXT NOT NULL" 395 ", client_usages TEXT NOT NULL" 396 ", usage INTEGER NOT NULL" 397 ", last_access_time INTEGER NOT NULL" 398 ", last_maintenance_date INTEGER NOT NULL" 399 ", accessed INTEGER NOT NULL" 400 ", persisted INTEGER NOT NULL" 401 ", PRIMARY KEY (repository_id, origin)" 402 ", FOREIGN KEY (repository_id) " 403 "REFERENCES repository(id) " 404 ");"_ns))); 405 406 #ifdef DEBUG 407 { 408 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection)); 409 MOZ_ASSERT(cacheVersion == 0); 410 } 411 #endif 412 413 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion))); 414 415 return NS_OK; 416 } 417 418 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) { 419 AssertIsOnIOThread(); 420 421 static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns; 422 static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns; 423 424 QM_TRY(QM_OR_ELSE_WARN( 425 // Expression. 426 ([&]() -> OkOrErr { 427 mozStorageTransaction transaction(&aConnection, 428 /*aCommitOnComplete */ false); 429 430 QM_TRY(QM_TO_RESULT(transaction.Start())); 431 QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery))); 432 QM_TRY( 433 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery))); 434 QM_TRY(QM_TO_RESULT(transaction.Commit())); 435 436 return Ok{}; 437 }()), 438 // Fallback. 439 ([&](const QMResult& rv) -> OkOrErr { 440 QM_TRY( 441 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery))); 442 443 return Ok{}; 444 }))); 445 446 return Ok{}; 447 } 448 449 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) { 450 AssertIsOnIOThread(); 451 452 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 453 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns))); 454 455 QM_TRY(InvalidateCache(aConnection)); 456 457 #ifdef DEBUG 458 { 459 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection)); 460 461 MOZ_ASSERT(cacheVersion == 1); 462 } 463 #endif 464 465 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2))); 466 467 return NS_OK; 468 } 469 470 nsresult UpgradeCacheFrom2To3(mozIStorageConnection& aConnection) { 471 AssertIsOnIOThread(); 472 473 QM_TRY(InvalidateCache(aConnection)); 474 475 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 476 "ALTER TABLE origin ADD COLUMN last_maintenance_date INTEGER NOT NULL"_ns))); 477 478 #ifdef DEBUG 479 { 480 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection)); 481 482 MOZ_ASSERT(cacheVersion == 2); 483 } 484 #endif 485 486 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 3))); 487 488 return NS_OK; 489 } 490 491 Result<bool, nsresult> MaybeCreateOrUpgradeCache( 492 mozIStorageConnection& aConnection) { 493 GECKO_TRACE_SCOPE("dom::quota", "MaybeCreateOrUpgradeCache"); 494 495 bool cacheUsable = true; 496 497 QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection)); 498 499 if (cacheVersion > kCacheVersion) { 500 cacheUsable = false; 501 } else if (cacheVersion != kCacheVersion) { 502 const bool newCache = !cacheVersion; 503 504 mozStorageTransaction transaction( 505 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 506 507 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 508 509 if (newCache) { 510 QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection))); 511 512 #ifdef DEBUG 513 { 514 QM_TRY_INSPECT(const int32_t& cacheVersion, 515 LoadCacheVersion(aConnection)); 516 MOZ_ASSERT(cacheVersion == kCacheVersion); 517 } 518 #endif 519 520 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 521 nsLiteralCString("INSERT INTO cache (valid, build_id) " 522 "VALUES (0, '')")))); 523 524 nsCOMPtr<mozIStorageStatement> insertStmt; 525 526 for (const PersistenceType persistenceType : kAllPersistenceTypes) { 527 if (insertStmt) { 528 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset()); 529 } else { 530 QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 531 nsCOMPtr<mozIStorageStatement>, 532 aConnection, CreateStatement, 533 "INSERT INTO repository (id, name) " 534 "VALUES (:id, :name)"_ns)); 535 } 536 537 QM_TRY(MOZ_TO_RESULT( 538 insertStmt->BindInt32ByName("id"_ns, persistenceType))); 539 540 QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName( 541 "name"_ns, PersistenceTypeToString(persistenceType)))); 542 543 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute())); 544 } 545 } else { 546 // This logic needs to change next time we change the cache! 547 static_assert(kCacheVersion == 3, 548 "Upgrade function needed due to cache version increase."); 549 550 while (cacheVersion != kCacheVersion) { 551 if (cacheVersion == 1) { 552 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection))); 553 } else if (cacheVersion == 2) { 554 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom2To3(aConnection))); 555 } else { 556 QM_FAIL(Err(NS_ERROR_FAILURE), []() { 557 QM_WARNING( 558 "Unable to initialize cache, no upgrade path is " 559 "available!"); 560 }); 561 } 562 563 QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection)); 564 } 565 566 MOZ_ASSERT(cacheVersion == kCacheVersion); 567 } 568 569 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 570 } 571 572 return cacheUsable; 573 } 574 575 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection( 576 nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) { 577 AssertIsOnIOThread(); 578 579 // Check if the old database exists at all. 580 QM_TRY_INSPECT(const bool& exists, 581 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists)); 582 583 if (!exists) { 584 // webappsstore.sqlite doesn't exist, return a null connection. 585 return nsCOMPtr<mozIStorageConnection>{}; 586 } 587 588 QM_TRY_INSPECT(const bool& isDirectory, 589 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory)); 590 591 if (isDirectory) { 592 QM_WARNING("webappsstore.sqlite is not a file!"); 593 return nsCOMPtr<mozIStorageConnection>{}; 594 } 595 596 QM_TRY_INSPECT(const auto& connection, 597 QM_OR_ELSE_WARN_IF( 598 // Expression. 599 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 600 nsCOMPtr<mozIStorageConnection>, aStorageService, 601 OpenUnsharedDatabase, &aWebAppsStoreFile, 602 mozIStorageService::CONNECTION_DEFAULT), 603 // Predicate. 604 IsDatabaseCorruptionError, 605 // Fallback. Don't throw an error, leave a corrupted 606 // webappsstore database as it is. 607 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>)); 608 609 if (connection) { 610 // Don't propagate an error, leave a non-updateable webappsstore database as 611 // it is. 612 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)), 613 nsCOMPtr<mozIStorageConnection>{}); 614 } 615 616 return connection; 617 } 618 619 Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile( 620 const nsAString& aDirectoryPath) { 621 AssertIsOnIOThread(); 622 MOZ_ASSERT(!aDirectoryPath.IsEmpty()); 623 624 QM_TRY_UNWRAP(auto lsArchiveFile, 625 QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath))); 626 627 QM_TRY(QM_TO_RESULT( 628 lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME)))); 629 630 return lsArchiveFile; 631 } 632 633 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile( 634 const nsAString& aDirectoryPath) { 635 AssertIsOnIOThread(); 636 MOZ_ASSERT(!aDirectoryPath.IsEmpty()); 637 638 QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath)); 639 640 QM_TRY(MOZ_TO_RESULT( 641 lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME)))); 642 643 return lsArchiveTmpFile; 644 } 645 646 Result<bool, nsresult> IsLocalStorageArchiveInitialized( 647 mozIStorageConnection& aConnection) { 648 AssertIsOnIOThread(); 649 650 QM_TRY_RETURN( 651 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns)); 652 } 653 654 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) { 655 AssertIsOnIOThread(); 656 MOZ_ASSERT(aConnection); 657 658 #ifdef DEBUG 659 { 660 QM_TRY_INSPECT(const auto& initialized, 661 IsLocalStorageArchiveInitialized(*aConnection)); 662 MOZ_ASSERT(!initialized); 663 } 664 #endif 665 666 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 667 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns))); 668 669 QM_TRY_INSPECT( 670 const auto& stmt, 671 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 672 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement, 673 "INSERT INTO database (version) VALUES (:version)"_ns)); 674 675 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0))); 676 QM_TRY(MOZ_TO_RESULT(stmt->Execute())); 677 678 return NS_OK; 679 } 680 681 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion( 682 mozIStorageConnection& aConnection) { 683 AssertIsOnIOThread(); 684 685 QM_TRY_INSPECT(const auto& stmt, 686 CreateAndExecuteSingleStepStatement< 687 SingleStepResult::ReturnNullIfNoResult>( 688 aConnection, "SELECT version FROM database"_ns)); 689 690 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED)); 691 692 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0)); 693 } 694 695 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection, 696 int32_t aVersion) { 697 AssertIsOnIOThread(); 698 MOZ_ASSERT(aConnection); 699 700 nsCOMPtr<mozIStorageStatement> stmt; 701 nsresult rv = aConnection->CreateStatement( 702 "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt)); 703 if (NS_WARN_IF(NS_FAILED(rv))) { 704 return rv; 705 } 706 707 rv = stmt->BindInt32ByName("version"_ns, aVersion); 708 if (NS_WARN_IF(NS_FAILED(rv))) { 709 return rv; 710 } 711 712 rv = stmt->Execute(); 713 if (NS_WARN_IF(NS_FAILED(rv))) { 714 return rv; 715 } 716 717 return NS_OK; 718 } 719 720 template <typename FileFunc, typename DirectoryFunc> 721 Result<mozilla::Ok, nsresult> CollectEachFileEntry( 722 nsIFile& aDirectory, const FileFunc& aFileFunc, 723 const DirectoryFunc& aDirectoryFunc) { 724 AssertIsOnIOThread(); 725 726 return CollectEachFile( 727 aDirectory, 728 [&aFileFunc, &aDirectoryFunc]( 729 const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> { 730 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file)); 731 732 switch (dirEntryKind) { 733 case nsIFileKind::ExistsAsDirectory: 734 return aDirectoryFunc(file); 735 736 case nsIFileKind::ExistsAsFile: 737 return aFileFunc(file); 738 739 case nsIFileKind::DoesNotExist: 740 // Ignore files that got removed externally while iterating. 741 break; 742 } 743 744 return Ok{}; 745 }); 746 } 747 748 /****************************************************************************** 749 * Quota manager class declarations 750 ******************************************************************************/ 751 752 } // namespace 753 754 class QuotaManager::Observer final : public nsIObserver { 755 static Observer* sInstance; 756 757 bool mPendingProfileChange; 758 bool mShutdownComplete; 759 760 public: 761 static nsresult Initialize(); 762 763 static nsIObserver* GetInstance(); 764 765 static void ShutdownCompleted(); 766 767 private: 768 Observer() : mPendingProfileChange(false), mShutdownComplete(false) { 769 MOZ_ASSERT(NS_IsMainThread()); 770 } 771 772 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); } 773 774 nsresult Init(); 775 776 nsresult Shutdown(); 777 778 NS_DECL_ISUPPORTS 779 NS_DECL_NSIOBSERVER 780 }; 781 782 namespace { 783 784 /******************************************************************************* 785 * Local class declarations 786 ******************************************************************************/ 787 788 } // namespace 789 790 namespace { 791 792 class CollectOriginsHelper final : public Runnable { 793 uint64_t mMinSizeToBeFreed; 794 795 Mutex& mMutex; 796 CondVar mCondVar; 797 798 // The members below are protected by mMutex. 799 nsTArray<RefPtr<OriginDirectoryLock>> mLocks; 800 uint64_t mSizeToBeFreed; 801 bool mWaiting; 802 803 public: 804 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed); 805 806 // Blocks the current thread until origins are collected on the main thread. 807 // The returned value contains an aggregate size of those origins. 808 int64_t BlockAndReturnOriginsForEviction( 809 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks); 810 811 private: 812 ~CollectOriginsHelper() = default; 813 814 NS_IMETHOD 815 Run() override; 816 }; 817 818 /******************************************************************************* 819 * Other class declarations 820 ******************************************************************************/ 821 822 class RecordTimeDeltaHelper final : public Runnable { 823 const mozilla::glean::impl::Labeled< 824 mozilla::glean::impl::TimingDistributionMetric, DynamicLabel>& mMetric; 825 826 // TimeStamps that are set on the IO thread. 827 LazyInitializedOnceNotNull<const TimeStamp> mStartTime; 828 LazyInitializedOnceNotNull<const TimeStamp> mEndTime; 829 830 // A TimeStamp that is set on the main thread. 831 LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime; 832 833 public: 834 explicit RecordTimeDeltaHelper(const mozilla::glean::impl::Labeled< 835 mozilla::glean::impl::TimingDistributionMetric, 836 DynamicLabel>& aMetric) 837 : Runnable("dom::quota::RecordTimeDeltaHelper"), mMetric(aMetric) {} 838 839 TimeStamp Start(); 840 841 TimeStamp End(); 842 843 private: 844 ~RecordTimeDeltaHelper() = default; 845 846 NS_DECL_NSIRUNNABLE 847 }; 848 849 /******************************************************************************* 850 * Helper classes 851 ******************************************************************************/ 852 853 /******************************************************************************* 854 * Helper Functions 855 ******************************************************************************/ 856 857 // Return whether the group was actually updated. 858 Result<bool, nsresult> MaybeUpdateGroupForOrigin( 859 OriginMetadata& aOriginMetadata) { 860 MOZ_ASSERT(!NS_IsMainThread()); 861 862 bool updated = false; 863 864 if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) { 865 if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) { 866 aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin); 867 updated = true; 868 } 869 } else { 870 nsCOMPtr<nsIPrincipal> principal = 871 BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin); 872 QM_TRY(MOZ_TO_RESULT(principal)); 873 874 QM_TRY_INSPECT(const auto& baseDomain, 875 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal, 876 GetBaseDomain)); 877 878 const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix; 879 880 if (aOriginMetadata.mGroup != upToDateGroup) { 881 aOriginMetadata.mGroup = upToDateGroup; 882 updated = true; 883 } 884 } 885 886 return updated; 887 } 888 889 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin( 890 FullOriginMetadata& aFullOriginMetadata) { 891 GECKO_TRACE_SCOPE("dom::quota", "MaybeUpdateLastAccessTimeForOrigin"); 892 893 MOZ_ASSERT(!NS_IsMainThread()); 894 895 if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) { 896 QuotaManager* quotaManager = QuotaManager::Get(); 897 MOZ_ASSERT(quotaManager); 898 899 QM_TRY_INSPECT(const auto& metadataFile, 900 quotaManager->GetOriginDirectory(aFullOriginMetadata)); 901 902 QM_TRY(MOZ_TO_RESULT( 903 metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME)))); 904 905 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER( 906 metadataFile, GetLastModifiedTime)); 907 908 // Need to convert from milliseconds to microseconds. 909 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp); 910 timestamp *= int64_t(PR_USEC_PER_MSEC); 911 912 aFullOriginMetadata.mLastAccessTime = timestamp; 913 914 return true; 915 } 916 917 return false; 918 } 919 920 } // namespace 921 922 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) { 923 // Get leaf of file path 924 for (const char* p = aFile; *p; ++p) { 925 if (*p == '/' && *(p + 1)) { 926 aFile = p + 1; 927 } 928 } 929 930 nsContentUtils::LogSimpleConsoleError( 931 NS_ConvertUTF8toUTF16( 932 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)), 933 "quota"_ns, 934 false /* Quota Manager is not active in private browsing mode */, 935 true /* Quota Manager runs always in a chrome context */); 936 } 937 938 namespace { 939 940 bool gInvalidateQuotaCache = false; 941 StaticAutoPtr<nsString> gBasePath; 942 StaticAutoPtr<nsString> gStorageName; 943 StaticAutoPtr<nsCString> gBuildId; 944 945 #ifdef DEBUG 946 bool gQuotaManagerInitialized = false; 947 #endif 948 949 StaticRefPtr<QuotaManager> gInstance; 950 mozilla::Atomic<bool> gShutdown(false); 951 952 // A time stamp that can only be accessed on the main thread. 953 TimeStamp gLastOSWake; 954 955 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a 956 // separate and includable file. 957 using NormalOriginOpArray = 958 nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>; 959 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps; 960 961 class StorageOperationBase { 962 protected: 963 struct OriginProps { 964 enum Type { eChrome, eContent, eObsolete, eInvalid }; 965 966 NotNull<nsCOMPtr<nsIFile>> mDirectory; 967 nsString mLeafName; 968 nsCString mSpec; 969 OriginAttributes mAttrs; 970 int64_t mTimestamp; 971 OriginMetadata mOriginMetadata; 972 nsCString mOriginalSuffix; 973 974 LazyInitializedOnceEarlyDestructible<const PersistenceType> 975 mPersistenceType; 976 Type mType; 977 bool mNeedsRestore; 978 bool mNeedsRestore2; 979 bool mIgnore; 980 981 public: 982 explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory) 983 : mDirectory(std::move(aDirectory)), 984 mTimestamp(0), 985 mType(eContent), 986 mNeedsRestore(false), 987 mNeedsRestore2(false), 988 mIgnore(false) {} 989 990 template <typename PersistenceTypeFunc> 991 nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc); 992 }; 993 994 nsTArray<OriginProps> mOriginProps; 995 996 nsCOMPtr<nsIFile> mDirectory; 997 998 public: 999 explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) { 1000 AssertIsOnIOThread(); 1001 } 1002 1003 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase) 1004 1005 protected: 1006 virtual ~StorageOperationBase() = default; 1007 1008 static nsresult CreateDirectoryMetadata( 1009 nsIFile& aDirectory, int64_t aTimestamp, 1010 const OriginMetadata& aOriginMetadata); 1011 1012 static nsresult CreateDirectoryMetadata2( 1013 nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted, 1014 const OriginMetadata& aOriginMetadata); 1015 1016 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp, 1017 nsACString& aGroup, nsACString& aOrigin, 1018 Nullable<bool>& aIsApp); 1019 1020 // Upgrade helper to load the contents of ".metadata-v2" files from previous 1021 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2 1022 // method, it is only intended to read current version ".metadata-v2" files. 1023 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve 1024 // because our "storage.sqlite" lets us track the overall version of the 1025 // storage directory. 1026 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp, 1027 nsACString& aSuffix, nsACString& aGroup, 1028 nsACString& aOrigin, bool& aIsApp); 1029 1030 int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps); 1031 1032 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps); 1033 1034 /** 1035 * Rename the origin if the origin string generation from nsIPrincipal 1036 * changed. This consists of renaming the origin in the metadata files and 1037 * renaming the origin directory itself. For simplicity, the origin in 1038 * metadata files is not actually updated, but the metadata files are 1039 * recreated instead. 1040 * 1041 * @param aOriginProps the properties of the origin to check. 1042 * 1043 * @return whether origin was renamed. 1044 */ 1045 Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps); 1046 1047 nsresult ProcessOriginDirectories(); 1048 1049 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0; 1050 }; 1051 1052 class RepositoryOperationBase : public StorageOperationBase { 1053 public: 1054 explicit RepositoryOperationBase(nsIFile* aDirectory) 1055 : StorageOperationBase(aDirectory) {} 1056 1057 nsresult ProcessRepository(); 1058 1059 protected: 1060 virtual ~RepositoryOperationBase() = default; 1061 1062 template <typename UpgradeMethod> 1063 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps, 1064 UpgradeMethod aMethod); 1065 1066 private: 1067 virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0; 1068 1069 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1070 bool* aRemoved) = 0; 1071 1072 virtual nsresult PrepareClientDirectory(nsIFile* aFile, 1073 const nsAString& aLeafName, 1074 bool& aRemoved); 1075 }; 1076 1077 class CreateOrUpgradeDirectoryMetadataHelper final 1078 : public RepositoryOperationBase { 1079 nsCOMPtr<nsIFile> mPermanentStorageDir; 1080 1081 // The legacy PersistenceType, before the default repository introduction. 1082 enum class LegacyPersistenceType { 1083 Persistent = 0, 1084 Temporary 1085 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need 1086 // it here. 1087 }; 1088 1089 LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType; 1090 1091 public: 1092 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory) 1093 : RepositoryOperationBase(aDirectory) {} 1094 1095 nsresult Init(); 1096 1097 private: 1098 Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile, 1099 const fallible_t&); 1100 1101 PersistenceType PersistenceTypeFromLegacyPersistentSpec( 1102 const nsCString& aSpec); 1103 1104 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override; 1105 1106 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory); 1107 1108 nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1109 bool* aRemoved) override; 1110 1111 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1112 }; 1113 1114 class UpgradeStorageHelperBase : public RepositoryOperationBase { 1115 LazyInitializedOnce<const PersistenceType> mPersistenceType; 1116 1117 public: 1118 explicit UpgradeStorageHelperBase(nsIFile* aDirectory) 1119 : RepositoryOperationBase(aDirectory) {} 1120 1121 nsresult Init(); 1122 1123 private: 1124 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override; 1125 }; 1126 1127 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase { 1128 public: 1129 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory) 1130 : UpgradeStorageHelperBase(aDirectory) {} 1131 1132 private: 1133 nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1134 bool* aRemoved) override; 1135 1136 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1137 }; 1138 1139 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase { 1140 public: 1141 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory) 1142 : UpgradeStorageHelperBase(aDirectory) {} 1143 1144 private: 1145 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps); 1146 1147 /** 1148 * Remove the origin directory if appId is present in origin attributes. 1149 * 1150 * @param aOriginProps the properties of the origin to check. 1151 * 1152 * @return whether the origin directory was removed. 1153 */ 1154 Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps); 1155 1156 nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1157 bool* aRemoved) override; 1158 1159 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1160 }; 1161 1162 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase { 1163 public: 1164 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory) 1165 : UpgradeStorageHelperBase(aDirectory) {} 1166 1167 private: 1168 nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1169 bool* aRemoved) override; 1170 1171 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1172 }; 1173 1174 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase { 1175 public: 1176 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory) 1177 : UpgradeStorageHelperBase(aDirectory) {} 1178 1179 private: 1180 nsresult PrepareOriginDirectory(OriginProps& aOriginProps, 1181 bool* aRemoved) override; 1182 1183 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1184 1185 nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName, 1186 bool& aRemoved) override; 1187 }; 1188 1189 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase { 1190 LazyInitializedOnce<const PersistenceType> mPersistenceType; 1191 1192 public: 1193 explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory) 1194 : StorageOperationBase(aDirectory) {} 1195 1196 nsresult Init(); 1197 1198 nsresult RestoreMetadata2File(); 1199 1200 private: 1201 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; 1202 }; 1203 1204 Result<nsAutoString, nsresult> GetPathForStorage( 1205 nsIFile& aBaseDir, const nsAString& aStorageName) { 1206 QM_TRY_INSPECT(const auto& storageDir, 1207 CloneFileAndAppend(aBaseDir, aStorageName)); 1208 1209 QM_TRY_RETURN( 1210 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath)); 1211 } 1212 1213 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) { 1214 AssertIsOnIOThread(); 1215 1216 class MOZ_STACK_CLASS Helper final { 1217 public: 1218 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) { 1219 AssertIsOnIOThread(); 1220 MOZ_ASSERT(aFile); 1221 MOZ_ASSERT(aTimestamp); 1222 1223 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile)); 1224 1225 switch (dirEntryKind) { 1226 case nsIFileKind::ExistsAsDirectory: 1227 QM_TRY(CollectEachFile( 1228 *aFile, 1229 [&aTimestamp](const nsCOMPtr<nsIFile>& file) 1230 -> Result<mozilla::Ok, nsresult> { 1231 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp))); 1232 1233 return Ok{}; 1234 })); 1235 break; 1236 1237 case nsIFileKind::ExistsAsFile: { 1238 QM_TRY_INSPECT(const auto& leafName, 1239 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile, 1240 GetLeafName)); 1241 1242 // Bug 1595445 will handle unknown files here. 1243 1244 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) || 1245 IsDotFile(leafName)) { 1246 return NS_OK; 1247 } 1248 1249 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER( 1250 aFile, GetLastModifiedTime)); 1251 1252 // Need to convert from milliseconds to microseconds. 1253 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp); 1254 timestamp *= int64_t(PR_USEC_PER_MSEC); 1255 1256 if (timestamp > *aTimestamp) { 1257 *aTimestamp = timestamp; 1258 } 1259 break; 1260 } 1261 1262 case nsIFileKind::DoesNotExist: 1263 // Ignore files that got removed externally while iterating. 1264 break; 1265 } 1266 1267 return NS_OK; 1268 } 1269 }; 1270 1271 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 1272 return PR_Now(); 1273 } 1274 1275 int64_t timestamp = INT64_MIN; 1276 nsresult rv = Helper::GetLastModifiedTime(&aFile, ×tamp); 1277 if (NS_FAILED(rv)) { 1278 timestamp = PR_Now(); 1279 } 1280 1281 // XXX if there were no suitable files for getting last modified time 1282 // (timestamp is still set to INT64_MIN), we should return the current time 1283 // instead of returning INT64_MIN. 1284 1285 return timestamp; 1286 } 1287 1288 // Returns a bool indicating whether the directory was newly created. 1289 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) { 1290 AssertIsOnIOThread(); 1291 1292 // Callers call this function without checking if the directory already 1293 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we 1294 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the 1295 // reports. 1296 QM_TRY_INSPECT(const auto& exists, 1297 QM_OR_ELSE_LOG_VERBOSE_IF( 1298 // Expression. 1299 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create, 1300 nsIFile::DIRECTORY_TYPE, 0755, 1301 /* aSkipAncestors = */ false) 1302 .map([](Ok) { return false; }), 1303 // Predicate. 1304 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 1305 // Fallback. 1306 ErrToOk<true>)); 1307 1308 if (exists) { 1309 QM_TRY_INSPECT(const bool& isDirectory, 1310 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory)); 1311 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED)); 1312 } 1313 1314 return !exists; 1315 } 1316 1317 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) { 1318 aJarPrefix.Truncate(); 1319 1320 // Fallback. 1321 if (!aInIsolatedMozBrowser) { 1322 return; 1323 } 1324 1325 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug 1326 // 1320404). 1327 // aJarPrefix = appId + "+" + { 't', 'f' } + "+"; 1328 aJarPrefix.AppendInt(0); // TODO: this is the appId, to be removed. 1329 aJarPrefix.Append('+'); 1330 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f'); 1331 aJarPrefix.Append('+'); 1332 } 1333 1334 // This method computes and returns our best guess for the temporary storage 1335 // limit (in bytes), based on disk capacity. 1336 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) { 1337 if (nsContentUtils::ShouldResistFingerprinting( 1338 "The storage limit is set only once and not webpage specific.", 1339 RFPTarget::DiskStorageLimit)) { 1340 return nsRFPService::GetSpoofedStorageLimit(); 1341 } 1342 1343 // The fixed limit pref can be used to override temporary storage limit 1344 // calculation. 1345 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) { 1346 return static_cast<uint64_t>( 1347 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) * 1348 1024; 1349 } 1350 1351 constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL); 1352 constexpr int64_t maxAllowedCapacity = 8LL * teraByte; 1353 1354 // Check for disk capacity of user's device on which storage directory lives. 1355 int64_t diskCapacity = maxAllowedCapacity; 1356 1357 // Log error when default disk capacity is returned due to the error 1358 QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity))); 1359 1360 MOZ_ASSERT(diskCapacity >= 0LL); 1361 1362 // Allow temporary storage to consume up to 50% of disk capacity. 1363 int64_t capacityLimit = diskCapacity / 2LL; 1364 1365 // If the disk capacity reported by the operating system is very 1366 // large and potentially incorrect due to hardware issues, 1367 // a hardcoded limit is supplied instead. 1368 QM_WARNONLY_TRY( 1369 OkIf(capacityLimit < maxAllowedCapacity), 1370 ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; })); 1371 1372 return capacityLimit; 1373 } 1374 1375 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata, 1376 const int64_t aRecentTime) { 1377 if (aFullOriginMetadata.mLastAccessTime > aRecentTime) { 1378 return false; 1379 } 1380 1381 return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC > 1382 StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec(); 1383 } 1384 1385 } // namespace 1386 1387 /******************************************************************************* 1388 * Exported functions 1389 ******************************************************************************/ 1390 1391 void InitializeQuotaManager() { 1392 MOZ_ASSERT(XRE_IsParentProcess()); 1393 MOZ_ASSERT(NS_IsMainThread()); 1394 MOZ_ASSERT(!gQuotaManagerInitialized); 1395 1396 if (!QuotaManager::IsRunningGTests()) { 1397 // These services have to be started on the main thread currently. 1398 const nsCOMPtr<mozIStorageService> ss = 1399 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); 1400 QM_WARNONLY_TRY(OkIf(ss)); 1401 1402 RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler = 1403 net::ExtensionProtocolHandler::GetSingleton(); 1404 QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler)); 1405 1406 IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate(); 1407 QM_WARNONLY_TRY(MOZ_TO_RESULT(mgr)); 1408 } 1409 1410 QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize())); 1411 1412 #ifdef DEBUG 1413 gQuotaManagerInitialized = true; 1414 #endif 1415 } 1416 1417 void InitializeScopedLogExtraInfo() { 1418 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED 1419 ScopedLogExtraInfo::Initialize(); 1420 #endif 1421 } 1422 1423 bool RecvShutdownQuotaManager() { 1424 AssertIsOnBackgroundThread(); 1425 1426 // If we are already in shutdown, don't call ShutdownInstance() 1427 // again and return true immediately. We shall see this incident 1428 // in Telemetry. 1429 // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124) 1430 // XXX todo: Active QM_TRY context for shutdown (Bug 1735170) 1431 QM_TRY(OkIf(!gShutdown), true); 1432 1433 QuotaManager::ShutdownInstance(); 1434 1435 return true; 1436 } 1437 1438 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr; 1439 1440 // static 1441 nsresult QuotaManager::Observer::Initialize() { 1442 MOZ_ASSERT(NS_IsMainThread()); 1443 1444 RefPtr<Observer> observer = new Observer(); 1445 1446 nsresult rv = observer->Init(); 1447 if (NS_WARN_IF(NS_FAILED(rv))) { 1448 return rv; 1449 } 1450 1451 sInstance = observer; 1452 1453 return NS_OK; 1454 } 1455 1456 // static 1457 nsIObserver* QuotaManager::Observer::GetInstance() { 1458 MOZ_ASSERT(NS_IsMainThread()); 1459 1460 return sInstance; 1461 } 1462 1463 // static 1464 void QuotaManager::Observer::ShutdownCompleted() { 1465 MOZ_ASSERT(NS_IsMainThread()); 1466 MOZ_ASSERT(sInstance); 1467 1468 sInstance->mShutdownComplete = true; 1469 } 1470 1471 nsresult QuotaManager::Observer::Init() { 1472 MOZ_ASSERT(NS_IsMainThread()); 1473 1474 /** 1475 * A RAII utility class to manage the registration and automatic 1476 * unregistration of observers with `nsIObserverService`. This class is 1477 * designed to simplify observer management, particularly when registering 1478 * for multiple topics, by ensuring that already registered topics are 1479 * unregistered if a failure occurs during subsequent registrations. 1480 */ 1481 class MOZ_RAII Registrar { 1482 public: 1483 Registrar(nsIObserverService* aObserverService, nsIObserver* aObserver, 1484 const char* aTopic) 1485 : mObserverService(std::move(aObserverService)), 1486 mObserver(aObserver), 1487 mTopic(aTopic), 1488 mUnregisterOnDestruction(false) { 1489 MOZ_ASSERT(aObserverService); 1490 MOZ_ASSERT(aObserver); 1491 MOZ_ASSERT(aTopic); 1492 } 1493 1494 ~Registrar() { 1495 if (mUnregisterOnDestruction) { 1496 mObserverService->RemoveObserver(mObserver, mTopic); 1497 } 1498 } 1499 1500 nsresult Register() { 1501 MOZ_ASSERT(!mUnregisterOnDestruction); 1502 1503 nsresult rv = mObserverService->AddObserver(mObserver, mTopic, false); 1504 if (NS_WARN_IF(NS_FAILED(rv))) { 1505 return rv; 1506 } 1507 1508 mUnregisterOnDestruction = true; 1509 1510 return NS_OK; 1511 } 1512 1513 void Commit() { mUnregisterOnDestruction = false; } 1514 1515 private: 1516 nsIObserverService* mObserverService; 1517 nsIObserver* mObserver; 1518 const char* mTopic; 1519 bool mUnregisterOnDestruction; 1520 }; 1521 1522 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1523 if (NS_WARN_IF(!obs)) { 1524 return NS_ERROR_FAILURE; 1525 } 1526 1527 Registrar xpcomShutdownRegistrar(obs, this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 1528 QM_TRY(MOZ_TO_RESULT(xpcomShutdownRegistrar.Register())); 1529 1530 Registrar profileDoChangeRegistrar(obs, this, kProfileDoChangeTopic); 1531 QM_TRY(MOZ_TO_RESULT(profileDoChangeRegistrar.Register())); 1532 1533 Registrar contextualIdentityServiceLoadFinishedRegistrar( 1534 obs, this, kContextualIdentityServiceLoadFinishedTopic); 1535 QM_TRY( 1536 MOZ_TO_RESULT(contextualIdentityServiceLoadFinishedRegistrar.Register())); 1537 1538 Registrar profileBeforeChangeQmRegistrar( 1539 obs, this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID); 1540 QM_TRY(MOZ_TO_RESULT(profileBeforeChangeQmRegistrar.Register())); 1541 1542 Registrar wakeNotificationRegistrar(obs, this, NS_WIDGET_WAKE_OBSERVER_TOPIC); 1543 QM_TRY(MOZ_TO_RESULT(wakeNotificationRegistrar.Register())); 1544 1545 Registrar lastPbContextExitedRegistrar(obs, this, 1546 kPrivateBrowsingObserverTopic); 1547 QM_TRY(MOZ_TO_RESULT(lastPbContextExitedRegistrar.Register())); 1548 1549 xpcomShutdownRegistrar.Commit(); 1550 profileDoChangeRegistrar.Commit(); 1551 contextualIdentityServiceLoadFinishedRegistrar.Commit(); 1552 profileBeforeChangeQmRegistrar.Commit(); 1553 wakeNotificationRegistrar.Commit(); 1554 lastPbContextExitedRegistrar.Commit(); 1555 1556 return NS_OK; 1557 } 1558 1559 nsresult QuotaManager::Observer::Shutdown() { 1560 MOZ_ASSERT(NS_IsMainThread()); 1561 1562 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1563 if (NS_WARN_IF(!obs)) { 1564 return NS_ERROR_FAILURE; 1565 } 1566 1567 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic)); 1568 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC)); 1569 MOZ_ALWAYS_SUCCEEDS( 1570 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)); 1571 MOZ_ALWAYS_SUCCEEDS( 1572 obs->RemoveObserver(this, kContextualIdentityServiceLoadFinishedTopic)); 1573 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic)); 1574 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)); 1575 1576 sInstance = nullptr; 1577 1578 // In general, the instance will have died after the latter removal call, so 1579 // it's not safe to do anything after that point. 1580 // However, Shutdown is currently called from Observe which is called by the 1581 // Observer Service which holds a strong reference to the observer while the 1582 // Observe method is being called. 1583 1584 return NS_OK; 1585 } 1586 1587 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver) 1588 1589 NS_IMETHODIMP 1590 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, 1591 const char16_t* aData) { 1592 MOZ_ASSERT(NS_IsMainThread()); 1593 1594 nsresult rv; 1595 1596 if (!strcmp(aTopic, kProfileDoChangeTopic)) { 1597 if (NS_WARN_IF(gBasePath)) { 1598 NS_WARNING( 1599 "profile-before-change-qm must precede repeated " 1600 "profile-do-change!"); 1601 return NS_OK; 1602 } 1603 1604 gBasePath = new nsString(); 1605 1606 nsCOMPtr<nsIFile> baseDir; 1607 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, 1608 getter_AddRefs(baseDir)); 1609 if (NS_FAILED(rv)) { 1610 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1611 getter_AddRefs(baseDir)); 1612 } 1613 if (NS_WARN_IF(NS_FAILED(rv))) { 1614 return rv; 1615 } 1616 1617 rv = baseDir->GetPath(*gBasePath); 1618 if (NS_WARN_IF(NS_FAILED(rv))) { 1619 return rv; 1620 } 1621 1622 #ifdef XP_WIN 1623 // Annotate if our profile lives on a network resource. 1624 bool isNetworkPath = PathIsNetworkPathW(gBasePath->get()); 1625 CrashReporter::RecordAnnotationBool( 1626 CrashReporter::Annotation::QuotaManagerStorageIsNetworkResource, 1627 isNetworkPath); 1628 #endif 1629 1630 QM_LOG(("Base path: %s", NS_ConvertUTF16toUTF8(*gBasePath).get())); 1631 1632 gStorageName = new nsString(); 1633 1634 rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName); 1635 if (NS_FAILED(rv)) { 1636 *gStorageName = kStorageName; 1637 } 1638 1639 gBuildId = new nsCString(); 1640 1641 nsCOMPtr<nsIPlatformInfo> platformInfo = 1642 do_GetService("@mozilla.org/xre/app-info;1"); 1643 if (NS_WARN_IF(!platformInfo)) { 1644 return NS_ERROR_FAILURE; 1645 } 1646 1647 rv = platformInfo->GetPlatformBuildID(*gBuildId); 1648 if (NS_WARN_IF(NS_FAILED(rv))) { 1649 return rv; 1650 } 1651 1652 return NS_OK; 1653 } 1654 1655 if (!strcmp(aTopic, kContextualIdentityServiceLoadFinishedTopic)) { 1656 if (NS_WARN_IF(!gBasePath)) { 1657 NS_WARNING( 1658 "profile-do-change must precede " 1659 "contextual-identity-service-load-finished!"); 1660 return NS_OK; 1661 } 1662 1663 nsCOMPtr<nsIQuotaManagerServiceInternal> quotaManagerService = 1664 QuotaManagerService::GetOrCreate(); 1665 if (NS_WARN_IF(!quotaManagerService)) { 1666 return NS_ERROR_FAILURE; 1667 } 1668 1669 nsCOMPtr<nsIQuotaUtilsService> quotaUtilsService = 1670 do_GetService("@mozilla.org/dom/quota-utils-service;1"); 1671 if (NS_WARN_IF(!quotaUtilsService)) { 1672 return NS_ERROR_FAILURE; 1673 } 1674 1675 uint32_t thumbnailPrivateIdentityId; 1676 nsresult rv = quotaUtilsService->GetPrivateIdentityId( 1677 u"userContextIdInternal.thumbnail"_ns, &thumbnailPrivateIdentityId); 1678 if (NS_WARN_IF(NS_FAILED(rv))) { 1679 return rv; 1680 } 1681 1682 rv = quotaManagerService->SetThumbnailPrivateIdentityId( 1683 thumbnailPrivateIdentityId); 1684 if (NS_WARN_IF(NS_FAILED(rv))) { 1685 return rv; 1686 } 1687 1688 return NS_OK; 1689 } 1690 1691 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) { 1692 if (NS_WARN_IF(!gBasePath)) { 1693 NS_WARNING("profile-do-change must precede profile-before-change-qm!"); 1694 return NS_OK; 1695 } 1696 1697 // mPendingProfileChange is our re-entrancy guard (the nested event loop 1698 // below may cause re-entrancy). 1699 if (mPendingProfileChange) { 1700 return NS_OK; 1701 } 1702 1703 AutoRestore<bool> pending(mPendingProfileChange); 1704 mPendingProfileChange = true; 1705 1706 mShutdownComplete = false; 1707 1708 PBackgroundChild* backgroundActor = 1709 BackgroundChild::GetOrCreateForCurrentThread(); 1710 if (NS_WARN_IF(!backgroundActor)) { 1711 return NS_ERROR_FAILURE; 1712 } 1713 1714 if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) { 1715 return NS_ERROR_FAILURE; 1716 } 1717 1718 MOZ_ALWAYS_TRUE(SpinEventLoopUntil( 1719 "QuotaManager::Observer::Observe profile-before-change-qm"_ns, 1720 [&]() { return mShutdownComplete; })); 1721 1722 gBasePath = nullptr; 1723 1724 gStorageName = nullptr; 1725 1726 gBuildId = nullptr; 1727 1728 return NS_OK; 1729 } 1730 1731 if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) { 1732 auto* const quotaManagerService = QuotaManagerService::GetOrCreate(); 1733 if (NS_WARN_IF(!quotaManagerService)) { 1734 return NS_ERROR_FAILURE; 1735 } 1736 1737 nsCOMPtr<nsIQuotaRequest> request; 1738 rv = quotaManagerService->ClearStoragesForPrivateBrowsing( 1739 nsGetterAddRefs(request)); 1740 if (NS_WARN_IF(NS_FAILED(rv))) { 1741 return rv; 1742 } 1743 1744 return NS_OK; 1745 } 1746 1747 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 1748 rv = Shutdown(); 1749 if (NS_WARN_IF(NS_FAILED(rv))) { 1750 return rv; 1751 } 1752 1753 return NS_OK; 1754 } 1755 1756 if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) { 1757 gLastOSWake = TimeStamp::Now(); 1758 1759 return NS_OK; 1760 } 1761 1762 NS_WARNING("Unknown observer topic!"); 1763 return NS_OK; 1764 } 1765 1766 /******************************************************************************* 1767 * Quota manager 1768 ******************************************************************************/ 1769 1770 QuotaManager::QuotaManager(const nsAString& aBasePath, 1771 const nsAString& aStorageName) 1772 : mQuotaMutex("QuotaManager.mQuotaMutex"), 1773 mBasePath(aBasePath), 1774 mStorageName(aStorageName), 1775 mTemporaryStorageUsage(0), 1776 mNextDirectoryLockId(0), 1777 mStorageInitialized(false), 1778 mPersistentStorageInitialized(false), 1779 mPersistentStorageInitializedInternal(false), 1780 mTemporaryStorageInitialized(false), 1781 mTemporaryStorageInitializedInternal(false), 1782 mInitializingAllTemporaryOrigins(false), 1783 mAllTemporaryOriginsInitialized(false), 1784 mCacheUsable(false) { 1785 AssertIsOnOwningThread(); 1786 MOZ_ASSERT(!gInstance); 1787 } 1788 1789 QuotaManager::~QuotaManager() { 1790 AssertIsOnOwningThread(); 1791 MOZ_ASSERT(!gInstance || gInstance == this); 1792 } 1793 1794 // static 1795 nsresult QuotaManager::Initialize() { 1796 MOZ_ASSERT(NS_IsMainThread()); 1797 1798 nsresult rv = Observer::Initialize(); 1799 if (NS_WARN_IF(NS_FAILED(rv))) { 1800 return rv; 1801 } 1802 1803 return NS_OK; 1804 } 1805 1806 // static 1807 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult> 1808 QuotaManager::GetOrCreate() { 1809 AssertIsOnBackgroundThread(); 1810 1811 if (gInstance) { 1812 return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance}); 1813 } 1814 1815 QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) { 1816 NS_WARNING( 1817 "Trying to create QuotaManager before profile-do-change! " 1818 "Forgot to call do_get_profile()?"); 1819 }); 1820 1821 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) { 1822 MOZ_ASSERT(false, 1823 "Trying to create QuotaManager after profile-before-change-qm!"); 1824 }); 1825 1826 auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName); 1827 1828 QM_TRY(MOZ_TO_RESULT(instance->Init())); 1829 1830 gInstance = instance; 1831 1832 // Do this before clients have a chance to acquire a directory lock for the 1833 // private repository. 1834 gInstance->ClearPrivateRepository(); 1835 1836 return WrapMovingNotNullUnchecked(std::move(instance)); 1837 } 1838 1839 Result<Ok, nsresult> QuotaManager::EnsureCreated() { 1840 AssertIsOnBackgroundThread(); 1841 1842 QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; })) 1843 } 1844 1845 // static 1846 QuotaManager* QuotaManager::Get() { 1847 // Does not return an owning reference. 1848 return gInstance; 1849 } 1850 1851 // static 1852 nsIObserver* QuotaManager::GetObserver() { 1853 MOZ_ASSERT(NS_IsMainThread()); 1854 1855 return Observer::GetInstance(); 1856 } 1857 1858 // static 1859 void QuotaManager::ProcessPendingNormalOriginOperations() { 1860 MOZ_ASSERT(IsRunningGTests()); 1861 1862 // Processes any pending events that may create normal origin operations. 1863 // This is needed in cases where an async method (e.g., InitializeStorage) 1864 // is called without a pre-acquired directory lock, which causes the 1865 // operation to be created and scheduled after the directory lock is 1866 // acquired. 1867 NS_ProcessPendingEvents(nullptr); 1868 1869 // Wait until all normal origin operations have completed. 1870 MOZ_ALWAYS_TRUE(SpinEventLoopUntil( 1871 "QuotaManager::ProcessPendingNormalOriginOperations"_ns, 1872 []() { return !gNormalOriginOps; })); 1873 1874 // Once an operation completes, it is removed from gNormalOriginOps. However, 1875 // there may still be a follow-up event pending that updates a flag after the 1876 // operation has finished. We need to process that event as well; otherwise, 1877 // callers of this helper may see inconsistent state. 1878 // For example, IsStorageInitialized could still return false even after 1879 // calling InitializeStorage and ProcessPendingNormalOriginOperations. 1880 NS_ProcessPendingEvents(nullptr); 1881 } 1882 1883 // static 1884 bool QuotaManager::IsShuttingDown() { return gShutdown; } 1885 1886 // static 1887 void QuotaManager::ShutdownInstance() { 1888 AssertIsOnBackgroundThread(); 1889 1890 if (gInstance) { 1891 auto recordTimeDeltaHelper = 1892 MakeRefPtr<RecordTimeDeltaHelper>(glean::dom_quota::shutdown_time); 1893 1894 recordTimeDeltaHelper->Start(); 1895 1896 // Glean SDK recommends using its own timing APIs where possible. In this 1897 // case, we use NowExcludingSuspendMs() directly to manually calculate a 1898 // duration that excludes suspend time. This is a valid exception because 1899 // our use case is sensitive to suspend, and we need full control over the 1900 // timing logic. 1901 // 1902 // We are currently recording both this and the older helper-based 1903 // measurement. The results are not directly comparable, since the new API 1904 // uses monotonic time. If this approach proves more reliable, we'll retire 1905 // the old telemetry, change the expiration of the new metric to never, 1906 // and add a matching "including suspend" version. 1907 1908 const auto startExcludingSuspendMs = NowExcludingSuspendMs(); 1909 1910 gInstance->Shutdown(); 1911 1912 const auto endExcludingSuspendMs = NowExcludingSuspendMs(); 1913 1914 if (startExcludingSuspendMs && endExcludingSuspendMs) { 1915 const auto duration = TimeDuration::FromMilliseconds( 1916 *endExcludingSuspendMs - *startExcludingSuspendMs); 1917 1918 glean::quotamanager_shutdown::total_time_excluding_suspend 1919 .AccumulateRawDuration(duration); 1920 } 1921 1922 recordTimeDeltaHelper->End(); 1923 1924 gInstance = nullptr; 1925 } else { 1926 // If we were never initialized, just set the flag to avoid late creation. 1927 gShutdown = true; 1928 } 1929 1930 RefPtr<Runnable> runnable = 1931 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted", 1932 []() { Observer::ShutdownCompleted(); }); 1933 MOZ_ASSERT(runnable); 1934 1935 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget())); 1936 } 1937 1938 // static 1939 void QuotaManager::Reset() { 1940 AssertIsOnBackgroundThread(); 1941 MOZ_ASSERT(!gInstance); 1942 MOZ_ASSERT(gShutdown); 1943 1944 gShutdown = false; 1945 } 1946 1947 // static 1948 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) { 1949 return mozilla::dom::quota::IsOSMetadata(aFileName); 1950 } 1951 1952 // static 1953 bool QuotaManager::IsDotFile(const nsAString& aFileName) { 1954 return mozilla::dom::quota::IsDotFile(aFileName); 1955 } 1956 1957 void QuotaManager::RegisterNormalOriginOp( 1958 NormalOriginOperationBase& aNormalOriginOp) { 1959 AssertIsOnBackgroundThread(); 1960 1961 if (!gNormalOriginOps) { 1962 gNormalOriginOps = new NormalOriginOpArray(); 1963 } 1964 1965 gNormalOriginOps->AppendElement(&aNormalOriginOp); 1966 } 1967 1968 void QuotaManager::UnregisterNormalOriginOp( 1969 NormalOriginOperationBase& aNormalOriginOp) { 1970 AssertIsOnBackgroundThread(); 1971 MOZ_ASSERT(gNormalOriginOps); 1972 1973 gNormalOriginOps->RemoveElement(&aNormalOriginOp); 1974 1975 if (gNormalOriginOps->IsEmpty()) { 1976 gNormalOriginOps = nullptr; 1977 } 1978 } 1979 1980 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) { 1981 AssertIsOnOwningThread(); 1982 1983 mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock)); 1984 1985 if (aLock.mExclusive) { 1986 mExclusiveDirectoryLocks.AppendElement(WrapNotNull(&aLock)); 1987 } 1988 1989 if (aLock.ShouldUpdateLockIdTable()) { 1990 MutexAutoLock lock(mQuotaMutex); 1991 1992 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id())); 1993 mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(), 1994 WrapNotNullUnchecked(&aLock)); 1995 } 1996 1997 aLock.SetRegistered(true); 1998 } 1999 2000 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) { 2001 AssertIsOnOwningThread(); 2002 2003 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock)); 2004 2005 if (aLock.mExclusive) { 2006 MOZ_ALWAYS_TRUE(mExclusiveDirectoryLocks.RemoveElement(&aLock)); 2007 } 2008 2009 if (aLock.ShouldUpdateLockIdTable()) { 2010 MutexAutoLock lock(mQuotaMutex); 2011 2012 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id())); 2013 mDirectoryLockIdTable.Remove(aLock.Id()); 2014 } 2015 2016 aLock.SetRegistered(false); 2017 } 2018 2019 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) { 2020 AssertIsOnOwningThread(); 2021 2022 mPendingDirectoryLocks.AppendElement(&aLock); 2023 } 2024 2025 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) { 2026 AssertIsOnOwningThread(); 2027 2028 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock)); 2029 } 2030 2031 uint64_t QuotaManager::CollectOriginsForEviction( 2032 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) { 2033 AssertIsOnOwningThread(); 2034 MOZ_ASSERT(aLocks.IsEmpty()); 2035 2036 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil, 2037 // or maybe a generalization if that. 2038 2039 struct MOZ_STACK_CLASS Helper final { 2040 static void GetInactiveOriginInfos( 2041 const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos, 2042 const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks, 2043 OriginInfosFlatTraversable& aInactiveOriginInfos) { 2044 for (const auto& originInfo : aOriginInfos) { 2045 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType != 2046 PERSISTENCE_TYPE_PERSISTENT); 2047 2048 if (originInfo->LockedPersisted()) { 2049 continue; 2050 } 2051 2052 // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a 2053 // moz-extension origin, unlike websites (which may more likely using 2054 // the local data as a cache but still able to retrieve the same data 2055 // from the server side) extensions do not have the same data stored 2056 // anywhere else and evicting the data would result into potential data 2057 // loss for the users. 2058 // 2059 // Also, unlike a website the extensions are explicitly installed and 2060 // uninstalled by the user and all data associated to the extension 2061 // principal will be completely removed once the addon is uninstalled. 2062 if (originInfo->mGroupInfo->mPersistenceType != 2063 PERSISTENCE_TYPE_TEMPORARY && 2064 originInfo->IsExtensionOrigin()) { 2065 continue; 2066 } 2067 2068 const auto originScope = 2069 OriginScope::FromOrigin(originInfo->FlattenToOriginMetadata()); 2070 2071 const bool match = 2072 std::any_of(aLocks.begin(), aLocks.end(), 2073 [&originScope](const DirectoryLockImpl* const lock) { 2074 return originScope.Matches(lock->GetOriginScope()); 2075 }); 2076 2077 if (!match) { 2078 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(), 2079 "Inactive origin shouldn't have open files!"); 2080 aInactiveOriginInfos.InsertElementSorted( 2081 originInfo, OriginInfoAccessTimeComparator()); 2082 } 2083 } 2084 } 2085 }; 2086 2087 // Split locks into separate arrays and filter out locks for persistent 2088 // storage, they can't block us. 2089 auto [temporaryStorageLocks, defaultStorageLocks, 2090 privateStorageLocks] = [this] { 2091 nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks; 2092 nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks; 2093 nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks; 2094 2095 for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) { 2096 const PersistenceScope& persistenceScope = lock->PersistenceScopeRef(); 2097 2098 if (persistenceScope.Matches( 2099 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_TEMPORARY))) { 2100 temporaryStorageLocks.AppendElement(lock); 2101 } 2102 2103 if (persistenceScope.Matches( 2104 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_DEFAULT))) { 2105 defaultStorageLocks.AppendElement(lock); 2106 } 2107 2108 if (persistenceScope.Matches( 2109 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PRIVATE))) { 2110 privateStorageLocks.AppendElement(lock); 2111 } 2112 } 2113 2114 return std::make_tuple(std::move(temporaryStorageLocks), 2115 std::move(defaultStorageLocks), 2116 std::move(privateStorageLocks)); 2117 }(); 2118 2119 // Enumerate and process inactive origins. This must be protected by the 2120 // mutex. 2121 MutexAutoLock lock(mQuotaMutex); 2122 2123 const auto [inactiveOrigins, sizeToBeFreed] = 2124 [this, &temporaryStorageLocks = temporaryStorageLocks, 2125 &defaultStorageLocks = defaultStorageLocks, 2126 &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] { 2127 nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins; 2128 for (const auto& entry : mGroupInfoPairs) { 2129 const auto& pair = entry.GetData(); 2130 2131 MOZ_ASSERT(!entry.GetKey().IsEmpty()); 2132 MOZ_ASSERT(pair); 2133 2134 RefPtr<GroupInfo> groupInfo = 2135 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); 2136 if (groupInfo) { 2137 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos, 2138 temporaryStorageLocks, 2139 inactiveOrigins); 2140 } 2141 2142 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); 2143 if (groupInfo) { 2144 Helper::GetInactiveOriginInfos( 2145 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins); 2146 } 2147 2148 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE); 2149 if (groupInfo) { 2150 Helper::GetInactiveOriginInfos( 2151 groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins); 2152 } 2153 } 2154 2155 #ifdef DEBUG 2156 // Make sure the array is sorted correctly. 2157 const bool inactiveOriginsSorted = 2158 std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(), 2159 [](const auto& lhs, const auto& rhs) { 2160 return lhs->mAccessTime < rhs->mAccessTime; 2161 }); 2162 MOZ_ASSERT(inactiveOriginsSorted); 2163 #endif 2164 2165 // Create a list of inactive and the least recently used origins 2166 // whose aggregate size is greater or equals the minimal size to be 2167 // freed. 2168 uint64_t sizeToBeFreed = 0; 2169 for (uint32_t count = inactiveOrigins.Length(), index = 0; 2170 index < count; index++) { 2171 if (sizeToBeFreed >= aMinSizeToBeFreed) { 2172 inactiveOrigins.TruncateLength(index); 2173 break; 2174 } 2175 2176 sizeToBeFreed += inactiveOrigins[index]->LockedUsage(); 2177 } 2178 2179 return std::pair(std::move(inactiveOrigins), sizeToBeFreed); 2180 }(); 2181 2182 if (sizeToBeFreed >= aMinSizeToBeFreed) { 2183 // Success, add directory locks for these origins, so any other 2184 // operations for them will be delayed (until origin eviction is finalized). 2185 2186 for (const auto& originInfo : inactiveOrigins) { 2187 auto lock = OriginDirectoryLock::CreateForEviction( 2188 WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType, 2189 originInfo->FlattenToOriginMetadata()); 2190 2191 lock->AcquireImmediately(); 2192 2193 aLocks.AppendElement(lock.forget()); 2194 } 2195 2196 return sizeToBeFreed; 2197 } 2198 2199 return 0; 2200 } 2201 2202 nsresult QuotaManager::Init() { 2203 AssertIsOnOwningThread(); 2204 2205 #ifdef XP_WIN 2206 CacheUseDOSDevicePathSyntaxPrefValue(); 2207 #endif 2208 2209 QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath)); 2210 2211 QM_TRY_UNWRAP( 2212 do_Init(mIndexedDBPath), 2213 GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME))); 2214 2215 QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName))); 2216 2217 QM_TRY_UNWRAP(do_Init(mStoragePath), 2218 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath)); 2219 2220 QM_TRY_UNWRAP( 2221 do_Init(mStorageArchivesPath), 2222 GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME))); 2223 2224 QM_TRY_UNWRAP( 2225 do_Init(mPermanentStoragePath), 2226 GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME))); 2227 2228 QM_TRY_UNWRAP( 2229 do_Init(mTemporaryStoragePath), 2230 GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME))); 2231 2232 QM_TRY_UNWRAP( 2233 do_Init(mDefaultStoragePath), 2234 GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME))); 2235 2236 QM_TRY_UNWRAP( 2237 do_Init(mPrivateStoragePath), 2238 GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME))); 2239 2240 QM_TRY_UNWRAP( 2241 do_Init(mToBeRemovedStoragePath), 2242 GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME))); 2243 2244 QM_TRY_UNWRAP(do_Init(mIOThread), 2245 MOZ_TO_RESULT_INVOKE_TYPED( 2246 nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread), 2247 "QuotaManager IO")); 2248 2249 // XXX This could be eventually moved to nsThreadUtils.h or nsIThread 2250 // could have an infallible method returning PRThread as return value. 2251 auto PRThreadFromThread = [](nsIThread* aThread) { 2252 MOZ_ASSERT(aThread); 2253 2254 PRThread* result; 2255 MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result)); 2256 MOZ_ASSERT(result); 2257 2258 return result; 2259 }; 2260 2261 mIOThreadAccessible.Transfer(PRThreadFromThread(*mIOThread)); 2262 2263 static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 && 2264 Client::FILESYSTEM == 3 && Client::LS == 4 && 2265 Client::TYPE_MAX == 5, 2266 "Fix the registration!"); 2267 2268 // Register clients. 2269 auto clients = decltype(mClients)::ValueType{}; 2270 clients.AppendElement(indexedDB::CreateQuotaClient()); 2271 clients.AppendElement(cache::CreateQuotaClient()); 2272 clients.AppendElement(simpledb::CreateQuotaClient()); 2273 clients.AppendElement(fs::CreateQuotaClient()); 2274 if (NextGenLocalStorageEnabled()) { 2275 clients.AppendElement(localstorage::CreateQuotaClient()); 2276 } else { 2277 clients.SetLength(Client::TypeMax()); 2278 } 2279 2280 mClients.init(std::move(clients)); 2281 2282 MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX, 2283 "Should be using an auto array with correct capacity!"); 2284 2285 mAllClientTypes.init(ClientTypesArray{ 2286 Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB, 2287 Client::Type::FILESYSTEM, Client::Type::LS}); 2288 mAllClientTypesExceptLS.init( 2289 ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE, 2290 Client::Type::SDB, Client::Type::FILESYSTEM}); 2291 2292 return NS_OK; 2293 } 2294 2295 // static 2296 void QuotaManager::MaybeRecordQuotaClientShutdownStep( 2297 const Client::Type aClientType, const nsACString& aStepDescription) { 2298 // Callable on any thread. 2299 2300 auto* const quotaManager = QuotaManager::Get(); 2301 MOZ_DIAGNOSTIC_ASSERT(quotaManager); 2302 2303 if (quotaManager->IsShuttingDown()) { 2304 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription); 2305 } 2306 } 2307 2308 // static 2309 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep( 2310 const Client::Type aClientType, const nsACString& aStepDescription) { 2311 // Callable on any thread. 2312 2313 auto* const quotaManager = QuotaManager::Get(); 2314 2315 if (quotaManager && quotaManager->IsShuttingDown()) { 2316 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription); 2317 } 2318 } 2319 2320 void QuotaManager::RecordQuotaManagerShutdownStep( 2321 const nsACString& aStepDescription) { 2322 // Callable on any thread. 2323 MOZ_ASSERT(IsShuttingDown()); 2324 2325 RecordShutdownStep(Nothing{}, aStepDescription); 2326 } 2327 2328 void QuotaManager::MaybeRecordQuotaManagerShutdownStep( 2329 const nsACString& aStepDescription) { 2330 // Callable on any thread. 2331 2332 if (IsShuttingDown()) { 2333 RecordQuotaManagerShutdownStep(aStepDescription); 2334 } 2335 } 2336 2337 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType, 2338 const nsACString& aStepDescription) { 2339 MOZ_ASSERT(IsShuttingDown()); 2340 2341 const TimeDuration elapsedSinceShutdownStart = 2342 TimeStamp::NowLoRes() - *mShutdownStartedAt; 2343 2344 const auto stepString = 2345 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(), 2346 nsPromiseFlatCString(aStepDescription).get()); 2347 2348 if (aClientType) { 2349 AssertIsOnBackgroundThread(); 2350 2351 mShutdownSteps[*aClientType].Append(stepString + "\n"_ns); 2352 } else { 2353 // Callable on any thread. 2354 MutexAutoLock lock(mQuotaMutex); 2355 2356 mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns); 2357 } 2358 2359 #ifdef DEBUG 2360 // XXX Probably this isn't the mechanism that should be used here. 2361 2362 NS_DebugBreak( 2363 NS_DEBUG_WARNING, 2364 nsAutoCString(aClientType ? Client::TypeToText(*aClientType) 2365 : "quota manager"_ns + " shutdown step"_ns) 2366 .get(), 2367 stepString.get(), __FILE__, __LINE__); 2368 #endif 2369 } 2370 2371 void QuotaManager::Shutdown() { 2372 AssertIsOnOwningThread(); 2373 MOZ_DIAGNOSTIC_ASSERT(!gShutdown); 2374 2375 // Define some local helper functions 2376 2377 auto flagShutdownStarted = [this]() { 2378 mShutdownStartedAt.init(TimeStamp::NowLoRes()); 2379 2380 // Setting this flag prevents the service from being recreated and prevents 2381 // further storages from being created. 2382 gShutdown = true; 2383 }; 2384 2385 nsCOMPtr<nsITimer> crashBrowserTimer; 2386 2387 auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) { 2388 auto* const quotaManager = static_cast<QuotaManager*>(aClosure); 2389 2390 quotaManager->RecordQuotaManagerShutdownStep( 2391 "crashBrowserTimerCallback"_ns); 2392 2393 nsCString annotation; 2394 2395 for (Client::Type type : quotaManager->AllClientTypes()) { 2396 auto& quotaClient = *(*quotaManager->mClients)[type]; 2397 2398 if (!quotaClient.IsShutdownCompleted()) { 2399 annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n", 2400 Client::TypeToText(type).get(), 2401 quotaClient.GetShutdownStatus().get(), 2402 quotaManager->mShutdownSteps[type].get()); 2403 } 2404 } 2405 2406 if (gNormalOriginOps) { 2407 annotation.AppendPrintf("QM: %zu normal origin ops pending\n", 2408 gNormalOriginOps->Length()); 2409 2410 for (const auto& op : *gNormalOriginOps) { 2411 #ifdef QM_COLLECTING_OPERATION_TELEMETRY 2412 annotation.AppendPrintf("Op: %s pending\n", op->Name()); 2413 #endif 2414 2415 nsCString data; 2416 op->Stringify(data); 2417 2418 annotation.AppendPrintf("Op details:\n%s\n", data.get()); 2419 } 2420 } 2421 { 2422 MutexAutoLock lock(quotaManager->mQuotaMutex); 2423 2424 annotation.AppendPrintf("Intermediate steps:\n%s\n", 2425 quotaManager->mQuotaManagerShutdownSteps.get()); 2426 } 2427 2428 CrashReporter::RecordAnnotationNSCString( 2429 CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation); 2430 2431 MOZ_CRASH("Quota manager shutdown timed out"); 2432 }; 2433 2434 auto startCrashBrowserTimer = [&]() { 2435 crashBrowserTimer = NS_NewTimer(); 2436 MOZ_ASSERT(crashBrowserTimer); 2437 if (crashBrowserTimer) { 2438 RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns); 2439 MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback( 2440 crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS, 2441 nsITimer::TYPE_ONE_SHOT, 2442 "quota::QuotaManager::Shutdown::crashBrowserTimer"_ns)); 2443 } 2444 }; 2445 2446 auto stopCrashBrowserTimer = [&]() { 2447 if (crashBrowserTimer) { 2448 RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns); 2449 QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel())); 2450 } 2451 }; 2452 2453 auto initiateShutdownWorkThreads = [this]() { 2454 RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns); 2455 bool needsToWait = false; 2456 for (Client::Type type : AllClientTypes()) { 2457 // Clients are supposed to also AbortAllOperations from this point on 2458 // to speed up shutdown, if possible. Thus pending operations 2459 // might not be executed anymore. 2460 needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads(); 2461 } 2462 2463 return needsToWait; 2464 }; 2465 2466 nsCOMPtr<nsITimer> killActorsTimer; 2467 2468 auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) { 2469 auto* const quotaManager = static_cast<QuotaManager*>(aClosure); 2470 2471 quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns); 2472 2473 // XXX: This abort is a workaround to unblock shutdown, which 2474 // ought to be removed by bug 1682326. We probably need more 2475 // checks to immediately abort new operations during 2476 // shutdown. 2477 quotaManager->GetClient(Client::IDB)->AbortAllOperations(); 2478 2479 for (Client::Type type : quotaManager->AllClientTypes()) { 2480 quotaManager->GetClient(type)->ForceKillActors(); 2481 } 2482 }; 2483 2484 auto startKillActorsTimer = [&]() { 2485 killActorsTimer = NS_NewTimer(); 2486 MOZ_ASSERT(killActorsTimer); 2487 if (killActorsTimer) { 2488 RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns); 2489 MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback( 2490 killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS, 2491 nsITimer::TYPE_ONE_SHOT, 2492 "quota::QuotaManager::Shutdown::killActorsTimer"_ns)); 2493 } 2494 }; 2495 2496 auto stopKillActorsTimer = [&]() { 2497 if (killActorsTimer) { 2498 RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns); 2499 QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel())); 2500 } 2501 }; 2502 2503 auto isAllClientsShutdownComplete = [this] { 2504 return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(), 2505 [&self = *this](const auto type) { 2506 return (*self.mClients)[type]->IsShutdownCompleted(); 2507 }); 2508 }; 2509 2510 auto shutdownAndJoinWorkThreads = [this]() { 2511 RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns); 2512 for (Client::Type type : AllClientTypes()) { 2513 (*mClients)[type]->FinalizeShutdownWorkThreads(); 2514 } 2515 }; 2516 2517 auto shutdownAndJoinIOThread = [this]() { 2518 RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns); 2519 2520 // Make sure to join with our IO thread. 2521 QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown())); 2522 }; 2523 2524 auto invalidatePendingDirectoryLocks = [this]() { 2525 RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns); 2526 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) { 2527 lock->Invalidate(); 2528 } 2529 }; 2530 2531 // Body of the function 2532 2533 ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContextTainted, 2534 "dom::quota::QuotaManager::Shutdown"_ns}; 2535 GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::Shutdown"); 2536 2537 // We always need to ensure that firefox does not shutdown with a private 2538 // repository still on disk. They are ideally cleaned up on PBM session end 2539 // but, in some cases like PBM autostart (i.e. 2540 // browser.privatebrowsing.autostart), private repository could only be 2541 // cleaned up on shutdown. ClearPrivateRepository below runs a async op and is 2542 // better to do it before we run the ShutdownStorageOp since it expects all 2543 // cleanup operations to be done by that point. We don't need to use the 2544 // returned promise here because `ClearPrivateRepository` registers the 2545 // underlying `ClearPrivateRepositoryOp` in `gNormalOriginOps`. 2546 ClearPrivateRepository(); 2547 2548 // This must be called before `flagShutdownStarted`, it would fail otherwise. 2549 // `ShutdownStorageOp` needs to acquire an exclusive directory lock over 2550 // entire <profile>/storage which will abort any existing operations and wait 2551 // for all existing directory locks to be released. So the shutdown operation 2552 // will effectively run after all existing operations. 2553 // Similar, to ClearPrivateRepository operation above, ShutdownStorageOp also 2554 // registers it's operation in `gNormalOriginOps` so we don't need to assign 2555 // returned promise. 2556 ShutdownStorage(); 2557 2558 flagShutdownStarted(); 2559 2560 startCrashBrowserTimer(); 2561 2562 // XXX: StopIdleMaintenance now just notifies all clients to abort any 2563 // maintenance work. 2564 // This could be done as part of QuotaClient::AbortAllOperations. 2565 StopIdleMaintenance(); 2566 2567 // XXX In theory, we could simplify the code below (and also the `Client` 2568 // interface) by removing the `initiateShutdownWorkThreads` and 2569 // `isAllClientsShutdownComplete` calls because it should be sufficient 2570 // to rely on `ShutdownStorage` to abort all existing operations and to 2571 // wait for all existing directory locks to be released as well. 2572 // 2573 // This might not be possible after adding mInitializingAllTemporaryOrigins 2574 // to the checks below. 2575 2576 const bool needsToWait = initiateShutdownWorkThreads() || 2577 static_cast<bool>(gNormalOriginOps) || 2578 mInitializingAllTemporaryOrigins; 2579 2580 // If any clients cannot shutdown immediately, spin the event loop while we 2581 // wait on all the threads to close. 2582 if (needsToWait) { 2583 startKillActorsTimer(); 2584 2585 MOZ_ALWAYS_TRUE(SpinEventLoopUntil( 2586 "QuotaManager::Shutdown"_ns, [this, isAllClientsShutdownComplete]() { 2587 return !gNormalOriginOps && isAllClientsShutdownComplete() && 2588 !mInitializingAllTemporaryOrigins; 2589 })); 2590 2591 stopKillActorsTimer(); 2592 } 2593 2594 shutdownAndJoinWorkThreads(); 2595 2596 shutdownAndJoinIOThread(); 2597 2598 invalidatePendingDirectoryLocks(); 2599 2600 stopCrashBrowserTimer(); 2601 } 2602 2603 void QuotaManager::InitQuotaForOrigin( 2604 const FullOriginMetadata& aFullOriginMetadata, bool aDirectoryExists) { 2605 AssertIsOnIOThread(); 2606 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType)); 2607 2608 MutexAutoLock lock(mQuotaMutex); 2609 2610 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo( 2611 aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix, 2612 aFullOriginMetadata.mGroup); 2613 2614 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>( 2615 groupInfo, aFullOriginMetadata.mOrigin, 2616 aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate, 2617 aFullOriginMetadata.mClientUsages, aFullOriginMetadata.mOriginUsage, 2618 aFullOriginMetadata.mLastAccessTime, 2619 aFullOriginMetadata.mLastMaintenanceDate, aFullOriginMetadata.mPersisted, 2620 aDirectoryExists)); 2621 } 2622 2623 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata, 2624 int64_t aSize) { 2625 MOZ_ASSERT(!NS_IsMainThread()); 2626 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType)); 2627 2628 MutexAutoLock lock(mQuotaMutex); 2629 2630 GroupInfoPair* pair; 2631 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) { 2632 return; 2633 } 2634 2635 RefPtr<GroupInfo> groupInfo = 2636 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType); 2637 if (!groupInfo) { 2638 return; 2639 } 2640 2641 RefPtr<OriginInfo> originInfo = 2642 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin); 2643 if (originInfo) { 2644 originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize); 2645 } 2646 } 2647 2648 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) { 2649 MOZ_ASSERT(!NS_IsMainThread()); 2650 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType)); 2651 2652 MutexAutoLock lock(mQuotaMutex); 2653 2654 GroupInfoPair* pair; 2655 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) { 2656 return; 2657 } 2658 2659 RefPtr<GroupInfo> groupInfo = 2660 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType); 2661 if (!groupInfo) { 2662 return; 2663 } 2664 2665 RefPtr<OriginInfo> originInfo = 2666 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin); 2667 if (originInfo) { 2668 originInfo->LockedResetUsageForClient(aClientMetadata.mClientType); 2669 } 2670 } 2671 2672 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType, 2673 const OriginMetadata& aOriginMetadata, 2674 Client::Type aClientType) { 2675 MOZ_ASSERT(!NS_IsMainThread()); 2676 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 2677 2678 MutexAutoLock lock(mQuotaMutex); 2679 2680 GroupInfoPair* pair; 2681 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 2682 return UsageInfo{}; 2683 } 2684 2685 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); 2686 if (!groupInfo) { 2687 return UsageInfo{}; 2688 } 2689 2690 RefPtr<OriginInfo> originInfo = 2691 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 2692 if (!originInfo) { 2693 return UsageInfo{}; 2694 } 2695 2696 return originInfo->LockedGetUsageForClient(aClientType); 2697 } 2698 2699 void QuotaManager::UpdateOriginAccessTime(const OriginMetadata& aOriginMetadata, 2700 int64_t aTimestamp) { 2701 AssertIsOnIOThread(); 2702 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 2703 2704 MutexAutoLock lock(mQuotaMutex); 2705 2706 GroupInfoPair* pair; 2707 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 2708 return; 2709 } 2710 2711 RefPtr<GroupInfo> groupInfo = 2712 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType); 2713 if (!groupInfo) { 2714 return; 2715 } 2716 2717 RefPtr<OriginInfo> originInfo = 2718 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 2719 if (!originInfo) { 2720 return; 2721 } 2722 2723 originInfo->LockedUpdateAccessTime(aTimestamp); 2724 } 2725 2726 void QuotaManager::UpdateOriginMaintenanceDate( 2727 const OriginMetadata& aOriginMetadata, int32_t aMaintenanceDate) { 2728 AssertIsOnIOThread(); 2729 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 2730 2731 MutexAutoLock lock(mQuotaMutex); 2732 2733 GroupInfoPair* pair; 2734 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 2735 return; 2736 } 2737 2738 RefPtr<GroupInfo> groupInfo = 2739 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType); 2740 if (!groupInfo) { 2741 return; 2742 } 2743 2744 RefPtr<OriginInfo> originInfo = 2745 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 2746 if (!originInfo) { 2747 return; 2748 } 2749 2750 originInfo->LockedUpdateMaintenanceDate(aMaintenanceDate); 2751 } 2752 2753 void QuotaManager::UpdateOriginAccessed(const OriginMetadata& aOriginMetadata) { 2754 AssertIsOnIOThread(); 2755 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 2756 2757 MutexAutoLock lock(mQuotaMutex); 2758 2759 GroupInfoPair* pair; 2760 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 2761 return; 2762 } 2763 2764 RefPtr<GroupInfo> groupInfo = 2765 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType); 2766 if (!groupInfo) { 2767 return; 2768 } 2769 2770 RefPtr<OriginInfo> originInfo = 2771 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 2772 if (!originInfo) { 2773 return; 2774 } 2775 2776 originInfo->LockedUpdateAccessed(); 2777 } 2778 2779 void QuotaManager::RemoveQuota() { 2780 AssertIsOnIOThread(); 2781 2782 MutexAutoLock lock(mQuotaMutex); 2783 2784 for (const auto& entry : mGroupInfoPairs) { 2785 const auto& pair = entry.GetData(); 2786 2787 MOZ_ASSERT(!entry.GetKey().IsEmpty()); 2788 MOZ_ASSERT(pair); 2789 2790 RefPtr<GroupInfo> groupInfo = 2791 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); 2792 if (groupInfo) { 2793 groupInfo->LockedRemoveOriginInfos(); 2794 } 2795 2796 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); 2797 if (groupInfo) { 2798 groupInfo->LockedRemoveOriginInfos(); 2799 } 2800 2801 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE); 2802 if (groupInfo) { 2803 groupInfo->LockedRemoveOriginInfos(); 2804 } 2805 } 2806 2807 mGroupInfoPairs.Clear(); 2808 2809 MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!"); 2810 } 2811 2812 // XXX Rename this method because the method doesn't load full quota 2813 // information if origin initialization is done lazily. 2814 nsresult QuotaManager::LoadQuota() { 2815 GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::LoadQuota"); 2816 2817 AssertIsOnIOThread(); 2818 MOZ_ASSERT(mStorageConnection); 2819 MOZ_ASSERT(!mTemporaryStorageInitializedInternal); 2820 2821 // A list of all unaccessed default or temporary origins. 2822 nsTArray<FullOriginMetadata> unaccessedOrigins; 2823 2824 // XXX The list of all unaccessed default or temporary origins can be now 2825 // generated from mAllTemporaryOrigins. 2826 auto MaybeCollectUnaccessedOrigin = 2827 [loadQuotaInfoStartTime = PR_Now(), 2828 &unaccessedOrigins](const auto& fullOriginMetadata) { 2829 if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) { 2830 unaccessedOrigins.AppendElement(fullOriginMetadata); 2831 } 2832 }; 2833 2834 auto recordTimeDeltaHelper = 2835 MakeRefPtr<RecordTimeDeltaHelper>(glean::dom_quota::info_load_time); 2836 2837 const auto startTime = recordTimeDeltaHelper->Start(); 2838 2839 auto LoadQuotaFromCache = [&]() -> nsresult { 2840 QM_TRY_INSPECT( 2841 const auto& stmt, 2842 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2843 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement, 2844 "SELECT repository_id, suffix, group_, " 2845 "origin, client_usages, usage, " 2846 "last_access_time, last_maintenance_date, accessed, persisted " 2847 "FROM origin"_ns)); 2848 2849 auto autoRemoveQuota = MakeScopeExit([&] { 2850 RemoveQuota(); 2851 RemoveTemporaryOrigins(); 2852 unaccessedOrigins.Clear(); 2853 }); 2854 2855 QM_TRY(quota::CollectWhileHasResult( 2856 *stmt, 2857 [this, 2858 &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> { 2859 QM_TRY_INSPECT(const int32_t& repositoryId, 2860 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0)); 2861 2862 const auto maybePersistenceType = 2863 PersistenceTypeFromInt32(repositoryId, fallible); 2864 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE)); 2865 2866 FullOriginMetadata fullOriginMetadata; 2867 2868 fullOriginMetadata.mPersistenceType = maybePersistenceType.value(); 2869 2870 QM_TRY_UNWRAP(fullOriginMetadata.mSuffix, 2871 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2872 GetUTF8String, 1)); 2873 2874 QM_TRY_UNWRAP(fullOriginMetadata.mGroup, 2875 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2876 GetUTF8String, 2)); 2877 2878 QM_TRY_UNWRAP(fullOriginMetadata.mOrigin, 2879 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2880 GetUTF8String, 3)); 2881 2882 fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin; 2883 2884 const auto extraInfo = 2885 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, 2886 fullOriginMetadata.mStorageOrigin}; 2887 2888 fullOriginMetadata.mIsPrivate = false; 2889 2890 QM_TRY_INSPECT(const auto& clientUsagesText, 2891 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 2892 GetUTF8String, 4)); 2893 2894 ClientUsageArray clientUsages; 2895 QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText))); 2896 2897 fullOriginMetadata.mClientUsages = clientUsages; 2898 2899 QM_TRY_INSPECT(fullOriginMetadata.mOriginUsage, 2900 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5)); 2901 2902 // fullOriginMetadata.mQuotaVersion is always set to kNoQuotaversion. 2903 // It is not cached in the database, and caching it would not make 2904 // sense because mQuotaVersion is currently unused by the L1 quota 2905 // info cache. Validation for the L1 quota info cache is performed 2906 // using the build ID instead. 2907 fullOriginMetadata.mQuotaVersion = kNoQuotaVersion; 2908 2909 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime, 2910 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6)); 2911 QM_TRY_UNWRAP(fullOriginMetadata.mLastMaintenanceDate, 2912 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7)); 2913 QM_TRY_UNWRAP(fullOriginMetadata.mAccessed, 2914 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8)); 2915 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted, 2916 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 9)); 2917 2918 QM_TRY_INSPECT(const bool& groupUpdated, 2919 MaybeUpdateGroupForOrigin(fullOriginMetadata)); 2920 2921 (void)groupUpdated; 2922 2923 QM_TRY_INSPECT( 2924 const bool& lastAccessTimeUpdated, 2925 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata)); 2926 2927 (void)lastAccessTimeUpdated; 2928 2929 // We don't need to update the .metadata-v2 file on disk here, 2930 // EnsureTemporaryOriginIsInitializedInternal is responsible for 2931 // doing that. We just need to use correct group and last access time 2932 // before initializing quota for the given origin. (Note that calling 2933 // LoadFullOriginMetadataWithRestore below might update the group in 2934 // the metadata file, but only as a side-effect. The actual place we 2935 // ensure consistency is in 2936 // EnsureTemporaryOriginIsInitializedInternal.) 2937 2938 if (fullOriginMetadata.mAccessed) { 2939 QM_TRY_INSPECT(const auto& directory, 2940 GetOriginDirectory(fullOriginMetadata)); 2941 2942 QM_TRY_INSPECT(const bool& exists, 2943 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists)); 2944 2945 QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND)); 2946 2947 QM_TRY_INSPECT(const bool& isDirectory, 2948 MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory)); 2949 2950 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR)); 2951 2952 // Calling LoadFullOriginMetadataWithRestore might update the group 2953 // in the metadata file, but only as a side-effect. The actual place 2954 // we ensure consistency is in 2955 // EnsureTemporaryOriginIsInitializedInternal. 2956 2957 QM_TRY_INSPECT(const auto& metadata, 2958 LoadFullOriginMetadataWithRestore(directory)); 2959 2960 QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime == 2961 metadata.mLastAccessTime)); 2962 2963 QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted), 2964 Err(NS_ERROR_FAILURE)); 2965 2966 // There was a previous regression where mLastAccessTime did not 2967 // match. To avoid failing on similar non-critical mismatches, we 2968 // wrap this check in a warn-only try macro for now. 2969 QM_WARNONLY_TRY( 2970 OkIf(fullOriginMetadata.mAccessed == metadata.mAccessed)); 2971 2972 // There was a previous regression where mLastAccessTime did not 2973 // match. To avoid failing on similar non-critical mismatches, we 2974 // wrap this check in a warn-only try macro for now. 2975 QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastMaintenanceDate == 2976 metadata.mLastMaintenanceDate)); 2977 2978 QM_TRY(OkIf(fullOriginMetadata.mPersistenceType == 2979 metadata.mPersistenceType), 2980 Err(NS_ERROR_FAILURE)); 2981 2982 QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix), 2983 Err(NS_ERROR_FAILURE)); 2984 2985 QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup), 2986 Err(NS_ERROR_FAILURE)); 2987 2988 QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin), 2989 Err(NS_ERROR_FAILURE)); 2990 2991 QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin == 2992 metadata.mStorageOrigin), 2993 Err(NS_ERROR_FAILURE)); 2994 2995 QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate), 2996 Err(NS_ERROR_FAILURE)); 2997 2998 // fullOriginMetadata.mQuotaVersion and metadata.mQuotaVersion do 2999 // not need to match, since fullOriginMetadata.mQuotaVersion is 3000 // always set to kNoQuotaVersion (see the comment above for more 3001 // details about fullOriginMetadata.mQuotaVersion). 3002 3003 // fullOriginMetadata.mOriginUsage and metadata.mOriginUsage do not 3004 // need to match, since mClientUsages is currently not saved after 3005 // last origin directory access. 3006 3007 // fullOriginMetadata.mClientUsages and metadata.mClientUsages do 3008 // not need to match, since mClientUsages is currently not saved 3009 // after last origin directory access. 3010 3011 MaybeCollectUnaccessedOrigin(metadata); 3012 3013 AddTemporaryOrigin(metadata); 3014 3015 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata))); 3016 } else { 3017 MaybeCollectUnaccessedOrigin(fullOriginMetadata); 3018 3019 AddTemporaryOrigin(fullOriginMetadata); 3020 3021 InitQuotaForOrigin(fullOriginMetadata); 3022 } 3023 3024 return Ok{}; 3025 })); 3026 3027 autoRemoveQuota.release(); 3028 3029 return NS_OK; 3030 }; 3031 3032 QM_TRY_INSPECT( 3033 const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> { 3034 if (mCacheUsable) { 3035 QM_TRY_INSPECT( 3036 const auto& stmt, 3037 CreateAndExecuteSingleStepStatement< 3038 SingleStepResult::ReturnNullIfNoResult>( 3039 *mStorageConnection, "SELECT valid, build_id FROM cache"_ns)); 3040 3041 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED)); 3042 3043 QM_TRY_INSPECT(const int32_t& valid, 3044 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0)); 3045 3046 if (valid) { 3047 if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) { 3048 return true; 3049 } 3050 3051 QM_TRY_INSPECT(const auto& buildId, 3052 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3053 nsAutoCString, stmt, GetUTF8String, 1)); 3054 3055 return buildId == *gBuildId; 3056 } 3057 } 3058 3059 return false; 3060 }())); 3061 3062 auto autoRemoveQuota = MakeScopeExit([&] { 3063 RemoveQuota(); 3064 RemoveTemporaryOrigins(); 3065 }); 3066 3067 if (!loadQuotaFromCache || 3068 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() || 3069 ![&LoadQuotaFromCache] { 3070 QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache())); 3071 return static_cast<bool>(res); 3072 }()) { 3073 // A keeper to defer the return only in Nightly, so that the telemetry data 3074 // for whole profile can be collected. 3075 #ifdef NIGHTLY_BUILD 3076 nsresult statusKeeper = NS_OK; 3077 #endif 3078 3079 const auto statusKeeperFunc = [&](const nsresult rv) { 3080 RECORD_IN_NIGHTLY(statusKeeper, rv); 3081 }; 3082 3083 for (const PersistenceType type : 3084 kInitializableBestEffortPersistenceTypes) { 3085 if (NS_WARN_IF(IsShuttingDown())) { 3086 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT); 3087 } 3088 3089 QM_TRY(([&]() -> Result<Ok, nsresult> { 3090 QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] { 3091 const auto innerFunc = [&](const auto&) -> nsresult { 3092 return InitializeRepository(type, 3093 MaybeCollectUnaccessedOrigin); 3094 }; 3095 3096 return ExecuteInitialization( 3097 type == PERSISTENCE_TYPE_DEFAULT 3098 ? Initialization::DefaultRepository 3099 : Initialization::TemporaryRepository, 3100 innerFunc); 3101 }())), 3102 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 3103 3104 return Ok{}; 3105 }())); 3106 } 3107 3108 #ifdef NIGHTLY_BUILD 3109 if (NS_FAILED(statusKeeper)) { 3110 return statusKeeper; 3111 } 3112 #endif 3113 } 3114 3115 autoRemoveQuota.release(); 3116 3117 const auto endTime = recordTimeDeltaHelper->End(); 3118 3119 if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() && 3120 static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >= 3121 StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() && 3122 !unaccessedOrigins.IsEmpty()) { 3123 QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins)); 3124 } 3125 3126 return NS_OK; 3127 } 3128 3129 void QuotaManager::UnloadQuota() { 3130 AssertIsOnIOThread(); 3131 MOZ_ASSERT(mStorageConnection); 3132 MOZ_ASSERT(mTemporaryStorageInitializedInternal); 3133 MOZ_ASSERT(mCacheUsable); 3134 3135 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); }); 3136 3137 mozStorageTransaction transaction( 3138 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 3139 3140 QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID); 3141 3142 QM_TRY(MOZ_TO_RESULT( 3143 mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)), 3144 QM_VOID); 3145 3146 nsCOMPtr<mozIStorageStatement> insertStmt; 3147 3148 { 3149 MutexAutoLock lock(mQuotaMutex); 3150 3151 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { 3152 MOZ_ASSERT(!iter.Key().IsEmpty()); 3153 3154 GroupInfoPair* const pair = iter.UserData(); 3155 MOZ_ASSERT(pair); 3156 3157 for (const PersistenceType type : kBestEffortPersistenceTypes) { 3158 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type); 3159 if (!groupInfo) { 3160 continue; 3161 } 3162 3163 for (const auto& originInfo : groupInfo->mOriginInfos) { 3164 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count()); 3165 3166 if (!originInfo->LockedDirectoryExists()) { 3167 continue; 3168 } 3169 3170 if (originInfo->mIsPrivate) { 3171 continue; 3172 } 3173 3174 if (insertStmt) { 3175 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset()); 3176 } else { 3177 QM_TRY_UNWRAP( 3178 insertStmt, 3179 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3180 nsCOMPtr<mozIStorageStatement>, mStorageConnection, 3181 CreateStatement, 3182 "INSERT INTO origin (repository_id, suffix, group_, " 3183 "origin, client_usages, usage, last_access_time, " 3184 "last_maintenance_date, accessed, persisted) " 3185 "VALUES (:repository_id, :suffix, :group_, :origin, " 3186 ":client_usages, :usage, :last_access_time, " 3187 ":last_maintenance_date, :accessed, :persisted)"_ns), 3188 QM_VOID); 3189 } 3190 3191 QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)), 3192 QM_VOID); 3193 3194 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID); 3195 } 3196 3197 groupInfo->LockedRemoveOriginInfos(); 3198 } 3199 3200 iter.Remove(); 3201 } 3202 } 3203 3204 QM_TRY_INSPECT( 3205 const auto& stmt, 3206 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3207 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement, 3208 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns), 3209 QM_VOID); 3210 3211 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID); 3212 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)), 3213 QM_VOID); 3214 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID); 3215 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID); 3216 } 3217 3218 void QuotaManager::RemoveOriginFromCache( 3219 const OriginMetadata& aOriginMetadata) { 3220 AssertIsOnIOThread(); 3221 MOZ_ASSERT(mStorageConnection); 3222 MOZ_ASSERT(!mTemporaryStorageInitializedInternal); 3223 3224 if (!mCacheUsable) { 3225 return; 3226 } 3227 3228 mozStorageTransaction transaction( 3229 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 3230 3231 QM_TRY_INSPECT( 3232 const auto& stmt, 3233 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3234 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement, 3235 "DELETE FROM origin WHERE repository_id = :repository_id AND suffix = :suffix AND group_ = :group AND origin = :origin;"_ns), 3236 QM_VOID); 3237 3238 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("repository_id"_ns, 3239 aOriginMetadata.mPersistenceType)), 3240 QM_VOID); 3241 QM_TRY(MOZ_TO_RESULT( 3242 stmt->BindUTF8StringByName("suffix"_ns, aOriginMetadata.mSuffix)), 3243 QM_VOID); 3244 QM_TRY(MOZ_TO_RESULT( 3245 stmt->BindUTF8StringByName("group"_ns, aOriginMetadata.mGroup)), 3246 QM_VOID); 3247 QM_TRY(MOZ_TO_RESULT( 3248 stmt->BindUTF8StringByName("origin"_ns, aOriginMetadata.mOrigin)), 3249 QM_VOID); 3250 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID); 3251 3252 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID); 3253 } 3254 3255 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject( 3256 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 3257 Client::Type aClientType, nsIFile* aFile, int64_t aFileSize, 3258 int64_t* aFileSizeOut /* = nullptr */) { 3259 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 3260 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType); 3261 3262 if (aFileSizeOut) { 3263 *aFileSizeOut = 0; 3264 } 3265 3266 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 3267 return nullptr; 3268 } 3269 3270 QM_TRY_INSPECT(const auto& path, 3271 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath), 3272 nullptr); 3273 3274 #ifdef DEBUG 3275 { 3276 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata), 3277 nullptr); 3278 3279 nsAutoString clientType; 3280 QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)), 3281 nullptr); 3282 3283 QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr); 3284 3285 QM_TRY_INSPECT( 3286 const auto& directoryPath, 3287 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath), 3288 nullptr); 3289 3290 MOZ_ASSERT(StringBeginsWith(path, directoryPath)); 3291 } 3292 #endif 3293 3294 QM_TRY_INSPECT( 3295 const int64_t fileSize, 3296 ([&aFile, aFileSize]() -> Result<int64_t, nsresult> { 3297 if (aFileSize == -1) { 3298 QM_TRY_INSPECT(const bool& exists, 3299 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists)); 3300 3301 if (exists) { 3302 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize)); 3303 } 3304 3305 return 0; 3306 } 3307 3308 return aFileSize; 3309 }()), 3310 nullptr); 3311 3312 RefPtr<QuotaObject> result; 3313 { 3314 MutexAutoLock lock(mQuotaMutex); 3315 3316 GroupInfoPair* pair; 3317 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 3318 return nullptr; 3319 } 3320 3321 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); 3322 3323 if (!groupInfo) { 3324 return nullptr; 3325 } 3326 3327 RefPtr<OriginInfo> originInfo = 3328 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 3329 3330 if (!originInfo) { 3331 return nullptr; 3332 } 3333 3334 // We need this extra raw pointer because we can't assign to the smart 3335 // pointer directly since QuotaObject::AddRef would try to acquire the same 3336 // mutex. 3337 const NotNull<CanonicalQuotaObject*> canonicalQuotaObject = 3338 originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] { 3339 // Create a new QuotaObject. The hashtable is not responsible to 3340 // delete the QuotaObject. 3341 return WrapNotNullUnchecked(new CanonicalQuotaObject( 3342 originInfo, aClientType, path, fileSize)); 3343 }); 3344 3345 // Addref the QuotaObject and move the ownership to the result. This must 3346 // happen before we unlock! 3347 result = canonicalQuotaObject->LockedAddRef(); 3348 } 3349 3350 if (aFileSizeOut) { 3351 *aFileSizeOut = fileSize; 3352 } 3353 3354 // The caller becomes the owner of the QuotaObject, that is, the caller is 3355 // is responsible to delete it when the last reference is removed. 3356 return result.forget(); 3357 } 3358 3359 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject( 3360 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, 3361 Client::Type aClientType, const nsAString& aPath, int64_t aFileSize, 3362 int64_t* aFileSizeOut /* = nullptr */) { 3363 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 3364 3365 if (aFileSizeOut) { 3366 *aFileSizeOut = 0; 3367 } 3368 3369 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr); 3370 3371 return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file, 3372 aFileSize, aFileSizeOut); 3373 } 3374 3375 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject( 3376 const int64_t aDirectoryLockId, const nsAString& aPath) { 3377 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 3378 3379 Maybe<MutexAutoLock> lock; 3380 3381 // See the comment for mDirectoryLockIdTable in QuotaManager.h 3382 if (!IsOnBackgroundThread()) { 3383 lock.emplace(mQuotaMutex); 3384 } 3385 3386 if (auto maybeDirectoryLock = 3387 mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) { 3388 const auto& directoryLock = *maybeDirectoryLock; 3389 MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable()); 3390 3391 const PersistenceType persistenceType = directoryLock->GetPersistenceType(); 3392 const OriginMetadata& originMetadata = directoryLock->OriginMetadata(); 3393 const Client::Type clientType = directoryLock->ClientType(); 3394 3395 lock.reset(); 3396 3397 return GetQuotaObject(persistenceType, originMetadata, clientType, aPath); 3398 } 3399 3400 MOZ_ASSERT(aDirectoryLockId == -1); 3401 return nullptr; 3402 } 3403 3404 Nullable<bool> QuotaManager::OriginPersisted( 3405 const OriginMetadata& aOriginMetadata) { 3406 AssertIsOnIOThread(); 3407 3408 MutexAutoLock lock(mQuotaMutex); 3409 3410 RefPtr<OriginInfo> originInfo = 3411 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata); 3412 if (originInfo) { 3413 return Nullable<bool>(originInfo->LockedPersisted()); 3414 } 3415 3416 return Nullable<bool>(); 3417 } 3418 3419 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) { 3420 AssertIsOnIOThread(); 3421 3422 MutexAutoLock lock(mQuotaMutex); 3423 3424 RefPtr<OriginInfo> originInfo = 3425 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata); 3426 if (originInfo && !originInfo->LockedPersisted()) { 3427 originInfo->LockedPersist(); 3428 } 3429 } 3430 3431 void QuotaManager::AbortOperationsForLocks( 3432 const DirectoryLockIdTableArray& aLockIds) { 3433 for (Client::Type type : AllClientTypes()) { 3434 if (aLockIds[type].Filled()) { 3435 (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]); 3436 } 3437 } 3438 } 3439 3440 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) { 3441 AssertIsOnOwningThread(); 3442 3443 for (const RefPtr<Client>& client : *mClients) { 3444 client->AbortOperationsForProcess(aContentParentId); 3445 } 3446 } 3447 3448 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory( 3449 const OriginMetadata& aOriginMetadata) const { 3450 QM_TRY_UNWRAP( 3451 auto directory, 3452 QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType))); 3453 3454 QM_TRY(MOZ_TO_RESULT(directory->Append( 3455 MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin)))); 3456 3457 return directory; 3458 } 3459 3460 Result<bool, nsresult> QuotaManager::DoesOriginDirectoryExist( 3461 const OriginMetadata& aOriginMetadata) const { 3462 AssertIsOnIOThread(); 3463 3464 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata)); 3465 3466 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists)); 3467 } 3468 3469 Result<nsCOMPtr<nsIFile>, nsresult> 3470 QuotaManager::GetOrCreateTemporaryOriginDirectory( 3471 const OriginMetadata& aOriginMetadata) { 3472 AssertIsOnIOThread(); 3473 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 3474 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal()); 3475 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal()); 3476 MOZ_ASSERT(IsTemporaryGroupInitializedInternal(aOriginMetadata)); 3477 MOZ_ASSERT(IsTemporaryOriginInitializedInternal(aOriginMetadata)); 3478 3479 ScopedLogExtraInfo scope{ 3480 ScopedLogExtraInfo::kTagContextTainted, 3481 "dom::quota::QuotaManager::GetOrCreateTemporaryOriginDirectory"_ns}; 3482 3483 // XXX Temporary band-aid fix until the root cause of uninitialized origins 3484 // after obtaining a client directory lock via OpenClientDirectory is 3485 // identified. 3486 QM_TRY( 3487 // Expression. 3488 MOZ_TO_RESULT(IsTemporaryOriginInitializedInternal(aOriginMetadata)) 3489 .mapErr([](const nsresult rv) { return NS_ERROR_NOT_INITIALIZED; }), 3490 // Custom return value. 3491 QM_PROPAGATE, 3492 // Cleanup function. 3493 ([this, aOriginMetadata](const nsresult) { 3494 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch( 3495 NS_NewRunnableFunction( 3496 "QuotaManager::GetOrCreateTemporaryOriginDirectory", 3497 [aOriginMetadata]() { 3498 QuotaManager* quotaManager = QuotaManager::Get(); 3499 MOZ_ASSERT(quotaManager); 3500 3501 OriginMetadataArray originMetadataArray; 3502 originMetadataArray.AppendElement(aOriginMetadata); 3503 3504 quotaManager->NoteUninitializedClients(originMetadataArray); 3505 quotaManager->NoteUninitializedOrigins(originMetadataArray); 3506 }), 3507 NS_DISPATCH_NORMAL)); 3508 })); 3509 3510 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata)); 3511 3512 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory)); 3513 3514 if (created) { 3515 // A new origin directory has been created. 3516 3517 // We have a temporary origin which has been initialized without ensuring 3518 // respective origin directory. So OriginInfo already exists and it needs 3519 // to be updated because the origin directory has been just created. 3520 3521 auto [timestamp, maintenanceDate, accessed, persisted] = 3522 WithOriginInfo(aOriginMetadata, [](const auto& originInfo) { 3523 const int64_t timestamp = originInfo->LockedAccessTime(); 3524 const int32_t maintenanceDate = originInfo->LockedMaintenanceDate(); 3525 const bool accessed = originInfo->LockedAccessed(); 3526 const bool persisted = originInfo->LockedPersisted(); 3527 3528 originInfo->LockedDirectoryCreated(); 3529 3530 return std::make_tuple(timestamp, maintenanceDate, accessed, 3531 persisted); 3532 }); 3533 3534 FullOriginMetadata fullOriginMetadata{ 3535 aOriginMetadata, 3536 OriginStateMetadata{timestamp, maintenanceDate, accessed, persisted}, 3537 ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion}; 3538 3539 // Usually, infallible operations are placed after fallible ones. However, 3540 // since we lack atomic support for creating the origin directory along 3541 // with its metadata, we need to add the origin to cached origins right 3542 // after directory creation. 3543 AddTemporaryOrigin(fullOriginMetadata); 3544 3545 QM_TRY(MOZ_TO_RESULT( 3546 CreateDirectoryMetadata2(*directory, fullOriginMetadata))); 3547 } 3548 3549 return std::move(directory); 3550 } 3551 3552 Result<Ok, nsresult> QuotaManager::EnsureTemporaryOriginDirectoryCreated( 3553 const OriginMetadata& aOriginMetadata) { 3554 QM_TRY_RETURN(GetOrCreateTemporaryOriginDirectory(aOriginMetadata) 3555 .map([](const auto& res) { return Ok{}; })); 3556 } 3557 3558 // static 3559 nsresult QuotaManager::CreateDirectoryMetadata2( 3560 nsIFile& aDirectory, const FullOriginMetadata& aFullOriginMetadata) { 3561 GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::CreateDirectoryMetadata2"); 3562 3563 AssertIsOnIOThread(); 3564 3565 QM_TRY(ArtificialFailure( 3566 nsIQuotaArtificialFailure::CATEGORY_CREATE_DIRECTORY_METADATA2)); 3567 3568 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3569 nsCOMPtr<nsIFile>, aDirectory, Clone)); 3570 3571 QM_TRY( 3572 MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME)))); 3573 3574 QM_TRY_INSPECT(const auto& stream, 3575 GetBinaryOutputStream(*file, FileFlag::Truncate)); 3576 MOZ_ASSERT(stream); 3577 3578 QM_TRY(MOZ_TO_RESULT( 3579 WriteDirectoryMetadataHeader(*stream, aFullOriginMetadata))); 3580 3581 // Legacy field, previously used for suffix. The value is no longer used, but 3582 // we continue writing the correct suffix value to preserve compatibility 3583 // with older builds that may still expect it. 3584 QM_TRY( 3585 MOZ_TO_RESULT(stream->WriteStringZ(aFullOriginMetadata.mSuffix.get()))); 3586 3587 // Legacy field, previously used for group. The value is no longer used, but 3588 // we continue writing the correct group value to preserve compatibility with 3589 // older builds that may still expect it. 3590 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aFullOriginMetadata.mGroup.get()))); 3591 3592 QM_TRY(MOZ_TO_RESULT( 3593 stream->WriteStringZ(aFullOriginMetadata.mStorageOrigin.get()))); 3594 3595 // Legacy field, previously used for isPrivate (and before that, for isApp). 3596 // The value is no longer used, but we continue writing the correct isPrivate 3597 // value (true or false) to preserve compatibility with older builds that may 3598 // still expect it. 3599 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aFullOriginMetadata.mIsPrivate))); 3600 3601 QM_TRY(MOZ_TO_RESULT(stream->Write32(kMetadataSentinel))); 3602 3603 QM_TRY(MOZ_TO_RESULT(stream->Write32(aFullOriginMetadata.mQuotaVersion))); 3604 3605 QM_TRY(MOZ_TO_RESULT(stream->Write64(aFullOriginMetadata.mOriginUsage))); 3606 3607 nsCString clientUsagesText; 3608 aFullOriginMetadata.mClientUsages.Serialize(clientUsagesText); 3609 3610 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(clientUsagesText.get()))); 3611 3612 QM_TRY(MOZ_TO_RESULT(stream->Flush())); 3613 3614 QM_TRY(MOZ_TO_RESULT(stream->Close())); 3615 3616 QM_TRY(MOZ_TO_RESULT( 3617 file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME)))); 3618 3619 return NS_OK; 3620 } 3621 3622 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) { 3623 AssertIsOnIOThread(); 3624 MOZ_ASSERT(aDirectory); 3625 MOZ_ASSERT(mStorageConnection); 3626 3627 glean::quotamanager::restore_origin_directory_metadata_counter.Add(); 3628 3629 RefPtr<RestoreDirectoryMetadata2Helper> helper = 3630 new RestoreDirectoryMetadata2Helper(aDirectory); 3631 3632 QM_TRY(MOZ_TO_RESULT(helper->Init())); 3633 3634 QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File())); 3635 3636 return NS_OK; 3637 } 3638 3639 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata( 3640 nsIFile* aDirectory, PersistenceType aPersistenceType) { 3641 MOZ_ASSERT(!NS_IsMainThread()); 3642 MOZ_ASSERT(aDirectory); 3643 MOZ_ASSERT(mStorageConnection); 3644 3645 QM_TRY_INSPECT(const auto& binaryStream, 3646 GetBinaryInputStream(*aDirectory, 3647 nsLiteralString(METADATA_V2_FILE_NAME))); 3648 3649 FullOriginMetadata fullOriginMetadata; 3650 3651 QM_TRY_INSPECT(const OriginStateMetadata& originStateMetadata, 3652 ReadDirectoryMetadataHeader(*binaryStream)); 3653 3654 static_cast<OriginStateMetadata&>(fullOriginMetadata) = originStateMetadata; 3655 3656 fullOriginMetadata.mPersistenceType = aPersistenceType; 3657 3658 // Legacy field, previously used for suffix. This value is no longer used, 3659 // but still read and discarded to preserve compatibility with older builds 3660 // that may still expect it. 3661 QM_TRY_INSPECT( 3662 const auto& unusedData1, 3663 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString)); 3664 (void)unusedData1; 3665 3666 // Legacy field, previously used for group. This value is no longer used, but 3667 // still read and discarded to preserve compatibility with older builds that 3668 // may still expect it. 3669 QM_TRY_INSPECT( 3670 const auto& unusedData2, 3671 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString)); 3672 (void)unusedData2; 3673 3674 QM_TRY_UNWRAP( 3675 fullOriginMetadata.mStorageOrigin, 3676 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString)); 3677 3678 const auto extraInfo = 3679 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, 3680 fullOriginMetadata.mStorageOrigin}; 3681 3682 // Legacy field, previously used for isPrivate (and before that, for isApp). 3683 // This value is no longer used, but still read and discarded to preserve 3684 // compatibility with older builds that may still expect it. 3685 QM_TRY_INSPECT(const bool& unusedData3, 3686 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean)); 3687 (void)unusedData3; 3688 3689 QM_VERBOSEONLY_TRY_UNWRAP(const auto sentinel, 3690 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32)); 3691 3692 if (sentinel) { 3693 if (sentinel.ref() != kMetadataSentinel) { 3694 QM_TRY(MOZ_TO_RESULT(false)); 3695 } 3696 3697 QM_TRY_UNWRAP(fullOriginMetadata.mQuotaVersion, 3698 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32)); 3699 3700 QM_TRY_UNWRAP(fullOriginMetadata.mOriginUsage, 3701 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64)); 3702 3703 QM_TRY_INSPECT(const auto& clientUsagesText, 3704 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, 3705 ReadCString)); 3706 3707 ClientUsageArray clientUsages; 3708 QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText))); 3709 3710 fullOriginMetadata.mClientUsages = clientUsages; 3711 } else { 3712 fullOriginMetadata.mQuotaVersion = kNoQuotaVersion; 3713 fullOriginMetadata.mOriginUsage = 0; 3714 fullOriginMetadata.mClientUsages = ClientUsageArray(); 3715 } 3716 3717 QM_TRY(MOZ_TO_RESULT(binaryStream->Close())); 3718 3719 auto principal = 3720 [&storageOrigin = 3721 fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> { 3722 if (storageOrigin.EqualsLiteral(kChromeOrigin)) { 3723 return SystemPrincipal::Get(); 3724 } 3725 return BasePrincipal::CreateContentPrincipal(storageOrigin); 3726 }(); 3727 QM_TRY(MOZ_TO_RESULT(principal)); 3728 3729 PrincipalInfo principalInfo; 3730 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo))); 3731 3732 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)), 3733 Err(NS_ERROR_MALFORMED_URI)); 3734 3735 QM_TRY_UNWRAP(auto principalMetadata, 3736 GetInfoFromValidatedPrincipalInfo(*this, principalInfo)); 3737 3738 fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix); 3739 fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup); 3740 fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin); 3741 fullOriginMetadata.mIsPrivate = principalMetadata.mIsPrivate; 3742 3743 QM_TRY_INSPECT(const bool& groupUpdated, 3744 MaybeUpdateGroupForOrigin(fullOriginMetadata)); 3745 3746 // A workaround for a bug in GetLastModifiedTime implementation which should 3747 // have returned the current time instead of INT64_MIN when there were no 3748 // suitable files for getting last modified time. 3749 QM_TRY_INSPECT(const bool& lastAccessTimeUpdated, 3750 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata)); 3751 3752 if (groupUpdated || lastAccessTimeUpdated) { 3753 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce 3754 // I/O. 3755 QM_TRY(MOZ_TO_RESULT( 3756 CreateDirectoryMetadata2(*aDirectory, fullOriginMetadata))); 3757 } 3758 3759 return fullOriginMetadata; 3760 } 3761 3762 Result<FullOriginMetadata, nsresult> 3763 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) { 3764 QM_TRY_INSPECT(const auto& result, 3765 LoadFullOriginMetadataWithRestoreAndStatus(aDirectory)); 3766 3767 // The first element of the pair is the FullOriginMetadata, the second is the 3768 // restore status. 3769 return result.first; 3770 } 3771 3772 Result<std::pair<FullOriginMetadata, bool /* restore status */>, nsresult> 3773 QuotaManager::LoadFullOriginMetadataWithRestoreAndStatus(nsIFile* aDirectory) { 3774 // XXX Once the persistence type is stored in the metadata file, this block 3775 // for getting the persistence type from the parent directory name can be 3776 // removed. 3777 nsCOMPtr<nsIFile> parentDir; 3778 QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir)))); 3779 3780 const auto maybePersistenceType = 3781 PersistenceTypeFromFile(*parentDir, fallible); 3782 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE)); 3783 3784 const auto& persistenceType = maybePersistenceType.value(); 3785 3786 bool restored = false; 3787 3788 QM_TRY_INSPECT( 3789 const auto& fullOriginMetadata, 3790 QM_OR_ELSE_WARN( 3791 // Expression. 3792 LoadFullOriginMetadata(aDirectory, persistenceType), 3793 // Fallback. 3794 ([&aDirectory, &persistenceType, &restored, 3795 this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> { 3796 restored = true; 3797 QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory))); 3798 3799 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType)); 3800 }))); 3801 3802 return std::make_pair(fullOriginMetadata, restored); 3803 } 3804 3805 Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata( 3806 nsIFile* aDirectory) { 3807 MOZ_ASSERT(aDirectory); 3808 MOZ_ASSERT(mStorageConnection); 3809 3810 QM_TRY_INSPECT( 3811 const auto& leafName, 3812 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName)); 3813 3814 // XXX Consider using QuotaManager::ParseOrigin here. 3815 nsCString spec; 3816 OriginAttributes attrs; 3817 nsCString originalSuffix; 3818 OriginParser::ResultType result = OriginParser::ParseOrigin( 3819 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix); 3820 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin)); 3821 3822 QM_TRY_INSPECT( 3823 const auto& principal, 3824 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> { 3825 if (spec.EqualsLiteral(kChromeOrigin)) { 3826 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get()); 3827 } 3828 3829 nsCOMPtr<nsIURI> uri; 3830 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec))); 3831 3832 return nsCOMPtr<nsIPrincipal>( 3833 BasePrincipal::CreateContentPrincipal(uri, attrs)); 3834 }())); 3835 QM_TRY(MOZ_TO_RESULT(principal)); 3836 3837 PrincipalInfo principalInfo; 3838 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo))); 3839 3840 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)), 3841 Err(NS_ERROR_MALFORMED_URI)); 3842 3843 QM_TRY_UNWRAP(auto principalMetadata, 3844 GetInfoFromValidatedPrincipalInfo(*this, principalInfo)); 3845 3846 QM_TRY_INSPECT(const auto& parentDirectory, 3847 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>, 3848 aDirectory, GetParent)); 3849 3850 const auto maybePersistenceType = 3851 PersistenceTypeFromFile(*parentDirectory, fallible); 3852 QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome())); 3853 3854 return OriginMetadata{std::move(principalMetadata), 3855 maybePersistenceType.value()}; 3856 } 3857 3858 Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) { 3859 AssertIsOnIOThread(); 3860 3861 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) { 3862 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true))); 3863 } 3864 3865 QM_TRY_INSPECT(const auto& toBeRemovedStorageDir, 3866 QM_NewLocalFile(*mToBeRemovedStoragePath)); 3867 3868 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir)); 3869 3870 (void)created; 3871 3872 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo( 3873 toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID())))); 3874 } 3875 3876 Result<bool, nsresult> QuotaManager::DoesClientDirectoryExist( 3877 const ClientMetadata& aClientMetadata) const { 3878 AssertIsOnIOThread(); 3879 3880 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aClientMetadata)); 3881 3882 QM_TRY(MOZ_TO_RESULT( 3883 directory->Append(Client::TypeToString(aClientMetadata.mClientType)))); 3884 3885 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists)); 3886 } 3887 3888 template <typename OriginFunc> 3889 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType, 3890 OriginFunc&& aOriginFunc) { 3891 GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::InitializeRepository"); 3892 3893 AssertIsOnIOThread(); 3894 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT || 3895 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY || 3896 aPersistenceType == PERSISTENCE_TYPE_DEFAULT); 3897 3898 QM_TRY_INSPECT(const auto& directory, 3899 QM_NewLocalFile(GetStoragePath(aPersistenceType))); 3900 3901 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory)); 3902 3903 (void)created; 3904 3905 uint64_t iterations = 0; 3906 3907 // A keeper to defer the return only in Nightly, so that the telemetry data 3908 // for whole profile can be collected 3909 #ifdef NIGHTLY_BUILD 3910 nsresult statusKeeper = NS_OK; 3911 #endif 3912 3913 const auto statusKeeperFunc = [&](const nsresult rv) { 3914 RECORD_IN_NIGHTLY(statusKeeper, rv); 3915 }; 3916 3917 struct RenameAndInitInfo { 3918 nsCOMPtr<nsIFile> mOriginDirectory; 3919 FullOriginMetadata mFullOriginMetadata; 3920 }; 3921 nsTArray<RenameAndInitInfo> renameAndInitInfos; 3922 3923 QM_TRY(([&]() -> Result<Ok, nsresult> { 3924 QM_TRY( 3925 CollectEachFile( 3926 *directory, 3927 [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> { 3928 if (NS_WARN_IF(IsShuttingDown())) { 3929 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT); 3930 } 3931 3932 QM_TRY( 3933 ([this, &iterations, &childDirectory, &renameAndInitInfos, 3934 aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> { 3935 QM_TRY_INSPECT( 3936 const auto& leafName, 3937 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3938 nsAutoString, childDirectory, GetLeafName)); 3939 3940 QM_TRY_INSPECT(const auto& dirEntryKind, 3941 GetDirEntryKind(*childDirectory)); 3942 3943 switch (dirEntryKind) { 3944 case nsIFileKind::ExistsAsDirectory: { 3945 QM_TRY_UNWRAP( 3946 auto maybeMetadata, 3947 QM_OR_ELSE_WARN_IF( 3948 // Expression 3949 LoadFullOriginMetadataWithRestore( 3950 childDirectory) 3951 .map([](auto metadata) 3952 -> Maybe<FullOriginMetadata> { 3953 return Some(std::move(metadata)); 3954 }), 3955 // Predicate. 3956 IsSpecificError<NS_ERROR_MALFORMED_URI>, 3957 // Fallback. 3958 ErrToDefaultOk<Maybe<FullOriginMetadata>>)); 3959 3960 if (!maybeMetadata) { 3961 // Unknown directories during initialization are 3962 // allowed. Just warn if we find them. 3963 UNKNOWN_FILE_WARNING(leafName); 3964 break; 3965 } 3966 3967 auto metadata = maybeMetadata.extract(); 3968 3969 MOZ_ASSERT(metadata.mPersistenceType == 3970 aPersistenceType); 3971 3972 const auto extraInfo = ScopedLogExtraInfo{ 3973 ScopedLogExtraInfo::kTagStorageOriginTainted, 3974 metadata.mStorageOrigin}; 3975 3976 // FIXME(tt): The check for origin name consistency can 3977 // be removed once we have an upgrade to traverse origin 3978 // directories and check through the directory metadata 3979 // files. 3980 const auto originSanitized = 3981 MakeSanitizedOriginCString(metadata.mOrigin); 3982 3983 NS_ConvertUTF16toUTF8 utf8LeafName(leafName); 3984 if (!originSanitized.Equals(utf8LeafName)) { 3985 QM_WARNING( 3986 "The name of the origin directory (%s) doesn't " 3987 "match the sanitized origin string (%s) in the " 3988 "metadata file!", 3989 utf8LeafName.get(), originSanitized.get()); 3990 3991 // If it's the known case, we try to restore the 3992 // origin directory name if it's possible. 3993 if (originSanitized.Equals(utf8LeafName + "."_ns)) { 3994 renameAndInitInfos.AppendElement( 3995 RenameAndInitInfo{std::move(childDirectory), 3996 std::move(metadata)}); 3997 break; 3998 } 3999 4000 // XXXtt: Try to restore the unknown cases base on the 4001 // content for their metadata files. Note that if the 4002 // restore fails, QM should maintain a list and ensure 4003 // they won't be accessed after initialization. 4004 } 4005 4006 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) { 4007 std::forward<OriginFunc>(aOriginFunc)(metadata); 4008 4009 AddTemporaryOrigin(metadata); 4010 4011 if (StaticPrefs:: 4012 dom_quotaManager_loadQuotaFromSecondaryCache() && 4013 metadata.mQuotaVersion == kCurrentQuotaVersion && 4014 !metadata.mAccessed) { 4015 InitQuotaForOrigin(metadata); 4016 4017 break; 4018 } 4019 } 4020 4021 QM_TRY(QM_OR_ELSE_WARN_IF( 4022 // Expression. 4023 MOZ_TO_RESULT( 4024 InitializeOrigin(childDirectory, metadata)), 4025 // Predicate. 4026 IsDatabaseCorruptionError, 4027 // Fallback. 4028 ([&childDirectory, &metadata, 4029 this](const nsresult rv) -> Result<Ok, nsresult> { 4030 // If the origin can't be initialized due to 4031 // corruption, this is a permanent 4032 // condition, and we need to remove all data 4033 // for the origin on disk. 4034 4035 QM_TRY( 4036 MOZ_TO_RESULT(childDirectory->Remove(true))); 4037 4038 RemoveTemporaryOrigin(metadata); 4039 4040 return Ok{}; 4041 }))); 4042 4043 break; 4044 } 4045 4046 case nsIFileKind::ExistsAsFile: 4047 if (IsOSMetadata(leafName) || IsDotFile(leafName)) { 4048 break; 4049 } 4050 4051 // Unknown files during initialization are now allowed. 4052 // Just warn if we find them. 4053 UNKNOWN_FILE_WARNING(leafName); 4054 break; 4055 4056 case nsIFileKind::DoesNotExist: 4057 // Ignore files that got removed externally while 4058 // iterating. 4059 break; 4060 } 4061 4062 iterations++; 4063 4064 return Ok{}; 4065 }()), 4066 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 4067 4068 return Ok{}; 4069 }), 4070 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 4071 4072 return Ok{}; 4073 }())); 4074 4075 for (auto& info : renameAndInitInfos) { 4076 QM_TRY(([&]() -> Result<Ok, nsresult> { 4077 QM_TRY( 4078 ([&directory, &info, this, aPersistenceType, 4079 &aOriginFunc]() -> Result<Ok, nsresult> { 4080 const auto& metadata = info.mFullOriginMetadata; 4081 4082 const auto extraInfo = 4083 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, 4084 metadata.mStorageOrigin}; 4085 4086 const auto originDirName = 4087 MakeSanitizedOriginString(metadata.mOrigin); 4088 4089 // Check if targetDirectory exist. 4090 QM_TRY_INSPECT(const auto& targetDirectory, 4091 CloneFileAndAppend(*directory, originDirName)); 4092 4093 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER( 4094 targetDirectory, Exists)); 4095 4096 if (exists) { 4097 QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true))); 4098 4099 return Ok{}; 4100 } 4101 4102 QM_TRY(MOZ_TO_RESULT( 4103 info.mOriginDirectory->RenameTo(nullptr, originDirName))); 4104 4105 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) { 4106 std::forward<OriginFunc>(aOriginFunc)(metadata); 4107 4108 AddTemporaryOrigin(metadata); 4109 4110 if (StaticPrefs::dom_quotaManager_loadQuotaFromSecondaryCache() && 4111 metadata.mQuotaVersion == kCurrentQuotaVersion && 4112 !metadata.mAccessed) { 4113 InitQuotaForOrigin(metadata); 4114 4115 return Ok{}; 4116 } 4117 } 4118 4119 // XXX We don't check corruption here ? 4120 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(targetDirectory, metadata))); 4121 4122 return Ok{}; 4123 }()), 4124 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 4125 4126 return Ok{}; 4127 }())); 4128 } 4129 4130 #ifdef NIGHTLY_BUILD 4131 if (NS_FAILED(statusKeeper)) { 4132 return statusKeeper; 4133 } 4134 #endif 4135 4136 glean::quotamanager_initialize_repository::number_of_iterations 4137 .Get(PersistenceTypeToString(aPersistenceType)) 4138 .AccumulateSingleSample(iterations); 4139 4140 return NS_OK; 4141 } 4142 4143 nsresult QuotaManager::InitializeOrigin( 4144 nsIFile* aDirectory, const FullOriginMetadata& aFullOriginMetadata, 4145 bool aForGroup) { 4146 GECKO_TRACE_SCOPE("dom::quota", "QuotaManager::InitializeOrigin"); 4147 4148 QM_LOG(("Starting origin initialization for: %s", 4149 aFullOriginMetadata.mOrigin.get())); 4150 4151 AssertIsOnIOThread(); 4152 4153 QM_TRY( 4154 ArtificialFailure(nsIQuotaArtificialFailure::CATEGORY_INITIALIZE_ORIGIN)); 4155 4156 // The ScopedLogExtraInfo is not set here on purpose, so the callers can 4157 // decide if they want to set it. The extra info can be set sooner this way 4158 // as well. 4159 4160 const bool trackQuota = 4161 aFullOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT; 4162 4163 if (trackQuota && !aForGroup && 4164 QuotaPrefs::LazyOriginInitializationEnabled()) { 4165 QM_LOG(("Skipping origin initialization for: %s (it will be done lazily)", 4166 aFullOriginMetadata.mOrigin.get())); 4167 4168 return NS_OK; 4169 } 4170 4171 NotifyOriginInitializationStarted(*this); 4172 4173 // We need to initialize directories of all clients if they exists and also 4174 // get the total usage to initialize the quota. 4175 4176 ClientUsageArray clientUsages; 4177 4178 // A keeper to defer the return only in Nightly, so that the telemetry data 4179 // for whole profile can be collected 4180 #ifdef NIGHTLY_BUILD 4181 nsresult statusKeeper = NS_OK; 4182 #endif 4183 4184 QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) { 4185 RECORD_IN_NIGHTLY(statusKeeper, rv); 4186 }]() -> Result<Ok, nsresult> { 4187 QM_TRY( 4188 CollectEachFile( 4189 *aDirectory, 4190 [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 4191 if (NS_WARN_IF(IsShuttingDown())) { 4192 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT); 4193 } 4194 4195 QM_TRY( 4196 ([this, &file, trackQuota, &aFullOriginMetadata, 4197 &clientUsages]() -> Result<Ok, nsresult> { 4198 QM_TRY_INSPECT(const auto& leafName, 4199 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 4200 nsAutoString, file, GetLeafName)); 4201 4202 QM_TRY_INSPECT(const auto& dirEntryKind, 4203 GetDirEntryKind(*file)); 4204 4205 switch (dirEntryKind) { 4206 case nsIFileKind::ExistsAsDirectory: { 4207 Client::Type clientType; 4208 const bool ok = Client::TypeFromText( 4209 leafName, clientType, fallible); 4210 if (!ok) { 4211 // Unknown directories during initialization are now 4212 // allowed. Just warn if we find them. 4213 UNKNOWN_FILE_WARNING(leafName); 4214 break; 4215 } 4216 4217 if (trackQuota) { 4218 QM_TRY_INSPECT( 4219 const auto& usageInfo, 4220 (*mClients)[clientType]->InitOrigin( 4221 aFullOriginMetadata.mPersistenceType, 4222 aFullOriginMetadata, 4223 /* aCanceled */ Atomic<bool>(false))); 4224 4225 MOZ_ASSERT(!clientUsages[clientType]); 4226 4227 if (usageInfo.TotalUsage()) { 4228 // XXX(Bug 1683863) Until we identify the root cause 4229 // of seemingly converted-from-negative usage 4230 // values, we will just treat them as unset here, 4231 // but log a warning to the browser console. 4232 if (static_cast<int64_t>(*usageInfo.TotalUsage()) >= 4233 0) { 4234 clientUsages[clientType] = usageInfo.TotalUsage(); 4235 } else { 4236 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG) 4237 const nsCOMPtr<nsIConsoleService> console = 4238 do_GetService(NS_CONSOLESERVICE_CONTRACTID); 4239 if (console) { 4240 console->LogStringMessage( 4241 nsString( 4242 u"QuotaManager warning: client "_ns + 4243 leafName + 4244 u" reported negative usage for group "_ns + 4245 NS_ConvertUTF8toUTF16( 4246 aFullOriginMetadata.mGroup) + 4247 u", origin "_ns + 4248 NS_ConvertUTF8toUTF16( 4249 aFullOriginMetadata.mOrigin)) 4250 .get()); 4251 } 4252 #endif 4253 } 4254 } 4255 } else { 4256 QM_TRY(MOZ_TO_RESULT( 4257 (*mClients)[clientType] 4258 ->InitOriginWithoutTracking( 4259 aFullOriginMetadata.mPersistenceType, 4260 aFullOriginMetadata, 4261 /* aCanceled */ Atomic<bool>(false)))); 4262 } 4263 4264 break; 4265 } 4266 4267 case nsIFileKind::ExistsAsFile: 4268 if (IsOriginMetadata(leafName)) { 4269 break; 4270 } 4271 4272 if (IsTempMetadata(leafName)) { 4273 QM_TRY(MOZ_TO_RESULT( 4274 file->Remove(/* recursive */ false))); 4275 4276 break; 4277 } 4278 4279 if (IsOSMetadata(leafName) || IsDotFile(leafName)) { 4280 break; 4281 } 4282 4283 // Unknown files during initialization are now allowed. 4284 // Just warn if we find them. 4285 UNKNOWN_FILE_WARNING(leafName); 4286 // Bug 1595448 will handle the case for unknown files 4287 // like idb, cache, or ls. 4288 break; 4289 4290 case nsIFileKind::DoesNotExist: 4291 // Ignore files that got removed externally while 4292 // iterating. 4293 break; 4294 } 4295 4296 return Ok{}; 4297 }()), 4298 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 4299 4300 return Ok{}; 4301 }), 4302 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc); 4303 4304 return Ok{}; 4305 }())); 4306 4307 #ifdef NIGHTLY_BUILD 4308 if (NS_FAILED(statusKeeper)) { 4309 return statusKeeper; 4310 } 4311 #endif 4312 4313 if (trackQuota) { 4314 const auto usage = std::accumulate( 4315 clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0), 4316 [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) { 4317 return value + clientUsage.valueOr(0); 4318 }); 4319 4320 // XXX Should we log more information, i.e. the whole clientUsages array, in 4321 // case usage is not valid? 4322 4323 QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE); 4324 4325 FullOriginMetadata fullOriginMetadata = aFullOriginMetadata.Clone(); 4326 4327 fullOriginMetadata.mAccessed = false; 4328 4329 fullOriginMetadata.mQuotaVersion = kCurrentQuotaVersion; 4330 fullOriginMetadata.mOriginUsage = usage.value(); 4331 fullOriginMetadata.mClientUsages = clientUsages; 4332 4333 if (StaticPrefs:: 4334 dom_quotaManager_originInitialization_updateOriginMetadata() && 4335 !fullOriginMetadata.Equals(aFullOriginMetadata)) { 4336 // If the pref is enabled and the current metadata differs from what's 4337 // stored, update the metadata file to reflect the most recent state. 4338 // This is essential for ensuring correctness of the L2 quota info cache. 4339 4340 if (fullOriginMetadata.EqualsIgnoringOriginState(aFullOriginMetadata)) { 4341 // If only the OriginStateMetadata (header) differs, we can perform a 4342 // fast, crash-safe in-place update of just the header. 4343 4344 QM_TRY(MOZ_TO_RESULT( 4345 SaveDirectoryMetadataHeader(*aDirectory, fullOriginMetadata))); 4346 4347 } else { 4348 // Otherwise, we fall back to recreating the full metadata file using a 4349 // temporary file and atomic rename, which is slower but safe for 4350 // structural changes. 4351 4352 QM_TRY(MOZ_TO_RESULT( 4353 CreateDirectoryMetadata2(*aDirectory, fullOriginMetadata))); 4354 } 4355 } 4356 4357 InitQuotaForOrigin(fullOriginMetadata); 4358 } 4359 4360 SleepIfEnabled( 4361 StaticPrefs::dom_quotaManager_originInitialization_pauseOnIOThreadMs()); 4362 4363 QM_LOG(("Ending origin initialization for: %s", 4364 aFullOriginMetadata.mOrigin.get())); 4365 4366 return NS_OK; 4367 } 4368 4369 nsresult 4370 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory( 4371 nsIFile* aIndexedDBDir) { 4372 AssertIsOnIOThread(); 4373 MOZ_ASSERT(aIndexedDBDir); 4374 4375 const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult { 4376 bool isDirectory; 4377 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory))); 4378 4379 if (!isDirectory) { 4380 NS_WARNING("indexedDB entry is not a directory!"); 4381 return NS_OK; 4382 } 4383 4384 auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath); 4385 if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) { 4386 return persistentStorageDirOrErr.unwrapErr(); 4387 } 4388 4389 nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap(); 4390 4391 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append( 4392 nsLiteralString(PERSISTENT_DIRECTORY_NAME)))); 4393 4394 bool exists; 4395 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists))); 4396 4397 if (exists) { 4398 QM_WARNING("Deleting old <profile>/indexedDB directory!"); 4399 4400 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true))); 4401 4402 return NS_OK; 4403 } 4404 4405 nsCOMPtr<nsIFile> storageDir; 4406 QM_TRY(MOZ_TO_RESULT( 4407 persistentStorageDir->GetParent(getter_AddRefs(storageDir)))); 4408 4409 // MoveTo() is atomic if the move happens on the same volume which should 4410 // be our case, so even if we crash in the middle of the operation nothing 4411 // breaks next time we try to initialize. 4412 // However there's a theoretical possibility that the indexedDB directory 4413 // is on different volume, but it should be rare enough that we don't have 4414 // to worry about it. 4415 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo( 4416 storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME)))); 4417 4418 return NS_OK; 4419 }; 4420 4421 return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory, 4422 innerFunc); 4423 } 4424 4425 nsresult 4426 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory( 4427 nsIFile* aPersistentStorageDir) { 4428 AssertIsOnIOThread(); 4429 MOZ_ASSERT(aPersistentStorageDir); 4430 4431 const auto innerFunc = [this, 4432 &aPersistentStorageDir](const auto&) -> nsresult { 4433 QM_TRY_INSPECT( 4434 const bool& isDirectory, 4435 MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory)); 4436 4437 if (!isDirectory) { 4438 NS_WARNING("persistent entry is not a directory!"); 4439 return NS_OK; 4440 } 4441 4442 { 4443 QM_TRY_INSPECT(const auto& defaultStorageDir, 4444 QM_NewLocalFile(*mDefaultStoragePath)); 4445 4446 QM_TRY_INSPECT(const bool& exists, 4447 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists)); 4448 4449 if (exists) { 4450 QM_WARNING("Deleting old <profile>/storage/persistent directory!"); 4451 4452 QM_TRY(MOZ_TO_RESULT( 4453 aPersistentStorageDir->Remove(/* aRecursive */ true))); 4454 4455 return NS_OK; 4456 } 4457 } 4458 4459 { 4460 // Create real metadata files for origin directories in persistent 4461 // storage. 4462 auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>( 4463 aPersistentStorageDir); 4464 4465 QM_TRY(MOZ_TO_RESULT(helper->Init())); 4466 4467 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository())); 4468 4469 // Upgrade metadata files for origin directories in temporary storage. 4470 QM_TRY_INSPECT(const auto& temporaryStorageDir, 4471 QM_NewLocalFile(*mTemporaryStoragePath)); 4472 4473 QM_TRY_INSPECT(const bool& exists, 4474 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists)); 4475 4476 if (exists) { 4477 QM_TRY_INSPECT( 4478 const bool& isDirectory, 4479 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory)); 4480 4481 if (!isDirectory) { 4482 NS_WARNING("temporary entry is not a directory!"); 4483 return NS_OK; 4484 } 4485 4486 helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>( 4487 temporaryStorageDir); 4488 4489 QM_TRY(MOZ_TO_RESULT(helper->Init())); 4490 4491 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository())); 4492 } 4493 } 4494 4495 // And finally rename persistent to default. 4496 QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo( 4497 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME)))); 4498 4499 return NS_OK; 4500 }; 4501 4502 return ExecuteInitialization( 4503 Initialization::UpgradeFromPersistentStorageDirectory, innerFunc); 4504 } 4505 4506 template <typename Helper> 4507 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion, 4508 const int32_t aNewVersion, 4509 mozIStorageConnection* aConnection) { 4510 AssertIsOnIOThread(); 4511 MOZ_ASSERT(aNewVersion > aOldVersion); 4512 MOZ_ASSERT(aNewVersion <= kStorageVersion); 4513 MOZ_ASSERT(aConnection); 4514 4515 for (const PersistenceType persistenceType : kAllPersistenceTypesButPrivate) { 4516 QM_TRY_UNWRAP(auto directory, 4517 QM_NewLocalFile(GetStoragePath(persistenceType))); 4518 4519 QM_TRY_INSPECT(const bool& exists, 4520 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists)); 4521 4522 if (!exists) { 4523 continue; 4524 } 4525 4526 RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory); 4527 4528 QM_TRY(MOZ_TO_RESULT(helper->Init())); 4529 4530 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository())); 4531 } 4532 4533 #ifdef DEBUG 4534 { 4535 QM_TRY_INSPECT(const int32_t& storageVersion, 4536 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion)); 4537 4538 MOZ_ASSERT(storageVersion == aOldVersion); 4539 } 4540 #endif 4541 4542 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion))); 4543 4544 return NS_OK; 4545 } 4546 4547 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0( 4548 mozIStorageConnection* aConnection) { 4549 AssertIsOnIOThread(); 4550 MOZ_ASSERT(aConnection); 4551 4552 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult { 4553 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>( 4554 0, MakeStorageVersion(1, 0), aConnection))); 4555 4556 return NS_OK; 4557 }; 4558 4559 return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0, 4560 innerFunc); 4561 } 4562 4563 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0( 4564 mozIStorageConnection* aConnection) { 4565 AssertIsOnIOThread(); 4566 MOZ_ASSERT(aConnection); 4567 4568 // The upgrade consists of a number of logically distinct bugs that 4569 // intentionally got fixed at the same time to trigger just one major 4570 // version bump. 4571 // 4572 // 4573 // Morgue directory cleanup 4574 // [Feature/Bug]: 4575 // The original bug that added "on demand" morgue cleanup is 1165119. 4576 // 4577 // [Mutations]: 4578 // Morgue directories are removed from all origin directories during the 4579 // upgrade process. Origin initialization and usage calculation doesn't try 4580 // to remove morgue directories anymore. 4581 // 4582 // [Downgrade-incompatible changes]: 4583 // Morgue directories can reappear if user runs an already upgraded profile 4584 // in an older version of Firefox. Morgue directories then prevent current 4585 // Firefox from initializing and using the storage. 4586 // 4587 // 4588 // App data removal 4589 // [Feature/Bug]: 4590 // The bug that removes isApp flags is 1311057. 4591 // 4592 // [Mutations]: 4593 // Origin directories with appIds are removed during the upgrade process. 4594 // 4595 // [Downgrade-incompatible changes]: 4596 // Origin directories with appIds can reappear if user runs an already 4597 // upgraded profile in an older version of Firefox. Origin directories with 4598 // appIds don't prevent current Firefox from initializing and using the 4599 // storage, but they wouldn't ever be removed again, potentially causing 4600 // problems once appId is removed from origin attributes. 4601 // 4602 // 4603 // Strip obsolete origin attributes 4604 // [Feature/Bug]: 4605 // The bug that strips obsolete origin attributes is 1314361. 4606 // 4607 // [Mutations]: 4608 // Origin directories with obsolete origin attributes are renamed and their 4609 // metadata files are updated during the upgrade process. 4610 // 4611 // [Downgrade-incompatible changes]: 4612 // Origin directories with obsolete origin attributes can reappear if user 4613 // runs an already upgraded profile in an older version of Firefox. Origin 4614 // directories with obsolete origin attributes don't prevent current Firefox 4615 // from initializing and using the storage, but they wouldn't ever be upgraded 4616 // again, potentially causing problems in future. 4617 // 4618 // 4619 // File manager directory renaming (client specific) 4620 // [Feature/Bug]: 4621 // The original bug that added "on demand" file manager directory renaming is 4622 // 1056939. 4623 // 4624 // [Mutations]: 4625 // All file manager directories are renamed to contain the ".files" suffix. 4626 // 4627 // [Downgrade-incompatible changes]: 4628 // File manager directories with the ".files" suffix prevent older versions of 4629 // Firefox from initializing and using the storage. 4630 // File manager directories without the ".files" suffix can appear if user 4631 // runs an already upgraded profile in an older version of Firefox. File 4632 // manager directories without the ".files" suffix then prevent current 4633 // Firefox from initializing and using the storage. 4634 4635 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult { 4636 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>( 4637 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection))); 4638 4639 return NS_OK; 4640 }; 4641 4642 return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0, 4643 innerFunc); 4644 } 4645 4646 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1( 4647 mozIStorageConnection* aConnection) { 4648 AssertIsOnIOThread(); 4649 MOZ_ASSERT(aConnection); 4650 4651 // The upgrade is mainly to create a directory padding file in DOM Cache 4652 // directory to record the overall padding size of an origin. 4653 4654 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult { 4655 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>( 4656 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection))); 4657 4658 return NS_OK; 4659 }; 4660 4661 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1, 4662 innerFunc); 4663 } 4664 4665 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2( 4666 mozIStorageConnection* aConnection) { 4667 AssertIsOnIOThread(); 4668 MOZ_ASSERT(aConnection); 4669 4670 // The upgrade is mainly to clean obsolete origins in the repositoies, remove 4671 // asmjs client, and ".tmp" file in the idb folers. 4672 4673 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult { 4674 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>( 4675 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection))); 4676 4677 return NS_OK; 4678 }; 4679 4680 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2, 4681 innerFunc); 4682 } 4683 4684 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3( 4685 mozIStorageConnection* aConnection) { 4686 AssertIsOnIOThread(); 4687 MOZ_ASSERT(aConnection); 4688 4689 const auto innerFunc = [&aConnection](const auto&) -> nsresult { 4690 // Table `database` 4691 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 4692 nsLiteralCString("CREATE TABLE database" 4693 "( cache_version INTEGER NOT NULL DEFAULT 0" 4694 ");")))); 4695 4696 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL( 4697 nsLiteralCString("INSERT INTO database (cache_version) " 4698 "VALUES (0)")))); 4699 4700 #ifdef DEBUG 4701 { 4702 QM_TRY_INSPECT( 4703 const int32_t& storageVersion, 4704 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion)); 4705 4706 MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2)); 4707 } 4708 #endif 4709 4710 QM_TRY( 4711 MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3)))); 4712 4713 return NS_OK; 4714 }; 4715 4716 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3, 4717 innerFunc); 4718 } 4719 4720 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive( 4721 nsIFile& aLsArchiveFile) { 4722 AssertIsOnIOThread(); 4723 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled()); 4724 4725 QM_TRY_INSPECT(const bool& exists, 4726 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 4727 4728 if (!exists) { 4729 // If the ls archive doesn't exist then ls directories can't exist either. 4730 return NS_OK; 4731 } 4732 4733 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories())); 4734 4735 InvalidateQuotaCache(); 4736 4737 // Finally remove the ls archive, so we don't have to check all origin 4738 // directories next time this method is called. 4739 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false))); 4740 4741 return NS_OK; 4742 } 4743 4744 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() { 4745 AssertIsOnIOThread(); 4746 4747 QM_TRY_INSPECT(const auto& defaultStorageDir, 4748 QM_NewLocalFile(*mDefaultStoragePath)); 4749 4750 QM_TRY_INSPECT(const bool& exists, 4751 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists)); 4752 4753 if (!exists) { 4754 return NS_OK; 4755 } 4756 4757 QM_TRY(CollectEachFile( 4758 *defaultStorageDir, 4759 [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> { 4760 #ifdef DEBUG 4761 { 4762 QM_TRY_INSPECT(const bool& exists, 4763 MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists)); 4764 MOZ_ASSERT(exists); 4765 } 4766 #endif 4767 4768 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir)); 4769 4770 switch (dirEntryKind) { 4771 case nsIFileKind::ExistsAsDirectory: { 4772 QM_TRY_INSPECT( 4773 const auto& lsDir, 4774 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING( 4775 LS_DIRECTORY_NAME))); 4776 4777 { 4778 QM_TRY_INSPECT(const bool& exists, 4779 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists)); 4780 4781 if (!exists) { 4782 return Ok{}; 4783 } 4784 } 4785 4786 { 4787 QM_TRY_INSPECT(const bool& isDirectory, 4788 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory)); 4789 4790 if (!isDirectory) { 4791 QM_WARNING("ls entry is not a directory!"); 4792 4793 return Ok{}; 4794 } 4795 } 4796 4797 nsString path; 4798 QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path))); 4799 4800 QM_WARNING("Deleting %s directory!", 4801 NS_ConvertUTF16toUTF8(path).get()); 4802 4803 QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true))); 4804 4805 break; 4806 } 4807 4808 case nsIFileKind::ExistsAsFile: { 4809 QM_TRY_INSPECT(const auto& leafName, 4810 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 4811 nsAutoString, originDir, GetLeafName)); 4812 4813 // Unknown files during upgrade are allowed. Just warn if we find 4814 // them. 4815 if (!IsOSMetadata(leafName)) { 4816 UNKNOWN_FILE_WARNING(leafName); 4817 } 4818 4819 break; 4820 } 4821 4822 case nsIFileKind::DoesNotExist: 4823 // Ignore files that got removed externally while iterating. 4824 break; 4825 } 4826 return Ok{}; 4827 })); 4828 4829 return NS_OK; 4830 } 4831 4832 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore( 4833 nsIFile& aLsArchiveFile) const { 4834 AssertIsOnIOThread(); 4835 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 4836 4837 #ifdef DEBUG 4838 { 4839 QM_TRY_INSPECT(const bool& exists, 4840 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 4841 MOZ_ASSERT(!exists); 4842 } 4843 #endif 4844 4845 // Get the storage service first, we will need it at multiple places. 4846 QM_TRY_INSPECT(const auto& ss, 4847 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 4848 MOZ_SELECT_OVERLOAD(do_GetService), 4849 MOZ_STORAGE_SERVICE_CONTRACTID)); 4850 4851 // Get the web apps store file. 4852 QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath)); 4853 4854 QM_TRY(MOZ_TO_RESULT( 4855 webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME)))); 4856 4857 // Now check if the web apps store is useable. 4858 QM_TRY_INSPECT(const auto& connection, 4859 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss)); 4860 4861 if (connection) { 4862 // Find out the journal mode. 4863 QM_TRY_INSPECT(const auto& stmt, 4864 CreateAndExecuteSingleStepStatement( 4865 *connection, "PRAGMA journal_mode;"_ns)); 4866 4867 QM_TRY_INSPECT(const auto& journalMode, 4868 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt, 4869 GetUTF8String, 0)); 4870 4871 QM_TRY(MOZ_TO_RESULT(stmt->Finalize())); 4872 4873 if (journalMode.EqualsLiteral("wal")) { 4874 // We don't copy the WAL file, so make sure the old database is fully 4875 // checkpointed. 4876 QM_TRY(MOZ_TO_RESULT( 4877 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns))); 4878 } 4879 4880 // Explicitely close the connection before the old database is copied. 4881 QM_TRY(MOZ_TO_RESULT(connection->Close())); 4882 4883 // Copy the old database. The database is copied from 4884 // <profile>/webappsstore.sqlite to 4885 // <profile>/storage/ls-archive-tmp.sqlite 4886 // We use a "-tmp" postfix since we are not done yet. 4887 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath)); 4888 4889 QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo( 4890 storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME)))); 4891 4892 QM_TRY_INSPECT(const auto& lsArchiveTmpFile, 4893 GetLocalStorageArchiveTmpFile(*mStoragePath)); 4894 4895 if (journalMode.EqualsLiteral("wal")) { 4896 QM_TRY_INSPECT( 4897 const auto& lsArchiveTmpConnection, 4898 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 4899 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 4900 lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT)); 4901 4902 // The archive will only be used for lazy data migration. There won't be 4903 // any concurrent readers and writers that could benefit from Write-Ahead 4904 // Logging. So switch to a standard rollback journal. The standard 4905 // rollback journal also provides atomicity across multiple attached 4906 // databases which is import for the lazy data migration to work safely. 4907 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL( 4908 "PRAGMA journal_mode = DELETE;"_ns))); 4909 4910 // Close the connection explicitly. We are going to rename the file below. 4911 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close())); 4912 } 4913 4914 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite 4915 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo( 4916 nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME)))); 4917 4918 return Ok{}; 4919 } 4920 4921 // If webappsstore database is not useable, just create an empty archive. 4922 // XXX The code below should be removed and the caller should call us only 4923 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be 4924 // reworked to propagate database corruption instead of returning null 4925 // connection. 4926 // So, if there's no webappsstore.sqlite 4927 // MaybeCreateOrUpgradeLocalStorageArchive will call 4928 // CreateEmptyLocalStorageArchive instead of 4929 // CopyLocalStorageArchiveFromWebAppsStore. 4930 // If there's any corruption detected during 4931 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like 4932 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection) 4933 // EnsureStorageIsInitializedInternal will fallback to 4934 // CreateEmptyLocalStorageArchive. 4935 4936 // Ensure the storage directory actually exists. 4937 QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath)); 4938 4939 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory)); 4940 4941 (void)created; 4942 4943 QM_TRY_UNWRAP(auto lsArchiveConnection, 4944 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 4945 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 4946 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT)); 4947 4948 QM_TRY(MOZ_TO_RESULT( 4949 StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection))); 4950 4951 return Ok{}; 4952 } 4953 4954 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 4955 QuotaManager::CreateLocalStorageArchiveConnection( 4956 nsIFile& aLsArchiveFile) const { 4957 AssertIsOnIOThread(); 4958 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 4959 4960 #ifdef DEBUG 4961 { 4962 QM_TRY_INSPECT(const bool& exists, 4963 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 4964 MOZ_ASSERT(exists); 4965 } 4966 #endif 4967 4968 QM_TRY_INSPECT(const bool& isDirectory, 4969 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory)); 4970 4971 // A directory with the name of the archive file is treated as corruption 4972 // (similarly as wrong content of the file). 4973 QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED)); 4974 4975 QM_TRY_INSPECT(const auto& ss, 4976 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 4977 MOZ_SELECT_OVERLOAD(do_GetService), 4978 MOZ_STORAGE_SERVICE_CONTRACTID)); 4979 4980 // This may return NS_ERROR_FILE_CORRUPTED too. 4981 QM_TRY_UNWRAP(auto connection, 4982 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 4983 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 4984 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT)); 4985 4986 // The legacy LS implementation removes the database and creates an empty one 4987 // when the schema can't be updated. The same effect can be achieved here by 4988 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by 4989 // sub test case 3 of dom/localstorage/test/unit/test_archive.js 4990 QM_TRY( 4991 MOZ_TO_RESULT(StorageDBUpdater::Update(connection)) 4992 .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; })); 4993 4994 return connection; 4995 } 4996 4997 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 4998 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore( 4999 nsIFile& aLsArchiveFile) { 5000 AssertIsOnIOThread(); 5001 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 5002 5003 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories())); 5004 5005 #ifdef DEBUG 5006 { 5007 QM_TRY_INSPECT(const bool& exists, 5008 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 5009 5010 MOZ_ASSERT(exists); 5011 } 5012 #endif 5013 5014 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false))); 5015 5016 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile)); 5017 5018 QM_TRY_UNWRAP(auto connection, 5019 CreateLocalStorageArchiveConnection(aLsArchiveFile)); 5020 5021 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection))); 5022 5023 return connection; 5024 } 5025 5026 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 5027 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) { 5028 AssertIsOnIOThread(); 5029 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 5030 5031 QM_TRY_UNWRAP(auto connection, 5032 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile)); 5033 5034 QM_TRY(MOZ_TO_RESULT( 5035 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion))); 5036 5037 return connection; 5038 } 5039 5040 Result<nsCOMPtr<mozIStorageConnection>, nsresult> 5041 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4( 5042 nsIFile& aLsArchiveFile) { 5043 AssertIsOnIOThread(); 5044 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 5045 5046 QM_TRY_UNWRAP(auto connection, 5047 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile)); 5048 5049 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4))); 5050 5051 return connection; 5052 } 5053 5054 /* 5055 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5( 5056 nsCOMPtr<mozIStorageConnection>& aConnection) { 5057 AssertIsOnIOThread(); 5058 MOZ_ASSERT(CachedNextGenLocalStorageEnabled()); 5059 5060 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5); 5061 if (NS_WARN_IF(NS_FAILED(rv))) { 5062 return rv; 5063 } 5064 5065 return NS_OK; 5066 } 5067 */ 5068 5069 #ifdef DEBUG 5070 5071 void QuotaManager::AssertStorageIsInitializedInternal() const { 5072 AssertIsOnIOThread(); 5073 MOZ_ASSERT(IsStorageInitializedInternal()); 5074 } 5075 5076 #endif // DEBUG 5077 5078 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory( 5079 nsIFile& aStorageFile) { 5080 AssertIsOnIOThread(); 5081 5082 QM_TRY_INSPECT(const auto& storageFileExists, 5083 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists)); 5084 5085 if (!storageFileExists) { 5086 QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath)); 5087 5088 QM_TRY_INSPECT(const auto& indexedDBDirExists, 5089 MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists)); 5090 5091 if (indexedDBDirExists) { 5092 QM_TRY(MOZ_TO_RESULT( 5093 UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory( 5094 indexedDBDir))); 5095 } 5096 5097 QM_TRY_INSPECT(const auto& persistentStorageDir, 5098 QM_NewLocalFile(*mStoragePath)); 5099 5100 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append( 5101 nsLiteralString(PERSISTENT_DIRECTORY_NAME)))); 5102 5103 QM_TRY_INSPECT(const auto& persistentStorageDirExists, 5104 MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists)); 5105 5106 if (persistentStorageDirExists) { 5107 QM_TRY(MOZ_TO_RESULT( 5108 UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory( 5109 persistentStorageDir))); 5110 } 5111 } 5112 5113 return NS_OK; 5114 } 5115 5116 nsresult QuotaManager::MaybeCreateOrUpgradeStorage( 5117 mozIStorageConnection& aConnection) { 5118 AssertIsOnIOThread(); 5119 5120 QM_TRY_UNWRAP(auto storageVersion, 5121 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion)); 5122 5123 // Hacky downgrade logic! 5124 // If we see major.minor of 3.0, downgrade it to be 2.1. 5125 if (storageVersion == kHackyPreDowngradeStorageVersion) { 5126 storageVersion = kHackyPostDowngradeStorageVersion; 5127 QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)), 5128 QM_PROPAGATE, 5129 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); }); 5130 } 5131 5132 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion), 5133 NS_ERROR_FAILURE, [](const auto&) { 5134 NS_WARNING("Unable to initialize storage, version is too high!"); 5135 }); 5136 5137 if (storageVersion < kStorageVersion) { 5138 const bool newDatabase = !storageVersion; 5139 5140 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath)); 5141 5142 QM_TRY_INSPECT(const auto& storageDirExists, 5143 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists)); 5144 5145 const bool newDirectory = !storageDirExists; 5146 5147 if (newDatabase) { 5148 // Set the page size first. 5149 if (kSQLitePageSizeOverride) { 5150 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString( 5151 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)))); 5152 } 5153 } 5154 5155 mozStorageTransaction transaction( 5156 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); 5157 5158 QM_TRY(MOZ_TO_RESULT(transaction.Start())); 5159 5160 // An upgrade method can upgrade the database, the storage or both. 5161 // The upgrade loop below can only be avoided when there's no database and 5162 // no storage yet (e.g. new profile). 5163 if (newDatabase && newDirectory) { 5164 QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection))); 5165 5166 #ifdef DEBUG 5167 { 5168 QM_TRY_INSPECT( 5169 const int32_t& storageVersion, 5170 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion), 5171 QM_ASSERT_UNREACHABLE); 5172 MOZ_ASSERT(storageVersion == kStorageVersion); 5173 } 5174 #endif 5175 5176 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL( 5177 nsLiteralCString("INSERT INTO database (cache_version) " 5178 "VALUES (0)")))); 5179 } else { 5180 // This logic needs to change next time we change the storage! 5181 static_assert(kStorageVersion == int32_t((2 << 16) + 3), 5182 "Upgrade function needed due to storage version increase."); 5183 5184 while (storageVersion != kStorageVersion) { 5185 if (storageVersion == 0) { 5186 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection))); 5187 } else if (storageVersion == MakeStorageVersion(1, 0)) { 5188 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection))); 5189 } else if (storageVersion == MakeStorageVersion(2, 0)) { 5190 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection))); 5191 } else if (storageVersion == MakeStorageVersion(2, 1)) { 5192 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection))); 5193 } else if (storageVersion == MakeStorageVersion(2, 2)) { 5194 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection))); 5195 } else { 5196 QM_FAIL(NS_ERROR_FAILURE, []() { 5197 NS_WARNING( 5198 "Unable to initialize storage, no upgrade path is " 5199 "available!"); 5200 }); 5201 } 5202 5203 QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER( 5204 aConnection, GetSchemaVersion)); 5205 } 5206 5207 MOZ_ASSERT(storageVersion == kStorageVersion); 5208 } 5209 5210 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 5211 } 5212 5213 return NS_OK; 5214 } 5215 5216 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() { 5217 AssertIsOnIOThread(); 5218 5219 QM_TRY_INSPECT( 5220 const auto& lsArchiveTmpFile, 5221 QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath))); 5222 5223 QM_TRY_INSPECT(const bool& exists, 5224 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists)); 5225 5226 if (exists) { 5227 QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false))); 5228 } 5229 5230 return Ok{}; 5231 } 5232 5233 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive( 5234 nsIFile& aLsArchiveFile) { 5235 AssertIsOnIOThread(); 5236 5237 QM_TRY_INSPECT( 5238 const bool& lsArchiveFileExisted, 5239 ([this, &aLsArchiveFile]() -> Result<bool, nsresult> { 5240 QM_TRY_INSPECT(const bool& exists, 5241 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 5242 5243 if (!exists) { 5244 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile)); 5245 } 5246 5247 return exists; 5248 }())); 5249 5250 QM_TRY_UNWRAP(auto connection, 5251 CreateLocalStorageArchiveConnection(aLsArchiveFile)); 5252 5253 QM_TRY_INSPECT(const auto& initialized, 5254 IsLocalStorageArchiveInitialized(*connection)); 5255 5256 if (!initialized) { 5257 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection))); 5258 } 5259 5260 QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection)); 5261 5262 if (version > kLocalStorageArchiveVersion) { 5263 // Close local storage archive connection. We are going to remove underlying 5264 // file. 5265 QM_TRY(MOZ_TO_RESULT(connection->Close())); 5266 5267 // This will wipe the archive and any migrated data and recopy the archive 5268 // from webappsstore.sqlite. 5269 QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile)); 5270 5271 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection)); 5272 5273 MOZ_ASSERT(version == kLocalStorageArchiveVersion); 5274 } else if (version != kLocalStorageArchiveVersion) { 5275 // The version can be zero either when the archive didn't exist or it did 5276 // exist, but the archive was created without any version information. 5277 // We don't need to do any upgrades only if it didn't exist because existing 5278 // archives without version information must be recopied to really fix bug 5279 // 1542104. See also bug 1546305 which introduced archive versions. 5280 if (!lsArchiveFileExisted) { 5281 MOZ_ASSERT(version == 0); 5282 5283 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion( 5284 connection, kLocalStorageArchiveVersion))); 5285 } else { 5286 static_assert(kLocalStorageArchiveVersion == 4, 5287 "Upgrade function needed due to LocalStorage archive " 5288 "version increase."); 5289 5290 while (version != kLocalStorageArchiveVersion) { 5291 if (version < 4) { 5292 // Close local storage archive connection. We are going to remove 5293 // underlying file. 5294 QM_TRY(MOZ_TO_RESULT(connection->Close())); 5295 5296 // This won't do an "upgrade" in a normal sense. It will wipe the 5297 // archive and any migrated data and recopy the archive from 5298 // webappsstore.sqlite 5299 QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4( 5300 aLsArchiveFile)); 5301 } /* else if (version == 4) { 5302 QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection))); 5303 } */ 5304 else { 5305 QM_FAIL(Err(NS_ERROR_FAILURE), []() { 5306 QM_WARNING( 5307 "Unable to initialize LocalStorage archive, no upgrade path " 5308 "is available!"); 5309 }); 5310 } 5311 5312 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection)); 5313 } 5314 5315 MOZ_ASSERT(version == kLocalStorageArchiveVersion); 5316 } 5317 } 5318 5319 // At this point, we have finished initializing the local storage archive, and 5320 // can continue storage initialization. We don't know though if the actual 5321 // data in the archive file is readable. We can't do a PRAGMA integrity_check 5322 // here though, because that would be too heavyweight. 5323 5324 return Ok{}; 5325 } 5326 5327 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive( 5328 nsIFile& aLsArchiveFile) const { 5329 AssertIsOnIOThread(); 5330 5331 QM_TRY_INSPECT(const bool& exists, 5332 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists)); 5333 5334 // If it exists, remove it. It might be a directory, so remove it recursively. 5335 if (exists) { 5336 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true))); 5337 5338 // XXX If we crash right here, the next session will copy the archive from 5339 // webappsstore.sqlite again! 5340 // XXX Create a marker file before removing the archive which can be 5341 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty 5342 // archive instead of recopying it from webapppstore.sqlite (in other 5343 // words, finishing what was started here). 5344 } 5345 5346 QM_TRY_INSPECT(const auto& ss, 5347 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 5348 MOZ_SELECT_OVERLOAD(do_GetService), 5349 MOZ_STORAGE_SERVICE_CONTRACTID)); 5350 5351 QM_TRY_UNWRAP(const auto connection, 5352 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 5353 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 5354 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT)); 5355 5356 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection))); 5357 5358 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection))); 5359 5360 QM_TRY(MOZ_TO_RESULT( 5361 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion))); 5362 5363 return Ok{}; 5364 } 5365 5366 RefPtr<BoolPromise> QuotaManager::InitializeStorage() { 5367 AssertIsOnOwningThread(); 5368 5369 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 5370 PersistenceScope::CreateFromNull(), OriginScope::FromNull(), 5371 ClientStorageScope::CreateFromNull(), 5372 /* aExclusive */ false); 5373 5374 auto prepareInfo = directoryLock->Prepare(); 5375 5376 // If storage is initialized but there's a clear storage or shutdown storage 5377 // operation already scheduled, we can't immediately resolve the promise and 5378 // return from the function because the clear and shutdown storage operation 5379 // uninitializes storage. 5380 if (mStorageInitialized && 5381 !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) { 5382 return BoolPromise::CreateAndResolve(true, __func__); 5383 } 5384 5385 return directoryLock->Acquire(std::move(prepareInfo)) 5386 ->Then(GetCurrentSerialEventTarget(), __func__, 5387 [self = RefPtr(this), directoryLock]( 5388 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 5389 if (aValue.IsReject()) { 5390 return BoolPromise::CreateAndReject(aValue.RejectValue(), 5391 __func__); 5392 } 5393 5394 return self->InitializeStorage(std::move(directoryLock)); 5395 }); 5396 } 5397 5398 RefPtr<BoolPromise> QuotaManager::InitializeStorage( 5399 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 5400 AssertIsOnOwningThread(); 5401 MOZ_ASSERT(aDirectoryLock); 5402 MOZ_ASSERT(aDirectoryLock->Acquired()); 5403 5404 // If storage is initialized and the directory lock for the initialize 5405 // storage operation is acquired, we can immediately resolve the promise and 5406 // return from the function because there can't be a clear storage or 5407 // shutdown storage operation which would uninitialize storage. 5408 if (mStorageInitialized) { 5409 DropDirectoryLock(aDirectoryLock); 5410 5411 return BoolPromise::CreateAndResolve(true, __func__); 5412 } 5413 5414 auto initializeStorageOp = 5415 CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock)); 5416 5417 RegisterNormalOriginOp(*initializeStorageOp); 5418 5419 initializeStorageOp->RunImmediately(); 5420 5421 return initializeStorageOp->OnResults()->Then( 5422 GetCurrentSerialEventTarget(), __func__, 5423 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 5424 if (aValue.IsReject()) { 5425 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 5426 } 5427 5428 self->mStorageInitialized = true; 5429 5430 return BoolPromise::CreateAndResolve(true, __func__); 5431 }); 5432 } 5433 5434 RefPtr<BoolPromise> QuotaManager::StorageInitialized() { 5435 AssertIsOnOwningThread(); 5436 5437 auto storageInitializedOp = 5438 CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this)); 5439 5440 RegisterNormalOriginOp(*storageInitializedOp); 5441 5442 storageInitializedOp->RunImmediately(); 5443 5444 return storageInitializedOp->OnResults(); 5445 } 5446 5447 nsresult QuotaManager::EnsureStorageIsInitializedInternal() { 5448 GECKO_TRACE_SCOPE("dom::quota", 5449 "QuotaManager::EnsureStorageIsInitializedInternal"); 5450 5451 DiagnosticAssertIsOnIOThread(); 5452 5453 const auto innerFunc = 5454 [&](const auto& firstInitializationAttempt) -> nsresult { 5455 if (mStorageConnection) { 5456 MOZ_ASSERT(firstInitializationAttempt.Recorded()); 5457 return NS_OK; 5458 } 5459 5460 QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath)); 5461 QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix))); 5462 5463 QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile))); 5464 5465 QM_TRY_INSPECT(const auto& ss, 5466 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, 5467 MOZ_SELECT_OVERLOAD(do_GetService), 5468 MOZ_STORAGE_SERVICE_CONTRACTID)); 5469 5470 QM_TRY_UNWRAP( 5471 auto connection, 5472 QM_OR_ELSE_WARN_IF( 5473 // Expression. 5474 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 5475 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase, 5476 storageFile, mozIStorageService::CONNECTION_DEFAULT), 5477 // Predicate. 5478 IsDatabaseCorruptionError, 5479 // Fallback. 5480 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>)); 5481 5482 if (!connection) { 5483 // Nuke the database file. 5484 QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false))); 5485 5486 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 5487 nsCOMPtr<mozIStorageConnection>, ss, 5488 OpenUnsharedDatabase, storageFile, 5489 mozIStorageService::CONNECTION_DEFAULT)); 5490 } 5491 5492 // We want extra durability for this important file. 5493 QM_TRY(MOZ_TO_RESULT( 5494 connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns))); 5495 5496 // Check to make sure that the storage version is correct. 5497 QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection))); 5498 5499 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile()); 5500 5501 QM_TRY_INSPECT(const auto& lsArchiveFile, 5502 GetLocalStorageArchiveFile(*mStoragePath)); 5503 5504 if (CachedNextGenLocalStorageEnabled()) { 5505 QM_TRY(QM_OR_ELSE_WARN_IF( 5506 // Expression. 5507 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile), 5508 // Predicate. 5509 IsDatabaseCorruptionError, 5510 // Fallback. 5511 ([&](const nsresult rv) -> Result<Ok, nsresult> { 5512 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile)); 5513 }))); 5514 } else { 5515 QM_TRY( 5516 MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile))); 5517 } 5518 5519 QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection)); 5520 5521 if (mCacheUsable && gInvalidateQuotaCache) { 5522 QM_TRY(InvalidateCache(*connection)); 5523 5524 gInvalidateQuotaCache = false; 5525 } 5526 5527 uint32_t pauseOnIOThreadMs = 5528 StaticPrefs::dom_quotaManager_storageInitialization_pauseOnIOThreadMs(); 5529 if (pauseOnIOThreadMs > 0) { 5530 PR_Sleep(PR_MillisecondsToInterval(pauseOnIOThreadMs)); 5531 } 5532 5533 mStorageConnection = std::move(connection); 5534 5535 return NS_OK; 5536 }; 5537 5538 return ExecuteInitialization( 5539 Initialization::Storage, 5540 "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc); 5541 } 5542 5543 RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() { 5544 AssertIsOnOwningThread(); 5545 5546 auto temporaryStorageInitializedOp = 5547 CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this)); 5548 5549 RegisterNormalOriginOp(*temporaryStorageInitializedOp); 5550 5551 temporaryStorageInitializedOp->RunImmediately(); 5552 5553 return temporaryStorageInitializedOp->OnResults(); 5554 } 5555 5556 RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory( 5557 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope, 5558 const ClientStorageScope& aClientStorageScope, bool aExclusive, 5559 bool aInitializeOrigins, DirectoryLockCategory aCategory, 5560 Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) { 5561 AssertIsOnOwningThread(); 5562 5563 nsTArray<RefPtr<BoolPromise>> promises; 5564 5565 // Directory locks for specific initializations can be null, indicating that 5566 // either the initialization should not occur or it has already been 5567 // completed. 5568 5569 RefPtr<UniversalDirectoryLock> storageDirectoryLock = 5570 CreateDirectoryLockForInitialization( 5571 *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(), 5572 ClientStorageScope::CreateFromNull(), mStorageInitialized, 5573 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5574 MakeBackInserter(promises)); 5575 5576 RefPtr<UniversalDirectoryLock> persistentStorageDirectoryLock; 5577 5578 RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock; 5579 5580 if (aInitializeOrigins) { 5581 if (MatchesPersistentPersistenceScope(aPersistenceScope)) { 5582 persistentStorageDirectoryLock = CreateDirectoryLockForInitialization( 5583 *this, PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT), 5584 OriginScope::FromNull(), ClientStorageScope::CreateFromNull(), 5585 mPersistentStorageInitialized, 5586 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5587 MakeBackInserter(promises)); 5588 } 5589 5590 // We match all best effort persistence types, but the persistence scope is 5591 // created only for the temporary and default persistence type because the 5592 // repository for the private persistence type is never initialized as part 5593 // of temporary initialization. However, some other steps of the temporary 5594 // storage initialization need to be done even for the private persistence 5595 // type. For example, the initialization of mTemporaryStorageLimit. 5596 if (MatchesBestEffortPersistenceScope(aPersistenceScope)) { 5597 temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization( 5598 *this, 5599 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY, 5600 PERSISTENCE_TYPE_DEFAULT), 5601 OriginScope::FromNull(), ClientStorageScope::CreateFromNull(), 5602 mTemporaryStorageInitialized, 5603 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5604 MakeBackInserter(promises)); 5605 } 5606 } 5607 5608 RefPtr<UniversalDirectoryLock> universalDirectoryLock = 5609 CreateDirectoryLockInternal(aPersistenceScope, aOriginScope, 5610 aClientStorageScope, aExclusive, aCategory); 5611 5612 RefPtr<BoolPromise> universalDirectoryLockPromise = 5613 universalDirectoryLock->Acquire(); 5614 5615 if (aPendingDirectoryLockOut.isSome()) { 5616 aPendingDirectoryLockOut.ref() = universalDirectoryLock; 5617 } 5618 5619 return BoolPromise::All(GetCurrentSerialEventTarget(), promises) 5620 ->Then( 5621 GetCurrentSerialEventTarget(), __func__, 5622 [](const CopyableTArray<bool>& aResolveValues) { 5623 return BoolPromise::CreateAndResolve(true, __func__); 5624 }, 5625 [](nsresult aRejectValue) { 5626 return BoolPromise::CreateAndReject(aRejectValue, __func__); 5627 }) 5628 ->Then(GetCurrentSerialEventTarget(), __func__, 5629 MaybeInitialize(std::move(storageDirectoryLock), this, 5630 &QuotaManager::InitializeStorage)) 5631 ->Then(GetCurrentSerialEventTarget(), __func__, 5632 MaybeInitialize(std::move(persistentStorageDirectoryLock), this, 5633 &QuotaManager::InitializePersistentStorage)) 5634 ->Then(GetCurrentSerialEventTarget(), __func__, 5635 MaybeInitialize(std::move(temporaryStorageDirectoryLock), this, 5636 &QuotaManager::InitializeTemporaryStorage)) 5637 ->Then(GetCurrentSerialEventTarget(), __func__, 5638 [universalDirectoryLockPromise = 5639 std::move(universalDirectoryLockPromise)]( 5640 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 5641 if (aValue.IsReject()) { 5642 return BoolPromise::CreateAndReject(aValue.RejectValue(), 5643 __func__); 5644 } 5645 5646 return std::move(universalDirectoryLockPromise); 5647 }) 5648 ->Then(GetCurrentSerialEventTarget(), __func__, 5649 [universalDirectoryLock = std::move(universalDirectoryLock)]( 5650 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 5651 if (aValue.IsReject()) { 5652 DropDirectoryLockIfNotDropped(universalDirectoryLock); 5653 5654 return UniversalDirectoryLockPromise::CreateAndReject( 5655 aValue.RejectValue(), __func__); 5656 } 5657 5658 return UniversalDirectoryLockPromise::CreateAndResolve( 5659 std::move(universalDirectoryLock), __func__); 5660 }); 5661 } 5662 5663 RefPtr<QuotaManager::ClientDirectoryLockHandlePromise> 5664 QuotaManager::OpenClientDirectory( 5665 const ClientMetadata& aClientMetadata, bool aInitializeOrigin, 5666 bool aCreateIfNonExistent, 5667 Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) { 5668 AssertIsOnOwningThread(); 5669 5670 RefPtr<ClientDirectoryLockHandlePromise> promise = 5671 OpenClientDirectoryImpl(aClientMetadata, aInitializeOrigin, 5672 aCreateIfNonExistent, aPendingDirectoryLockOut); 5673 5674 NotifyClientDirectoryOpeningStarted(*this); 5675 5676 return promise; 5677 } 5678 5679 RefPtr<QuotaManager::ClientDirectoryLockHandlePromise> 5680 QuotaManager::OpenClientDirectoryImpl( 5681 const ClientMetadata& aClientMetadata, bool aInitializeOrigin, 5682 bool aCreateIfNonExistent, 5683 Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) { 5684 AssertIsOnOwningThread(); 5685 5686 const auto persistenceType = aClientMetadata.mPersistenceType; 5687 5688 nsTArray<RefPtr<BoolPromise>> promises; 5689 5690 // Directory locks for specific initializations can be null, indicating that 5691 // either the initialization should not occur or it has already been 5692 // completed. 5693 5694 RefPtr<UniversalDirectoryLock> storageDirectoryLock = 5695 CreateDirectoryLockForInitialization( 5696 *this, PersistenceScope::CreateFromNull(), OriginScope::FromNull(), 5697 ClientStorageScope::CreateFromNull(), mStorageInitialized, 5698 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5699 MakeBackInserter(promises)); 5700 5701 RefPtr<UniversalDirectoryLock> temporaryStorageDirectoryLock; 5702 5703 RefPtr<UniversalDirectoryLock> groupDirectoryLock; 5704 5705 if (IsBestEffortPersistenceType(persistenceType)) { 5706 temporaryStorageDirectoryLock = CreateDirectoryLockForInitialization( 5707 *this, 5708 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY, 5709 PERSISTENCE_TYPE_DEFAULT), 5710 OriginScope::FromNull(), ClientStorageScope::CreateFromNull(), 5711 mTemporaryStorageInitialized, 5712 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5713 MakeBackInserter(promises)); 5714 5715 const bool groupInitialized = IsTemporaryGroupInitialized(aClientMetadata); 5716 5717 groupDirectoryLock = CreateDirectoryLockForInitialization( 5718 *this, 5719 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY, 5720 PERSISTENCE_TYPE_DEFAULT), 5721 OriginScope::FromGroup(aClientMetadata.mGroup), 5722 ClientStorageScope::CreateFromNull(), groupInitialized, 5723 MakeBlockedByChecker(kUninitStorageOnlyCategory), 5724 MakeBackInserter(promises)); 5725 } 5726 5727 RefPtr<UniversalDirectoryLock> originDirectoryLock; 5728 5729 if (aInitializeOrigin) { 5730 const bool originInitialized = 5731 persistenceType == PERSISTENCE_TYPE_PERSISTENT 5732 ? IsPersistentOriginInitialized(aClientMetadata) 5733 : IsTemporaryOriginInitialized(aClientMetadata); 5734 5735 originDirectoryLock = CreateDirectoryLockForInitialization( 5736 *this, PersistenceScope::CreateFromValue(persistenceType), 5737 OriginScope::FromOrigin(aClientMetadata), 5738 ClientStorageScope::CreateFromNull(), originInitialized, 5739 MakeBlockedByChecker(kUninitOriginsAndBroaderCategories), 5740 MakeBackInserter(promises)); 5741 } 5742 5743 RefPtr<UniversalDirectoryLock> clientInitDirectoryLock; 5744 5745 const bool clientInitialized = 5746 persistenceType == PERSISTENCE_TYPE_PERSISTENT 5747 ? IsPersistentClientInitialized(aClientMetadata) 5748 : IsTemporaryClientInitialized(aClientMetadata); 5749 5750 clientInitDirectoryLock = CreateDirectoryLockForInitialization( 5751 *this, PersistenceScope::CreateFromValue(persistenceType), 5752 OriginScope::FromOrigin(aClientMetadata), 5753 ClientStorageScope::CreateFromClient(aClientMetadata.mClientType), 5754 clientInitialized, 5755 MakeBlockedByChecker(kUninitClientsAndBroaderCategories), 5756 MakeBackInserter(promises)); 5757 5758 RefPtr<ClientDirectoryLock> clientDirectoryLock = 5759 CreateDirectoryLock(aClientMetadata, /* aExclusive */ false); 5760 5761 promises.AppendElement(clientDirectoryLock->Acquire()); 5762 5763 if (aPendingDirectoryLockOut.isSome()) { 5764 aPendingDirectoryLockOut.ref() = clientDirectoryLock; 5765 } 5766 5767 RefPtr<UniversalDirectoryLock> firstAccessDirectoryLock = 5768 CreateSaveOriginAccessTimeLock(*this, aClientMetadata); 5769 5770 promises.AppendElement(firstAccessDirectoryLock->Acquire()); 5771 5772 RefPtr<UniversalDirectoryLock> lastAccessDirectoryLock = 5773 CreateSaveOriginAccessTimeLock(*this, aClientMetadata); 5774 5775 promises.AppendElement(lastAccessDirectoryLock->Acquire()); 5776 5777 return BoolPromise::All(GetCurrentSerialEventTarget(), promises) 5778 ->Then( 5779 GetCurrentSerialEventTarget(), __func__, 5780 [](const CopyableTArray<bool>& aResolveValues) { 5781 return BoolPromise::CreateAndResolve(true, __func__); 5782 }, 5783 [](nsresult aRejectValue) { 5784 return BoolPromise::CreateAndReject(aRejectValue, __func__); 5785 }) 5786 ->Then(GetCurrentSerialEventTarget(), __func__, 5787 MaybeInitialize(std::move(storageDirectoryLock), this, 5788 &QuotaManager::InitializeStorage)) 5789 ->Then(GetCurrentSerialEventTarget(), __func__, 5790 MaybeInitialize(std::move(temporaryStorageDirectoryLock), this, 5791 &QuotaManager::InitializeTemporaryStorage)) 5792 ->Then( 5793 GetCurrentSerialEventTarget(), __func__, 5794 MaybeInitialize( 5795 std::move(groupDirectoryLock), 5796 [self = RefPtr(this), aClientMetadata]( 5797 RefPtr<UniversalDirectoryLock> groupDirectoryLock) mutable { 5798 return self->InitializeTemporaryGroup( 5799 aClientMetadata, std::move(groupDirectoryLock)); 5800 })) 5801 ->Then( 5802 GetCurrentSerialEventTarget(), __func__, 5803 MaybeInitialize( 5804 std::move(originDirectoryLock), 5805 [self = RefPtr(this), aClientMetadata, aCreateIfNonExistent]( 5806 RefPtr<UniversalDirectoryLock> originDirectoryLock) mutable { 5807 if (aClientMetadata.mPersistenceType == 5808 PERSISTENCE_TYPE_PERSISTENT) { 5809 return self->InitializePersistentOrigin( 5810 aClientMetadata, std::move(originDirectoryLock)); 5811 } 5812 5813 return self->InitializeTemporaryOrigin( 5814 aClientMetadata, aCreateIfNonExistent, 5815 std::move(originDirectoryLock)); 5816 })) 5817 ->Then(GetCurrentSerialEventTarget(), __func__, 5818 MaybeInitialize( 5819 std::move(clientInitDirectoryLock), 5820 [self = RefPtr(this), aClientMetadata, 5821 aCreateIfNonExistent](RefPtr<UniversalDirectoryLock> 5822 clientInitDirectoryLock) mutable { 5823 if (aClientMetadata.mPersistenceType == 5824 PERSISTENCE_TYPE_PERSISTENT) { 5825 return self->InitializePersistentClient( 5826 aClientMetadata, std::move(clientInitDirectoryLock)); 5827 } 5828 5829 return self->InitializeTemporaryClient( 5830 aClientMetadata, aCreateIfNonExistent, 5831 std::move(clientInitDirectoryLock)); 5832 })) 5833 ->Then( 5834 GetCurrentSerialEventTarget(), __func__ /* clang formatting anchor */, 5835 MaybeFinalize( 5836 std::move(clientDirectoryLock), 5837 std::move(firstAccessDirectoryLock), 5838 std::move(lastAccessDirectoryLock), 5839 [self = RefPtr(this), aClientMetadata]( 5840 RefPtr<ClientDirectoryLock> clientDirectoryLock, 5841 RefPtr<UniversalDirectoryLock> firstAccessDirectoryLock, 5842 RefPtr<UniversalDirectoryLock> 5843 lastAccessDirectoryLock) mutable { 5844 auto clientDirectoryLockHandle = 5845 ClientDirectoryLockHandle(std::move(clientDirectoryLock)); 5846 5847 self->RegisterClientDirectoryLockHandle( 5848 aClientMetadata, 5849 [&self = *self, &aClientMetadata, &firstAccessDirectoryLock, 5850 &lastAccessDirectoryLock]( 5851 OpenClientDirectoryInfo& 5852 aOpenClientDirectoryInfo) mutable { 5853 if (aClientMetadata.mPersistenceType != 5854 PERSISTENCE_TYPE_PERSISTENT) { 5855 aOpenClientDirectoryInfo.SetFirstAccessPromise( 5856 self.SaveOriginAccessTime( 5857 aClientMetadata, 5858 std::move(firstAccessDirectoryLock))); 5859 5860 aOpenClientDirectoryInfo.SetLastAccessDirectoryLock( 5861 std::move(lastAccessDirectoryLock)); 5862 } 5863 }); 5864 5865 clientDirectoryLockHandle.SetRegistered(true); 5866 5867 SafeDropDirectoryLock(firstAccessDirectoryLock); 5868 SafeDropDirectoryLock(lastAccessDirectoryLock); 5869 5870 return Map<ClientDirectoryLockHandlePromise>( 5871 self->WithOpenClientDirectoryInfo( 5872 aClientMetadata, 5873 [aClientMetadata]( 5874 OpenClientDirectoryInfo& aOpenClientDirectoryInfo) { 5875 if (aClientMetadata.mPersistenceType != 5876 PERSISTENCE_TYPE_PERSISTENT) { 5877 return aOpenClientDirectoryInfo 5878 .AcquireFirstAccessPromise(); 5879 } 5880 5881 return BoolPromise::CreateAndResolve(true, __func__); 5882 }), 5883 [clientDirectoryLockHandle = 5884 std::move(clientDirectoryLockHandle)]( 5885 const BoolPromise::ResolveOrRejectValue& 5886 aValue) mutable { 5887 return std::move(clientDirectoryLockHandle); 5888 }); 5889 })); 5890 } 5891 5892 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock( 5893 const ClientMetadata& aClientMetadata, bool aExclusive) { 5894 AssertIsOnOwningThread(); 5895 5896 return ClientDirectoryLock::Create( 5897 WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType, 5898 aClientMetadata, aClientMetadata.mClientType, aExclusive); 5899 } 5900 5901 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal( 5902 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope, 5903 const ClientStorageScope& aClientStorageScope, bool aExclusive, 5904 DirectoryLockCategory aCategory) { 5905 AssertIsOnOwningThread(); 5906 5907 return UniversalDirectoryLock::CreateInternal( 5908 WrapNotNullUnchecked(this), aPersistenceScope, aOriginScope, 5909 aClientStorageScope, aExclusive, aCategory); 5910 } 5911 5912 bool QuotaManager::IsPendingOrigin( 5913 const OriginMetadata& aOriginMetadata) const { 5914 MutexAutoLock lock(mQuotaMutex); 5915 5916 RefPtr<OriginInfo> originInfo = 5917 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata); 5918 5919 return originInfo && !originInfo->LockedDirectoryExists(); 5920 } 5921 5922 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage() { 5923 AssertIsOnOwningThread(); 5924 5925 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 5926 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT), 5927 OriginScope::FromNull(), ClientStorageScope::CreateFromNull(), 5928 /* aExclusive */ false); 5929 5930 auto prepareInfo = directoryLock->Prepare(); 5931 5932 // If persistent storage is initialized but there's a clear storage or 5933 // shutdown storage operation already scheduled, we can't immediately resolve 5934 // the promise and return from the function because the clear or shutdown 5935 // storage operation uninitializes storage. 5936 if (mPersistentStorageInitialized && 5937 !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) { 5938 return BoolPromise::CreateAndResolve(true, __func__); 5939 } 5940 5941 return directoryLock->Acquire(std::move(prepareInfo)) 5942 ->Then( 5943 GetCurrentSerialEventTarget(), __func__, 5944 [self = RefPtr(this), directoryLock]( 5945 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 5946 if (aValue.IsReject()) { 5947 return BoolPromise::CreateAndReject(aValue.RejectValue(), 5948 __func__); 5949 } 5950 5951 return self->InitializePersistentStorage(std::move(directoryLock)); 5952 }); 5953 } 5954 5955 RefPtr<BoolPromise> QuotaManager::InitializePersistentStorage( 5956 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 5957 AssertIsOnOwningThread(); 5958 MOZ_ASSERT(aDirectoryLock); 5959 MOZ_ASSERT(aDirectoryLock->Acquired()); 5960 5961 // If persistent storage is initialized and the directory lock for the 5962 // initialize persistent storage operation is acquired, we can immediately 5963 // resolve the promise and return from the function because there can't be a 5964 // clear storage or shutdown storage operation which would uninitialize 5965 // persistent storage. 5966 if (mPersistentStorageInitialized) { 5967 DropDirectoryLock(aDirectoryLock); 5968 5969 return BoolPromise::CreateAndResolve(true, __func__); 5970 } 5971 5972 auto initializePersistentStorageOp = CreateInitializePersistentStorageOp( 5973 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock)); 5974 5975 RegisterNormalOriginOp(*initializePersistentStorageOp); 5976 5977 initializePersistentStorageOp->RunImmediately(); 5978 5979 return initializePersistentStorageOp->OnResults()->Then( 5980 GetCurrentSerialEventTarget(), __func__, 5981 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 5982 if (aValue.IsReject()) { 5983 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 5984 } 5985 5986 self->mPersistentStorageInitialized = true; 5987 5988 return BoolPromise::CreateAndResolve(true, __func__); 5989 }); 5990 } 5991 5992 RefPtr<BoolPromise> QuotaManager::PersistentStorageInitialized() { 5993 AssertIsOnOwningThread(); 5994 5995 auto persistentStorageInitializedOp = 5996 CreatePersistentStorageInitializedOp(WrapMovingNotNullUnchecked(this)); 5997 5998 RegisterNormalOriginOp(*persistentStorageInitializedOp); 5999 6000 persistentStorageInitializedOp->RunImmediately(); 6001 6002 return persistentStorageInitializedOp->OnResults(); 6003 } 6004 6005 nsresult QuotaManager::EnsurePersistentStorageIsInitializedInternal() { 6006 AssertIsOnIOThread(); 6007 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6008 6009 const auto innerFunc = 6010 [&](const auto& firstInitializationAttempt) -> nsresult { 6011 if (mPersistentStorageInitializedInternal) { 6012 MOZ_ASSERT(firstInitializationAttempt.Recorded()); 6013 return NS_OK; 6014 } 6015 6016 QM_TRY(MOZ_TO_RESULT(ExecuteInitialization( 6017 Initialization::PersistentRepository, [&](const auto&) -> nsresult { 6018 return InitializeRepository(PERSISTENCE_TYPE_PERSISTENT, 6019 [](auto&) {}); 6020 }))); 6021 6022 mPersistentStorageInitializedInternal = true; 6023 6024 return NS_OK; 6025 }; 6026 6027 return ExecuteInitialization( 6028 Initialization::TemporaryStorage, 6029 "dom::quota::FirstInitializationAttempt::PersistentStorage"_ns, 6030 innerFunc); 6031 } 6032 6033 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup( 6034 const PrincipalMetadata& aPrincipalMetadata) { 6035 AssertIsOnOwningThread(); 6036 6037 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6038 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY, 6039 PERSISTENCE_TYPE_DEFAULT), 6040 OriginScope::FromGroup(aPrincipalMetadata.mGroup), 6041 ClientStorageScope::CreateFromNull(), 6042 /* aExclusive */ false); 6043 6044 auto prepareInfo = directoryLock->Prepare(); 6045 6046 // If temporary group is initialized but there's a clear storage or shutdown 6047 // storage operation already scheduled, we can't immediately resolve the 6048 // promise and return from the function because the clear and shutdown 6049 // storage operation uninitializes storage. 6050 if (IsTemporaryGroupInitialized(aPrincipalMetadata) && 6051 !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) { 6052 return BoolPromise::CreateAndResolve(true, __func__); 6053 } 6054 6055 return directoryLock->Acquire(std::move(prepareInfo)) 6056 ->Then(GetCurrentSerialEventTarget(), __func__, 6057 [self = RefPtr(this), aPrincipalMetadata, directoryLock]( 6058 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6059 if (aValue.IsReject()) { 6060 return BoolPromise::CreateAndReject(aValue.RejectValue(), 6061 __func__); 6062 } 6063 6064 return self->InitializeTemporaryGroup(aPrincipalMetadata, 6065 std::move(directoryLock)); 6066 }); 6067 } 6068 6069 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryGroup( 6070 const PrincipalMetadata& aPrincipalMetadata, 6071 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6072 AssertIsOnOwningThread(); 6073 MOZ_ASSERT(aDirectoryLock); 6074 MOZ_ASSERT(aDirectoryLock->Acquired()); 6075 6076 // If temporary group is initialized and the directory lock for the 6077 // initialize temporary group operation is acquired, we can immediately 6078 // resolve the promise and return from the function because there can't be a 6079 // clear storage or shutdown storage operation which would uninitialize 6080 // temporary storage. 6081 if (IsTemporaryGroupInitialized(aPrincipalMetadata)) { 6082 DropDirectoryLock(aDirectoryLock); 6083 6084 return BoolPromise::CreateAndResolve(true, __func__); 6085 } 6086 6087 auto initializeTemporaryGroupOp = CreateInitializeTemporaryGroupOp( 6088 WrapMovingNotNullUnchecked(this), aPrincipalMetadata, 6089 std::move(aDirectoryLock)); 6090 6091 RegisterNormalOriginOp(*initializeTemporaryGroupOp); 6092 6093 initializeTemporaryGroupOp->RunImmediately(); 6094 6095 return Map<BoolPromise>( 6096 initializeTemporaryGroupOp->OnResults(), 6097 [self = RefPtr(this), group = aPrincipalMetadata.mGroup]( 6098 const BoolPromise::ResolveOrRejectValue& aValue) { 6099 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Insert( 6100 group); 6101 6102 return aValue.ResolveValue(); 6103 }); 6104 } 6105 6106 RefPtr<BoolPromise> QuotaManager::TemporaryGroupInitialized( 6107 const PrincipalMetadata& aPrincipalMetadata) { 6108 AssertIsOnOwningThread(); 6109 6110 auto temporaryGroupInitializedOp = CreateTemporaryGroupInitializedOp( 6111 WrapMovingNotNullUnchecked(this), aPrincipalMetadata); 6112 6113 RegisterNormalOriginOp(*temporaryGroupInitializedOp); 6114 6115 temporaryGroupInitializedOp->RunImmediately(); 6116 6117 return temporaryGroupInitializedOp->OnResults(); 6118 } 6119 6120 bool QuotaManager::IsTemporaryGroupInitialized( 6121 const PrincipalMetadata& aPrincipalMetadata) { 6122 AssertIsOnOwningThread(); 6123 6124 return mBackgroundThreadAccessible.Access()->mInitializedGroups.Contains( 6125 aPrincipalMetadata.mGroup); 6126 } 6127 6128 bool QuotaManager::IsTemporaryGroupInitializedInternal( 6129 const PrincipalMetadata& aPrincipalMetadata) const { 6130 AssertIsOnIOThread(); 6131 6132 MutexAutoLock lock(mQuotaMutex); 6133 6134 return LockedHasGroupInfoPair(aPrincipalMetadata.mGroup); 6135 } 6136 6137 Result<Ok, nsresult> QuotaManager::EnsureTemporaryGroupIsInitializedInternal( 6138 const PrincipalMetadata& aPrincipalMetadata) { 6139 AssertIsOnIOThread(); 6140 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6141 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal); 6142 6143 const auto innerFunc = [&aPrincipalMetadata, 6144 this](const auto&) -> mozilla::Result<Ok, nsresult> { 6145 NotifyGroupInitializationStarted(*this); 6146 6147 const auto& array = 6148 mIOThreadAccessible.Access()->mAllTemporaryOrigins.Lookup( 6149 aPrincipalMetadata.mGroup); 6150 if (!array) { 6151 return Ok{}; 6152 } 6153 6154 // XXX At the moment, the loop skips all elements in the array because 6155 // temporary storage initialization still initializes all temporary 6156 // origins. This is going to change soon with the planned asynchronous 6157 // temporary origin initialization done in the background. 6158 for (const auto& originMetadata : *array) { 6159 if (NS_WARN_IF(IsShuttingDown())) { 6160 return Err(NS_ERROR_ABORT); 6161 } 6162 6163 if (IsTemporaryOriginInitializedInternal(originMetadata)) { 6164 continue; 6165 } 6166 6167 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(originMetadata)); 6168 6169 QM_TRY_INSPECT(const auto& metadata, 6170 LoadFullOriginMetadataWithRestore(directory)); 6171 6172 // XXX Check corruption here! 6173 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata, 6174 /* aForGroup */ true))); 6175 } 6176 6177 // XXX Evict origins that exceed their group limit here. 6178 6179 SleepIfEnabled( 6180 StaticPrefs::dom_quotaManager_groupInitialization_pauseOnIOThreadMs()); 6181 6182 return Ok{}; 6183 }; 6184 6185 return ExecuteGroupInitialization( 6186 aPrincipalMetadata.mGroup, GroupInitialization::TemporaryGroup, 6187 "dom::quota::FirstOriginInitializationAttempt::TemporaryGroup"_ns, 6188 innerFunc); 6189 } 6190 6191 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin( 6192 const OriginMetadata& aOriginMetadata) { 6193 AssertIsOnOwningThread(); 6194 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6195 6196 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6197 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT), 6198 OriginScope::FromOrigin(aOriginMetadata), 6199 ClientStorageScope::CreateFromNull(), 6200 /* aExclusive */ false); 6201 6202 auto prepareInfo = directoryLock->Prepare(); 6203 6204 // If persistent origin is initialized but there's a clear storage, shutdown 6205 // storage, clear origin, or shutdown origin operation already scheduled, we 6206 // can't immediately resolve the promise and return from the function because 6207 // the clear and shutdown storage operations uninitialize storage (which also 6208 // includes uninitialization of origins) and because clear and shutdown origin 6209 // operations uninitialize origins directly. 6210 if (IsPersistentOriginInitialized(aOriginMetadata) && 6211 !prepareInfo.IsBlockedBy(kUninitOriginsAndBroaderCategories)) { 6212 return BoolPromise::CreateAndResolve(true, __func__); 6213 } 6214 6215 return directoryLock->Acquire(std::move(prepareInfo)) 6216 ->Then(GetCurrentSerialEventTarget(), __func__, 6217 [self = RefPtr(this), aOriginMetadata, directoryLock]( 6218 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6219 if (aValue.IsReject()) { 6220 return BoolPromise::CreateAndReject(aValue.RejectValue(), 6221 __func__); 6222 } 6223 6224 return self->InitializePersistentOrigin( 6225 aOriginMetadata, std::move(directoryLock)); 6226 }); 6227 } 6228 6229 RefPtr<BoolPromise> QuotaManager::InitializePersistentOrigin( 6230 const OriginMetadata& aOriginMetadata, 6231 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6232 AssertIsOnOwningThread(); 6233 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6234 MOZ_ASSERT(aDirectoryLock); 6235 MOZ_ASSERT(aDirectoryLock->Acquired()); 6236 6237 // If persistent origin is initialized and the directory lock for the 6238 // initialize persistent origin operation is acquired, we can immediately 6239 // resolve the promise and return from the function because there can't be a 6240 // clear storage, shutdown storage, clear origin, or shutdown origin 6241 // operation which would uninitialize storage (which also includes 6242 // uninitialization of origins), or which would uninitialize origins 6243 // directly. 6244 if (IsPersistentOriginInitialized(aOriginMetadata)) { 6245 DropDirectoryLock(aDirectoryLock); 6246 6247 return BoolPromise::CreateAndResolve(true, __func__); 6248 } 6249 6250 auto initializePersistentOriginOp = CreateInitializePersistentOriginOp( 6251 WrapMovingNotNullUnchecked(this), aOriginMetadata, 6252 std::move(aDirectoryLock)); 6253 6254 RegisterNormalOriginOp(*initializePersistentOriginOp); 6255 6256 initializePersistentOriginOp->RunImmediately(); 6257 6258 return Map<BoolPromise>( 6259 initializePersistentOriginOp->OnResults(), 6260 [self = RefPtr(this), origin = aOriginMetadata.mOrigin]( 6261 const BoolPromise::ResolveOrRejectValue& aValue) { 6262 self->NoteInitializedOrigin(PERSISTENCE_TYPE_PERSISTENT, origin); 6263 6264 return aValue.ResolveValue(); 6265 }); 6266 } 6267 6268 RefPtr<BoolPromise> QuotaManager::PersistentOriginInitialized( 6269 const OriginMetadata& aOriginMetadata) { 6270 AssertIsOnOwningThread(); 6271 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6272 6273 auto persistentOriginInitializedOp = CreatePersistentOriginInitializedOp( 6274 WrapMovingNotNullUnchecked(this), aOriginMetadata); 6275 6276 RegisterNormalOriginOp(*persistentOriginInitializedOp); 6277 6278 persistentOriginInitializedOp->RunImmediately(); 6279 6280 return persistentOriginInitializedOp->OnResults(); 6281 } 6282 6283 bool QuotaManager::IsPersistentOriginInitialized( 6284 const OriginMetadata& aOriginMetadata) { 6285 AssertIsOnOwningThread(); 6286 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6287 6288 return IsOriginInitialized(aOriginMetadata.mPersistenceType, 6289 aOriginMetadata.mOrigin); 6290 } 6291 6292 bool QuotaManager::IsPersistentOriginInitializedInternal( 6293 const OriginMetadata& aOriginMetadata) const { 6294 AssertIsOnIOThread(); 6295 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6296 6297 return mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin); 6298 } 6299 6300 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> 6301 QuotaManager::EnsurePersistentOriginIsInitializedInternal( 6302 const OriginMetadata& aOriginMetadata) { 6303 AssertIsOnIOThread(); 6304 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6305 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6306 6307 const auto innerFunc = [&aOriginMetadata, 6308 this](const auto& firstInitializationAttempt) 6309 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> { 6310 const auto extraInfo = 6311 ScopedLogExtraInfo{ScopedLogExtraInfo::kTagStorageOriginTainted, 6312 aOriginMetadata.mStorageOrigin}; 6313 6314 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata)); 6315 6316 if (mInitializedOriginsInternal.Contains(aOriginMetadata.mOrigin)) { 6317 MOZ_ASSERT(firstInitializationAttempt.Recorded()); 6318 return std::pair(std::move(directory), false); 6319 } 6320 6321 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory)); 6322 6323 QM_TRY_INSPECT( 6324 const FullOriginMetadata& fullOriginMetadata, 6325 ([this, created, &directory, 6326 &aOriginMetadata]() -> Result<FullOriginMetadata, nsresult> { 6327 if (created) { 6328 const int64_t timestamp = PR_Now(); 6329 6330 FullOriginMetadata fullOriginMetadata{ 6331 aOriginMetadata, 6332 OriginStateMetadata{/* aLastAccessTime */ timestamp, 6333 /* aLastMaintenanceDate */ 6334 Date::FromTimestamp(timestamp).ToDays(), 6335 /* aAccessed */ false, 6336 /* aPersisted */ true}, 6337 ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion}; 6338 6339 // Only creating .metadata-v2 to reduce IO. 6340 QM_TRY(MOZ_TO_RESULT( 6341 CreateDirectoryMetadata2(*directory, fullOriginMetadata))); 6342 6343 return fullOriginMetadata; 6344 } 6345 6346 // Get the metadata. We only use the timestamp. 6347 QM_TRY_INSPECT(const auto& metadata, 6348 LoadFullOriginMetadataWithRestore(directory)); 6349 6350 MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now()); 6351 6352 return metadata; 6353 }())); 6354 6355 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, fullOriginMetadata))); 6356 6357 mInitializedOriginsInternal.AppendElement(aOriginMetadata.mOrigin); 6358 6359 return std::pair(std::move(directory), created); 6360 }; 6361 6362 return ExecuteOriginInitialization( 6363 aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin, 6364 "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns, 6365 innerFunc); 6366 } 6367 6368 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin( 6369 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) { 6370 AssertIsOnOwningThread(); 6371 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6372 6373 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6374 PersistenceScope::CreateFromValue(aOriginMetadata.mPersistenceType), 6375 OriginScope::FromOrigin(aOriginMetadata), 6376 ClientStorageScope::CreateFromNull(), 6377 /* aExclusive */ false); 6378 6379 auto prepareInfo = directoryLock->Prepare(); 6380 6381 // If temporary origin is initialized but there's a clear storage, shutdown 6382 // storage, clear origin, or shutdown origin operation already scheduled, we 6383 // can't immediately resolve the promise and return from the function because 6384 // the clear and shutdown storage operations uninitialize storage (which also 6385 // includes uninitialization of origins) and because clear and shutdown origin 6386 // operations uninitialize origins directly. 6387 if (IsTemporaryOriginInitialized(aOriginMetadata) && 6388 !prepareInfo.IsBlockedBy(kUninitOriginsAndBroaderCategories)) { 6389 return BoolPromise::CreateAndResolve(true, __func__); 6390 } 6391 6392 return directoryLock->Acquire(std::move(prepareInfo)) 6393 ->Then(GetCurrentSerialEventTarget(), __func__, 6394 [self = RefPtr(this), aOriginMetadata, aCreateIfNonExistent, 6395 directoryLock]( 6396 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6397 if (aValue.IsReject()) { 6398 return BoolPromise::CreateAndReject(aValue.RejectValue(), 6399 __func__); 6400 } 6401 6402 return self->InitializeTemporaryOrigin(aOriginMetadata, 6403 aCreateIfNonExistent, 6404 std::move(directoryLock)); 6405 }); 6406 } 6407 6408 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryOrigin( 6409 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent, 6410 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6411 AssertIsOnOwningThread(); 6412 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6413 MOZ_ASSERT(aDirectoryLock); 6414 MOZ_ASSERT(aDirectoryLock->Acquired()); 6415 6416 // If temporary origin is initialized and the directory lock for the 6417 // initialize temporary origin operation is acquired, we can immediately 6418 // resolve the promise and return from the function because there can't be a 6419 // clear storage, shutdown storage, clear origin, or shutdown origin 6420 // operation which would uninitialize storage (which also includes 6421 // uninitialization of origins), or which would uninitialize origins 6422 // directly. 6423 if (IsTemporaryOriginInitialized(aOriginMetadata)) { 6424 DropDirectoryLock(aDirectoryLock); 6425 6426 return BoolPromise::CreateAndResolve(true, __func__); 6427 } 6428 6429 auto initializeTemporaryOriginOp = CreateInitializeTemporaryOriginOp( 6430 WrapMovingNotNullUnchecked(this), aOriginMetadata, aCreateIfNonExistent, 6431 std::move(aDirectoryLock)); 6432 6433 RegisterNormalOriginOp(*initializeTemporaryOriginOp); 6434 6435 initializeTemporaryOriginOp->RunImmediately(); 6436 6437 return Map<BoolPromise>( 6438 initializeTemporaryOriginOp->OnResults(), 6439 [self = RefPtr(this), persistenceType = aOriginMetadata.mPersistenceType, 6440 origin = aOriginMetadata.mOrigin]( 6441 const BoolPromise::ResolveOrRejectValue& aValue) { 6442 self->NoteInitializedOrigin(persistenceType, origin); 6443 6444 return aValue.ResolveValue(); 6445 }); 6446 } 6447 6448 RefPtr<BoolPromise> QuotaManager::TemporaryOriginInitialized( 6449 const OriginMetadata& aOriginMetadata) { 6450 AssertIsOnOwningThread(); 6451 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6452 6453 auto temporaryOriginInitializedOp = CreateTemporaryOriginInitializedOp( 6454 WrapMovingNotNullUnchecked(this), aOriginMetadata); 6455 6456 RegisterNormalOriginOp(*temporaryOriginInitializedOp); 6457 6458 temporaryOriginInitializedOp->RunImmediately(); 6459 6460 return temporaryOriginInitializedOp->OnResults(); 6461 } 6462 6463 bool QuotaManager::IsTemporaryOriginInitialized( 6464 const OriginMetadata& aOriginMetadata) { 6465 AssertIsOnOwningThread(); 6466 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6467 6468 return IsOriginInitialized(aOriginMetadata.mPersistenceType, 6469 aOriginMetadata.mOrigin); 6470 } 6471 6472 bool QuotaManager::IsTemporaryOriginInitializedInternal( 6473 const OriginMetadata& aOriginMetadata) const { 6474 AssertIsOnIOThread(); 6475 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6476 6477 MutexAutoLock lock(mQuotaMutex); 6478 6479 RefPtr<OriginInfo> originInfo = 6480 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata); 6481 6482 return static_cast<bool>(originInfo); 6483 } 6484 6485 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> 6486 QuotaManager::EnsureTemporaryOriginIsInitializedInternal( 6487 const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent) { 6488 AssertIsOnIOThread(); 6489 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6490 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6491 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal); 6492 6493 const auto innerFunc = [&aOriginMetadata, aCreateIfNonExistent, 6494 this](const auto&) 6495 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> { 6496 // Get directory for this origin and persistence type. 6497 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata)); 6498 6499 if (IsTemporaryOriginInitializedInternal(aOriginMetadata)) { 6500 return std::pair(std::move(directory), false); 6501 } 6502 6503 // We apply the offset only here when setting the initial access time for 6504 // a new origin. In theory PersistOp::DoDirectoryWork could also honor this 6505 // pref, but since the pref is intended for testing only, we do it only 6506 // here for now for simplicity. 6507 const int64_t timestamp = []() { 6508 const int64_t now = PR_Now(); 6509 const uint32_t offsetSec = StaticPrefs:: 6510 dom_quotaManager_temporaryStorage_initialOriginAccessTimeOffsetSec(); 6511 6512 if (offsetSec > 0) { 6513 CheckedInt<int64_t> ts(now); 6514 6515 ts -= CheckedInt<int64_t>(offsetSec) * PR_USEC_PER_SEC; 6516 if (!ts.isValid()) { 6517 // Offset too large (underflow), use current time as if there was no 6518 // offset. 6519 6520 QM_WARNING("Initial origin access time offset too large!"); 6521 6522 return now; 6523 } 6524 6525 return ts.value(); 6526 } 6527 6528 return now; 6529 }(); 6530 6531 FullOriginMetadata fullOriginMetadata{ 6532 aOriginMetadata, 6533 OriginStateMetadata{ 6534 /* aLastAccessTime */ timestamp, 6535 /* aLastMaintenanceDate */ Date::FromTimestamp(timestamp).ToDays(), 6536 /* aAccessed */ false, 6537 /* aPersisted */ false}, 6538 ClientUsageArray(), /* aUsage */ 0, kCurrentQuotaVersion}; 6539 6540 if (!aCreateIfNonExistent) { 6541 InitQuotaForOrigin(fullOriginMetadata, /* aDirectoryExists */ false); 6542 6543 return std::pair(std::move(directory), false); 6544 } 6545 6546 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory)); 6547 6548 if (created) { 6549 // Usually, infallible operations are placed after fallible ones. 6550 // However, since we lack atomic support for creating the origin 6551 // directory along with its metadata, we need to add the origin to cached 6552 // origins right after directory creation. 6553 AddTemporaryOrigin(fullOriginMetadata); 6554 6555 // Only creating .metadata-v2 to reduce IO. 6556 QM_TRY(MOZ_TO_RESULT( 6557 CreateDirectoryMetadata2(*directory, fullOriginMetadata))); 6558 6559 // Don't need to traverse the directory, since it's empty. 6560 InitQuotaForOrigin(fullOriginMetadata); 6561 } else { 6562 QM_TRY_INSPECT(const auto& metadata, 6563 LoadFullOriginMetadataWithRestore(directory)); 6564 6565 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(directory, metadata))); 6566 } 6567 6568 // TODO: If the metadata file exists and we didn't call 6569 // LoadFullOriginMetadataWithRestore for it (because the quota info 6570 // was loaded from the cache), then the group in the metadata file 6571 // may be wrong, so it should be checked and eventually updated. 6572 // It's not a big deal that we are not doing it here, because the 6573 // origin will be marked as "accessed", so 6574 // LoadFullOriginMetadataWithRestore will be called for the metadata 6575 // file in next session in LoadQuotaFromCache. 6576 // (If a previous origin initialization failed, we actually do call 6577 // LoadFullOriginMetadataWithRestore which updates the group) 6578 6579 return std::pair(std::move(directory), created); 6580 }; 6581 6582 return ExecuteOriginInitialization( 6583 aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin, 6584 "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns, 6585 innerFunc); 6586 } 6587 6588 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient( 6589 const ClientMetadata& aClientMetadata) { 6590 AssertIsOnOwningThread(); 6591 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6592 6593 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6594 PersistenceScope::CreateFromValue(PERSISTENCE_TYPE_PERSISTENT), 6595 OriginScope::FromOrigin(aClientMetadata), 6596 ClientStorageScope::CreateFromClient(aClientMetadata.mClientType), 6597 /* aExclusive */ false); 6598 6599 auto prepareInfo = directoryLock->Prepare(); 6600 6601 // If the persistent client is initialized but there's a clear storage, 6602 // shutdown storage, clear origin, shutdown origin, clear client, or shutdown 6603 // client operation already scheduled, we can't immediately resolve the 6604 // promise and return from the function. This is because: 6605 // - clear and shutdown storage operations uninitialize storage, 6606 // which cascades to origins and clients; 6607 // - clear and shutdown origin operations uninitialize origins, 6608 // which also uninitialize clients; 6609 // - clear and shutdown client operations uninitialize the client directly. 6610 if (IsPersistentClientInitialized(aClientMetadata) && 6611 !prepareInfo.IsBlockedBy(kUninitClientsAndBroaderCategories)) { 6612 return BoolPromise::CreateAndResolve(true, __func__); 6613 } 6614 6615 return directoryLock->Acquire()->Then( 6616 GetCurrentSerialEventTarget(), __func__, 6617 [self = RefPtr(this), aClientMetadata, 6618 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6619 if (aValue.IsReject()) { 6620 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 6621 } 6622 6623 return self->InitializePersistentClient(aClientMetadata, 6624 std::move(directoryLock)); 6625 }); 6626 } 6627 6628 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient( 6629 const ClientMetadata& aClientMetadata, 6630 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6631 AssertIsOnOwningThread(); 6632 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6633 MOZ_ASSERT(aDirectoryLock); 6634 MOZ_ASSERT(aDirectoryLock->Acquired()); 6635 6636 // If the persistent client is initialized and the directory lock for the 6637 // initialize persistent client operation is acquired, we can immediately 6638 // resolve the promise and return from the function because there can't be a 6639 // clear storage, shutdown storage, clear origin, shutdown origin, clear 6640 // client, or shutdown client operation in progress. These operations would 6641 // otherwise uninitialize storage (which cascades to origins and clients), 6642 // uninitialize origins (which also uninitialize clients), or uninitialize 6643 // the client directly. 6644 if (IsPersistentClientInitialized(aClientMetadata)) { 6645 DropDirectoryLock(aDirectoryLock); 6646 6647 return BoolPromise::CreateAndResolve(true, __func__); 6648 } 6649 6650 auto initializePersistentClientOp = CreateInitializePersistentClientOp( 6651 WrapMovingNotNullUnchecked(this), aClientMetadata, 6652 std::move(aDirectoryLock)); 6653 6654 RegisterNormalOriginOp(*initializePersistentClientOp); 6655 6656 initializePersistentClientOp->RunImmediately(); 6657 6658 return Map<BoolPromise>( 6659 initializePersistentClientOp->OnResults(), 6660 [self = RefPtr(this), origin = aClientMetadata.mOrigin, 6661 clientType = aClientMetadata.mClientType]( 6662 const BoolPromise::ResolveOrRejectValue& aValue) { 6663 self->NoteInitializedClient(PERSISTENCE_TYPE_PERSISTENT, origin, 6664 clientType); 6665 6666 return aValue.ResolveValue(); 6667 }); 6668 } 6669 6670 bool QuotaManager::IsPersistentClientInitialized( 6671 const ClientMetadata& aClientMetadata) { 6672 AssertIsOnOwningThread(); 6673 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6674 6675 return IsClientInitialized(aClientMetadata.mPersistenceType, 6676 aClientMetadata.mOrigin, 6677 aClientMetadata.mClientType); 6678 } 6679 6680 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> 6681 QuotaManager::EnsurePersistentClientIsInitialized( 6682 const ClientMetadata& aClientMetadata) { 6683 AssertIsOnIOThread(); 6684 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT); 6685 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType)); 6686 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal()); 6687 MOZ_DIAGNOSTIC_ASSERT( 6688 IsPersistentOriginInitializedInternal(aClientMetadata.mOrigin)); 6689 6690 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata)); 6691 6692 QM_TRY(MOZ_TO_RESULT( 6693 directory->Append(Client::TypeToString(aClientMetadata.mClientType)))); 6694 6695 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory)); 6696 6697 return std::pair(std::move(directory), created); 6698 } 6699 6700 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient( 6701 const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent) { 6702 AssertIsOnOwningThread(); 6703 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6704 6705 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6706 PersistenceScope::CreateFromValue(aClientMetadata.mPersistenceType), 6707 OriginScope::FromOrigin(aClientMetadata), 6708 ClientStorageScope::CreateFromClient(aClientMetadata.mClientType), 6709 /* aExclusive */ false); 6710 6711 auto prepareInfo = directoryLock->Prepare(); 6712 6713 // If the temporary client is initialized but there's a clear storage, 6714 // shutdown storage, clear origin, shutdown origin, clear client, or shutdown 6715 // client operation already scheduled, we can't immediately resolve the 6716 // promise and return from the function. This is because: 6717 // - clear and shutdown storage operations uninitialize storage, 6718 // which cascades to origins and clients; 6719 // - clear and shutdown origin operations uninitialize origins, 6720 // which also uninitialize clients; 6721 // - clear and shutdown client operations uninitialize the client directly. 6722 if (IsTemporaryClientInitialized(aClientMetadata) && 6723 !prepareInfo.IsBlockedBy(kUninitClientsAndBroaderCategories)) { 6724 return BoolPromise::CreateAndResolve(true, __func__); 6725 } 6726 6727 return directoryLock->Acquire()->Then( 6728 GetCurrentSerialEventTarget(), __func__, 6729 [self = RefPtr(this), aClientMetadata, aCreateIfNonExistent, 6730 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6731 if (aValue.IsReject()) { 6732 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 6733 } 6734 6735 return self->InitializeTemporaryClient( 6736 aClientMetadata, aCreateIfNonExistent, std::move(directoryLock)); 6737 }); 6738 } 6739 6740 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient( 6741 const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent, 6742 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6743 AssertIsOnOwningThread(); 6744 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6745 MOZ_ASSERT(aDirectoryLock); 6746 MOZ_ASSERT(aDirectoryLock->Acquired()); 6747 6748 // If the temporary client is initialized and the directory lock for the 6749 // initialize persistent client operation is acquired, we can immediately 6750 // resolve the promise and return from the function because there can't be a 6751 // clear storage, shutdown storage, clear origin, shutdown origin, clear 6752 // client, or shutdown client operation in progress. These operations would 6753 // otherwise uninitialize storage (which cascades to origins and clients), 6754 // uninitialize origins (which also uninitialize clients), or uninitialize 6755 // the client directly. 6756 if (IsTemporaryClientInitialized(aClientMetadata)) { 6757 DropDirectoryLock(aDirectoryLock); 6758 6759 return BoolPromise::CreateAndResolve(true, __func__); 6760 } 6761 6762 auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp( 6763 WrapMovingNotNullUnchecked(this), aClientMetadata, aCreateIfNonExistent, 6764 std::move(aDirectoryLock)); 6765 6766 RegisterNormalOriginOp(*initializeTemporaryClientOp); 6767 6768 initializeTemporaryClientOp->RunImmediately(); 6769 6770 return Map<BoolPromise>( 6771 initializeTemporaryClientOp->OnResults(), 6772 [self = RefPtr(this), persistenceType = aClientMetadata.mPersistenceType, 6773 origin = aClientMetadata.mOrigin, 6774 clientType = aClientMetadata.mClientType]( 6775 const BoolPromise::ResolveOrRejectValue& aValue) { 6776 self->NoteInitializedClient(persistenceType, origin, clientType); 6777 6778 return aValue.ResolveValue(); 6779 }); 6780 } 6781 6782 bool QuotaManager::IsTemporaryClientInitialized( 6783 const ClientMetadata& aClientMetadata) { 6784 AssertIsOnOwningThread(); 6785 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6786 6787 return IsClientInitialized(aClientMetadata.mPersistenceType, 6788 aClientMetadata.mOrigin, 6789 aClientMetadata.mClientType); 6790 } 6791 6792 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> 6793 QuotaManager::EnsureTemporaryClientIsInitialized( 6794 const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent) { 6795 AssertIsOnIOThread(); 6796 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 6797 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType)); 6798 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal()); 6799 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal()); 6800 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitializedInternal(aClientMetadata)); 6801 6802 if (!aCreateIfNonExistent) { 6803 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata)); 6804 6805 return std::pair(std::move(directory), false); 6806 } 6807 6808 QM_TRY_UNWRAP(auto directory, 6809 GetOrCreateTemporaryOriginDirectory(aClientMetadata)); 6810 6811 QM_TRY(MOZ_TO_RESULT( 6812 directory->Append(Client::TypeToString(aClientMetadata.mClientType)))); 6813 6814 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory)); 6815 6816 return std::pair(std::move(directory), created); 6817 } 6818 6819 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() { 6820 AssertIsOnOwningThread(); 6821 6822 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal( 6823 PersistenceScope::CreateFromSet(PERSISTENCE_TYPE_TEMPORARY, 6824 PERSISTENCE_TYPE_DEFAULT), 6825 OriginScope::FromNull(), ClientStorageScope::CreateFromNull(), 6826 /* aExclusive */ false); 6827 6828 auto prepareInfo = directoryLock->Prepare(); 6829 6830 // If temporary storage is initialized but there's a clear storage or 6831 // shutdown storage operation already scheduled, we can't immediately resolve 6832 // the promise and return from the function because the clear and shutdown 6833 // storage operation uninitializes storage. 6834 if (mTemporaryStorageInitialized && 6835 !prepareInfo.IsBlockedBy(kUninitStorageOnlyCategory)) { 6836 return BoolPromise::CreateAndResolve(true, __func__); 6837 } 6838 6839 return directoryLock->Acquire(std::move(prepareInfo)) 6840 ->Then( 6841 GetCurrentSerialEventTarget(), __func__, 6842 [self = RefPtr(this), directoryLock]( 6843 const BoolPromise::ResolveOrRejectValue& aValue) mutable { 6844 if (aValue.IsReject()) { 6845 return BoolPromise::CreateAndReject(aValue.RejectValue(), 6846 __func__); 6847 } 6848 6849 return self->InitializeTemporaryStorage(std::move(directoryLock)); 6850 }); 6851 } 6852 6853 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage( 6854 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 6855 AssertIsOnOwningThread(); 6856 MOZ_ASSERT(aDirectoryLock); 6857 MOZ_ASSERT(aDirectoryLock->Acquired()); 6858 6859 // If temporary storage is initialized and the directory lock for the 6860 // initialize temporary storage operation is acquired, we can immediately 6861 // resolve the promise and return from the function because there can't be a 6862 // clear storage or shutdown storage operation which would uninitialize 6863 // temporary storage. 6864 if (mTemporaryStorageInitialized) { 6865 DropDirectoryLock(aDirectoryLock); 6866 6867 return BoolPromise::CreateAndResolve(true, __func__); 6868 } 6869 6870 auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp( 6871 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock)); 6872 6873 RegisterNormalOriginOp(*initializeTemporaryStorageOp); 6874 6875 initializeTemporaryStorageOp->RunImmediately(); 6876 6877 return initializeTemporaryStorageOp->OnResults()->Then( 6878 GetCurrentSerialEventTarget(), __func__, 6879 [self = RefPtr(this)]( 6880 MaybePrincipalMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 6881 if (aValue.IsReject()) { 6882 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 6883 } 6884 6885 self->mTemporaryStorageInitialized = true; 6886 6887 if (aValue.ResolveValue() && 6888 QuotaPrefs::LazyOriginInitializationEnabled()) { 6889 self->mBackgroundThreadAccessible.Access()->mUninitializedGroups = 6890 aValue.ResolveValue().extract(); 6891 6892 if (QuotaPrefs::TriggerOriginInitializationInBackgroundEnabled()) { 6893 self->InitializeAllTemporaryOrigins(); 6894 } 6895 } 6896 6897 return BoolPromise::CreateAndResolve(true, __func__); 6898 }); 6899 } 6900 6901 nsresult QuotaManager::InitializeTemporaryStorageInternal() { 6902 AssertIsOnIOThread(); 6903 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6904 MOZ_DIAGNOSTIC_ASSERT(!mTemporaryStorageInitializedInternal); 6905 6906 nsCOMPtr<nsIFile> storageDir; 6907 QM_TRY(MOZ_TO_RESULT( 6908 NS_NewLocalFile(GetStoragePath(), getter_AddRefs(storageDir)))); 6909 6910 // The storage directory must exist before calling GetTemporaryStorageLimit. 6911 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir)); 6912 6913 (void)created; 6914 6915 QM_TRY_UNWRAP(mTemporaryStorageLimit, GetTemporaryStorageLimit(*storageDir)); 6916 6917 QM_TRY(MOZ_TO_RESULT(LoadQuota())); 6918 6919 mTemporaryStorageInitializedInternal = true; 6920 6921 // If origin initialization is done lazily, then there's either no quota 6922 // information at this point (if the cache couldn't be used) or only 6923 // partial quota information (origins accessed in a previous session 6924 // require full initialization). Given that, the cleanup can't be done 6925 // at this point yet. 6926 // For the same reason, recording metrics like origins with zero usage can't 6927 // be done either. 6928 if (!QuotaPrefs::LazyOriginInitializationEnabled()) { 6929 CleanupTemporaryStorage(); 6930 6931 RecordTemporaryStorageMetrics(); 6932 } 6933 6934 if (mCacheUsable) { 6935 QM_TRY(InvalidateCache(*mStorageConnection)); 6936 } 6937 6938 return NS_OK; 6939 } 6940 6941 nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() { 6942 AssertIsOnIOThread(); 6943 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 6944 6945 const auto innerFunc = 6946 [&](const auto& firstInitializationAttempt) -> nsresult { 6947 if (mTemporaryStorageInitializedInternal) { 6948 MOZ_ASSERT(firstInitializationAttempt.Recorded()); 6949 return NS_OK; 6950 } 6951 6952 // Glean SDK recommends using its own timing APIs where possible. In this 6953 // case, we use NowExcludingSuspendMs() directly to manually calculate a 6954 // duration that excludes suspend time. This is a valid exception because 6955 // our use case is sensitive to suspend, and we need full control over the 6956 // timing logic. 6957 6958 const auto startExcludingSuspendMs = NowExcludingSuspendMs(); 6959 6960 QM_TRY(MOZ_TO_RESULT(InitializeTemporaryStorageInternal())); 6961 6962 const auto endExcludingSuspendMs = NowExcludingSuspendMs(); 6963 6964 if (startExcludingSuspendMs && endExcludingSuspendMs) { 6965 const auto duration = TimeDuration::FromMilliseconds( 6966 *endExcludingSuspendMs - *startExcludingSuspendMs); 6967 6968 glean::quotamanager_initialize_temporarystorage:: 6969 total_time_excluding_suspend.AccumulateRawDuration(duration); 6970 } 6971 6972 return NS_OK; 6973 }; 6974 6975 return ExecuteInitialization( 6976 Initialization::TemporaryStorage, 6977 "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc); 6978 } 6979 6980 RefPtr<BoolPromise> QuotaManager::InitializeAllTemporaryOrigins() { 6981 AssertIsOnOwningThread(); 6982 6983 if (mAllTemporaryOriginsInitialized) { 6984 return BoolPromise::CreateAndResolve(true, __func__); 6985 } 6986 6987 // Ensure the promise holder is initialized (a promise is created). This is 6988 // needed because processNextGroup can synchronously attempt to resolve the 6989 // promise. Without this, consumers may endlessly wait for an unresolved 6990 // promise, and in debug builds, an assertion failure may occur at shutdown, 6991 // indicating the promise exists but was never resolved or rejected. 6992 RefPtr<BoolPromise> promise = 6993 mInitializeAllTemporaryOriginsPromiseHolder.Ensure(__func__); 6994 6995 if (!mInitializingAllTemporaryOrigins) { 6996 // TODO: make a copy of mUninitializedGroups and use it as the queue. 6997 6998 mInitializingAllTemporaryOrigins = true; 6999 7000 auto processNextGroup = [self = RefPtr(this)]( 7001 auto&& processNextGroupCallback) { 7002 auto backgroundThreadData = self->mBackgroundThreadAccessible.Access(); 7003 7004 if (backgroundThreadData->mUninitializedGroups.IsEmpty()) { 7005 self->mInitializingAllTemporaryOrigins = false; 7006 self->mAllTemporaryOriginsInitialized = true; 7007 7008 self->mInitializeAllTemporaryOriginsPromiseHolder.ResolveIfExists( 7009 true, __func__); 7010 7011 return; 7012 } 7013 7014 if (NS_WARN_IF(IsShuttingDown())) { 7015 self->mInitializingAllTemporaryOrigins = false; 7016 7017 self->mInitializeAllTemporaryOriginsPromiseHolder.RejectIfExists( 7018 NS_ERROR_ABORT, __func__); 7019 7020 return; 7021 } 7022 7023 auto principalMetadata = 7024 backgroundThreadData->mUninitializedGroups.PopLastElement(); 7025 7026 self->InitializeTemporaryGroup(principalMetadata) 7027 ->Then(GetCurrentSerialEventTarget(), __func__, 7028 [processNextGroupCallback]( 7029 const BoolPromise::ResolveOrRejectValue& aValue) { 7030 // TODO: Remove the group from mUninitializedGroups only 7031 // when the group initialization succeeded. 7032 7033 processNextGroupCallback(processNextGroupCallback); 7034 }); 7035 }; 7036 7037 processNextGroup(/* processNextGroupCallback */ processNextGroup); 7038 } 7039 7040 return promise; 7041 } 7042 7043 RefPtr<BoolPromise> QuotaManager::SaveOriginAccessTime( 7044 const OriginMetadata& aOriginMetadata) { 7045 AssertIsOnOwningThread(); 7046 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7047 7048 RefPtr<UniversalDirectoryLock> directoryLock = 7049 CreateSaveOriginAccessTimeLock(*this, aOriginMetadata); 7050 7051 return directoryLock->Acquire()->Then( 7052 GetCurrentSerialEventTarget(), __func__, 7053 [self = RefPtr(this), aOriginMetadata, 7054 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable { 7055 if (aValue.IsReject()) { 7056 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 7057 } 7058 7059 return self->SaveOriginAccessTime(aOriginMetadata, 7060 std::move(directoryLock)); 7061 }); 7062 } 7063 7064 RefPtr<BoolPromise> QuotaManager::SaveOriginAccessTime( 7065 const OriginMetadata& aOriginMetadata, 7066 RefPtr<UniversalDirectoryLock> aDirectoryLock) { 7067 AssertIsOnOwningThread(); 7068 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7069 MOZ_ASSERT(aDirectoryLock); 7070 MOZ_ASSERT(aDirectoryLock->Acquired()); 7071 7072 auto saveOriginAccessTimeOp = 7073 CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this), 7074 aOriginMetadata, std::move(aDirectoryLock)); 7075 7076 RegisterNormalOriginOp(*saveOriginAccessTimeOp); 7077 7078 saveOriginAccessTimeOp->RunImmediately(); 7079 7080 return Map<BoolPromise>( 7081 saveOriginAccessTimeOp->OnResults(), 7082 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 7083 if (aValue.ResolveValue()) { 7084 self->IncreaseSaveOriginAccessTimeCount(); 7085 } 7086 7087 return aValue.ResolveValue(); 7088 }); 7089 } 7090 7091 RefPtr<OriginUsageMetadataArrayPromise> QuotaManager::GetUsage( 7092 bool aGetAll, RefPtr<BoolPromise> aOnCancelPromise) { 7093 AssertIsOnOwningThread(); 7094 7095 auto getUsageOp = CreateGetUsageOp(WrapMovingNotNullUnchecked(this), aGetAll); 7096 7097 RegisterNormalOriginOp(*getUsageOp); 7098 7099 getUsageOp->RunImmediately(); 7100 7101 if (aOnCancelPromise) { 7102 RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise); 7103 7104 onCancelPromise->Then( 7105 GetCurrentSerialEventTarget(), __func__, 7106 [getUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) { 7107 if (aValue.IsReject()) { 7108 return; 7109 } 7110 7111 if (getUsageOp->Cancel()) { 7112 NS_WARNING("Canceled more than once?!"); 7113 } 7114 }); 7115 } 7116 7117 return getUsageOp->OnResults(); 7118 } 7119 7120 RefPtr<UsageInfoPromise> QuotaManager::GetOriginUsage( 7121 const PrincipalInfo& aPrincipalInfo, RefPtr<BoolPromise> aOnCancelPromise) { 7122 AssertIsOnOwningThread(); 7123 7124 auto getOriginUsageOp = 7125 CreateGetOriginUsageOp(WrapMovingNotNullUnchecked(this), aPrincipalInfo); 7126 7127 RegisterNormalOriginOp(*getOriginUsageOp); 7128 7129 getOriginUsageOp->RunImmediately(); 7130 7131 if (aOnCancelPromise) { 7132 RefPtr<BoolPromise> onCancelPromise = std::move(aOnCancelPromise); 7133 7134 onCancelPromise->Then( 7135 GetCurrentSerialEventTarget(), __func__, 7136 [getOriginUsageOp](const BoolPromise::ResolveOrRejectValue& aValue) { 7137 if (aValue.IsReject()) { 7138 return; 7139 } 7140 7141 if (getOriginUsageOp->Cancel()) { 7142 NS_WARNING("Canceled more than once?!"); 7143 } 7144 }); 7145 } 7146 7147 return getOriginUsageOp->OnResults(); 7148 } 7149 7150 RefPtr<UInt64Promise> QuotaManager::GetCachedOriginUsage( 7151 const PrincipalInfo& aPrincipalInfo) { 7152 AssertIsOnOwningThread(); 7153 7154 auto getCachedOriginUsageOp = CreateGetCachedOriginUsageOp( 7155 WrapMovingNotNullUnchecked(this), aPrincipalInfo); 7156 7157 RegisterNormalOriginOp(*getCachedOriginUsageOp); 7158 7159 getCachedOriginUsageOp->RunImmediately(); 7160 7161 return getCachedOriginUsageOp->OnResults(); 7162 } 7163 7164 RefPtr<CStringArrayPromise> QuotaManager::ListOrigins() { 7165 AssertIsOnOwningThread(); 7166 7167 auto listOriginsOp = CreateListOriginsOp(WrapMovingNotNullUnchecked(this)); 7168 7169 RegisterNormalOriginOp(*listOriginsOp); 7170 7171 listOriginsOp->RunImmediately(); 7172 7173 return listOriginsOp->OnResults(); 7174 } 7175 7176 RefPtr<CStringArrayPromise> QuotaManager::ListCachedOrigins() { 7177 AssertIsOnOwningThread(); 7178 7179 auto listCachedOriginsOp = 7180 CreateListCachedOriginsOp(WrapMovingNotNullUnchecked(this)); 7181 7182 RegisterNormalOriginOp(*listCachedOriginsOp); 7183 7184 listCachedOriginsOp->RunImmediately(); 7185 7186 return listCachedOriginsOp->OnResults(); 7187 } 7188 7189 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin( 7190 const Maybe<PersistenceType>& aPersistenceType, 7191 const PrincipalInfo& aPrincipalInfo) { 7192 AssertIsOnOwningThread(); 7193 7194 auto clearOriginOp = CreateClearOriginOp(WrapMovingNotNullUnchecked(this), 7195 aPersistenceType, aPrincipalInfo); 7196 7197 RegisterNormalOriginOp(*clearOriginOp); 7198 7199 clearOriginOp->RunImmediately(); 7200 7201 return Map<BoolPromise>( 7202 clearOriginOp->OnResults(), 7203 [self = RefPtr(this)]( 7204 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7205 self->NoteUninitializedClients(aValue.ResolveValue()); 7206 self->NoteUninitializedOrigins(aValue.ResolveValue()); 7207 7208 return true; 7209 }); 7210 } 7211 7212 RefPtr<BoolPromise> QuotaManager::ClearStoragesForClient( 7213 Maybe<PersistenceType> aPersistenceType, 7214 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) { 7215 AssertIsOnOwningThread(); 7216 7217 auto clearClientOp = 7218 CreateClearClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType, 7219 aPrincipalInfo, aClientType); 7220 7221 RegisterNormalOriginOp(*clearClientOp); 7222 7223 clearClientOp->RunImmediately(); 7224 7225 return Map<BoolPromise>( 7226 clearClientOp->OnResults(), 7227 [self = RefPtr(this)]( 7228 ClientMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7229 self->NoteUninitializedClients(aValue.ResolveValue()); 7230 return true; 7231 }); 7232 } 7233 7234 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix( 7235 const Maybe<PersistenceType>& aPersistenceType, 7236 const PrincipalInfo& aPrincipalInfo) { 7237 AssertIsOnOwningThread(); 7238 7239 auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp( 7240 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo); 7241 7242 RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp); 7243 7244 clearStoragesForOriginPrefixOp->RunImmediately(); 7245 7246 return Map<BoolPromise>( 7247 clearStoragesForOriginPrefixOp->OnResults(), 7248 [self = RefPtr(this)]( 7249 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7250 self->NoteUninitializedClients(aValue.ResolveValue()); 7251 self->NoteUninitializedOrigins(aValue.ResolveValue()); 7252 7253 return true; 7254 }); 7255 } 7256 7257 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern( 7258 const OriginAttributesPattern& aPattern) { 7259 AssertIsOnOwningThread(); 7260 7261 auto clearDataOp = 7262 CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern); 7263 7264 RegisterNormalOriginOp(*clearDataOp); 7265 7266 clearDataOp->RunImmediately(); 7267 7268 return Map<BoolPromise>( 7269 clearDataOp->OnResults(), 7270 [self = RefPtr(this)]( 7271 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7272 self->NoteUninitializedClients(aValue.ResolveValue()); 7273 self->NoteUninitializedOrigins(aValue.ResolveValue()); 7274 7275 return true; 7276 }); 7277 } 7278 7279 RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() { 7280 AssertIsOnOwningThread(); 7281 7282 auto clearPrivateRepositoryOp = 7283 CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this)); 7284 7285 RegisterNormalOriginOp(*clearPrivateRepositoryOp); 7286 7287 clearPrivateRepositoryOp->RunImmediately(); 7288 7289 return Map<BoolPromise>( 7290 clearPrivateRepositoryOp->OnResults(), 7291 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 7292 self->NoteUninitializedClients(PERSISTENCE_TYPE_PRIVATE); 7293 self->NoteUninitializedRepository(PERSISTENCE_TYPE_PRIVATE); 7294 7295 return aValue.ResolveValue(); 7296 }); 7297 } 7298 7299 RefPtr<BoolPromise> QuotaManager::ClearStorage() { 7300 AssertIsOnOwningThread(); 7301 7302 auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this)); 7303 7304 RegisterNormalOriginOp(*clearStorageOp); 7305 7306 clearStorageOp->RunImmediately(); 7307 7308 return clearStorageOp->OnResults()->Then( 7309 GetCurrentSerialEventTarget(), __func__, 7310 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 7311 if (aValue.IsReject()) { 7312 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 7313 } 7314 7315 self->mInitializedClients.Clear(); 7316 self->mInitializedOrigins.Clear(); 7317 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear(); 7318 self->mAllTemporaryOriginsInitialized = false; 7319 self->mTemporaryStorageInitialized = false; 7320 self->mStorageInitialized = false; 7321 7322 return BoolPromise::CreateAndResolve(true, __func__); 7323 }); 7324 } 7325 7326 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForOrigin( 7327 Maybe<PersistenceType> aPersistenceType, 7328 const PrincipalInfo& aPrincipalInfo) { 7329 AssertIsOnOwningThread(); 7330 7331 auto shutdownOriginOp = CreateShutdownOriginOp( 7332 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo); 7333 7334 RegisterNormalOriginOp(*shutdownOriginOp); 7335 7336 shutdownOriginOp->RunImmediately(); 7337 7338 return Map<BoolPromise>( 7339 shutdownOriginOp->OnResults(), 7340 [self = RefPtr(this)]( 7341 OriginMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7342 self->NoteUninitializedClients(aValue.ResolveValue()); 7343 self->NoteUninitializedOrigins(aValue.ResolveValue()); 7344 7345 return true; 7346 }); 7347 } 7348 7349 RefPtr<BoolPromise> QuotaManager::ShutdownStoragesForClient( 7350 Maybe<PersistenceType> aPersistenceType, 7351 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) { 7352 AssertIsOnOwningThread(); 7353 7354 auto shutdownClientOp = 7355 CreateShutdownClientOp(WrapMovingNotNullUnchecked(this), aPersistenceType, 7356 aPrincipalInfo, aClientType); 7357 7358 RegisterNormalOriginOp(*shutdownClientOp); 7359 7360 shutdownClientOp->RunImmediately(); 7361 7362 return Map<BoolPromise>( 7363 shutdownClientOp->OnResults(), 7364 [self = RefPtr(this)]( 7365 ClientMetadataArrayPromise::ResolveOrRejectValue&& aValue) { 7366 self->NoteUninitializedClients(aValue.ResolveValue()); 7367 7368 return true; 7369 }); 7370 } 7371 7372 RefPtr<BoolPromise> QuotaManager::ShutdownStorage( 7373 Maybe<OriginOperationCallbackOptions> aCallbackOptions, 7374 Maybe<OriginOperationCallbacks&> aCallbacks) { 7375 AssertIsOnOwningThread(); 7376 7377 auto shutdownStorageOp = 7378 CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this)); 7379 7380 RegisterNormalOriginOp(*shutdownStorageOp); 7381 7382 shutdownStorageOp->RunImmediately(); 7383 7384 if (aCallbackOptions.isSome() && aCallbacks.isSome()) { 7385 aCallbacks.ref() = shutdownStorageOp->GetCallbacks(aCallbackOptions.ref()); 7386 } 7387 7388 return shutdownStorageOp->OnResults()->Then( 7389 GetCurrentSerialEventTarget(), __func__, 7390 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) { 7391 if (aValue.IsReject()) { 7392 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__); 7393 } 7394 7395 self->mInitializedClients.Clear(); 7396 self->mInitializedOrigins.Clear(); 7397 self->mBackgroundThreadAccessible.Access()->mInitializedGroups.Clear(); 7398 self->mAllTemporaryOriginsInitialized = false; 7399 self->mTemporaryStorageInitialized = false; 7400 self->mStorageInitialized = false; 7401 7402 return BoolPromise::CreateAndResolve(true, __func__); 7403 }); 7404 } 7405 7406 void QuotaManager::ShutdownStorageInternal() { 7407 AssertIsOnIOThread(); 7408 7409 if (mStorageConnection) { 7410 mInitializationInfo.ResetOriginInitializationInfos(); 7411 mInitializedOriginsInternal.Clear(); 7412 7413 if (mTemporaryStorageInitializedInternal) { 7414 if (mCacheUsable) { 7415 UnloadQuota(); 7416 } else { 7417 RemoveQuota(); 7418 } 7419 7420 RemoveTemporaryOrigins(); 7421 7422 mTemporaryStorageInitializedInternal = false; 7423 } 7424 7425 ReleaseIOThreadObjects(); 7426 7427 mStorageConnection = nullptr; 7428 mCacheUsable = false; 7429 } 7430 7431 mInitializationInfo.ResetFirstInitializationAttempts(); 7432 } 7433 7434 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory( 7435 nsIFile& aDirectory) { 7436 AssertIsOnIOThread(); 7437 7438 QM_TRY_INSPECT(const bool& exists, 7439 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists)); 7440 7441 if (!exists) { 7442 QM_TRY_INSPECT( 7443 const auto& leafName, 7444 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName) 7445 .map([](const auto& leafName) { 7446 return NS_ConvertUTF16toUTF8(leafName); 7447 })); 7448 7449 QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE), 7450 [](const auto&) { 7451 QM_WARNING( 7452 "Preventing creation of a new origin directory which is not " 7453 "supported by our origin parser or is obsolete!"); 7454 }); 7455 } 7456 7457 QM_TRY_RETURN(EnsureDirectory(aDirectory)); 7458 } 7459 7460 nsresult QuotaManager::AboutToClearOrigins( 7461 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope, 7462 const ClientStorageScope& aClientStorageScope) { 7463 AssertIsOnIOThread(); 7464 MOZ_ASSERT(aClientStorageScope.IsClient() || aClientStorageScope.IsNull()); 7465 7466 if (aClientStorageScope.IsNull()) { 7467 for (Client::Type type : AllClientTypes()) { 7468 QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins( 7469 aPersistenceScope, aOriginScope))); 7470 } 7471 } else { 7472 QM_TRY(MOZ_TO_RESULT( 7473 (*mClients)[aClientStorageScope.GetClientType()]->AboutToClearOrigins( 7474 aPersistenceScope, aOriginScope))); 7475 } 7476 7477 return NS_OK; 7478 } 7479 7480 void QuotaManager::OriginClearCompleted( 7481 const OriginMetadata& aOriginMetadata, 7482 const ClientStorageScope& aClientStorageScope) { 7483 AssertIsOnIOThread(); 7484 MOZ_ASSERT(aClientStorageScope.IsClient() || aClientStorageScope.IsNull()); 7485 7486 if (aClientStorageScope.IsNull()) { 7487 if (aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 7488 mInitializedOriginsInternal.RemoveElement(aOriginMetadata.mOrigin); 7489 } else { 7490 RemoveTemporaryOrigin(aOriginMetadata); 7491 } 7492 7493 for (Client::Type type : AllClientTypes()) { 7494 (*mClients)[type]->OnOriginClearCompleted(aOriginMetadata); 7495 } 7496 } else { 7497 (*mClients)[aClientStorageScope.GetClientType()]->OnOriginClearCompleted( 7498 aOriginMetadata); 7499 } 7500 } 7501 7502 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) { 7503 AssertIsOnIOThread(); 7504 7505 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { 7506 mInitializedOriginsInternal.Clear(); 7507 } else { 7508 RemoveTemporaryOrigins(aPersistenceType); 7509 } 7510 7511 for (Client::Type type : AllClientTypes()) { 7512 (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType); 7513 } 7514 } 7515 7516 Client* QuotaManager::GetClient(Client::Type aClientType) { 7517 MOZ_ASSERT(aClientType >= Client::IDB); 7518 MOZ_ASSERT(aClientType < Client::TypeMax()); 7519 7520 return (*mClients)[aClientType]; 7521 } 7522 7523 const AutoTArray<Client::Type, Client::TYPE_MAX>& 7524 QuotaManager::AllClientTypes() { 7525 if (CachedNextGenLocalStorageEnabled()) { 7526 return *mAllClientTypes; 7527 } 7528 return *mAllClientTypesExceptLS; 7529 } 7530 7531 bool QuotaManager::IsThumbnailPrivateIdentityIdKnown() const { 7532 AssertIsOnIOThread(); 7533 7534 return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.isSome(); 7535 } 7536 7537 uint32_t QuotaManager::GetThumbnailPrivateIdentityId() const { 7538 AssertIsOnIOThread(); 7539 7540 return mIOThreadAccessible.Access()->mThumbnailPrivateIdentityId.ref(); 7541 } 7542 7543 void QuotaManager::SetThumbnailPrivateIdentityId( 7544 uint32_t aThumbnailPrivateIdentityId) { 7545 AssertIsOnIOThread(); 7546 7547 auto ioThreadData = mIOThreadAccessible.Access(); 7548 7549 ioThreadData->mThumbnailPrivateIdentityId = Some(aThumbnailPrivateIdentityId); 7550 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0; 7551 7552 for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done(); 7553 iter.Next()) { 7554 auto& array = iter.Data(); 7555 7556 for (const auto& originMetadata : array) { 7557 if (IsUserContextSuffix(originMetadata.mSuffix, 7558 GetThumbnailPrivateIdentityId())) { 7559 AssertNoOverflow( 7560 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1); 7561 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++; 7562 } 7563 } 7564 } 7565 } 7566 7567 /* static */ 7568 uint64_t QuotaManager::GetGroupLimitForLimit(uint64_t aLimit) { 7569 // To avoid one group evicting all the rest, limit the amount any one group 7570 // can use to 20% resp. a fifth. To prevent individual sites from using 7571 // exorbitant amounts of storage where there is a lot of free space, cap the 7572 // group limit to 10GB. 7573 const auto x = std::min<uint64_t>(aLimit / 5, 10 GB); 7574 7575 // In low-storage situations, make an exception (while not exceeding the total 7576 // storage limit). 7577 return std::min<uint64_t>(aLimit, std::max<uint64_t>(x, 10 MB)); 7578 } 7579 7580 uint64_t QuotaManager::GetGroupLimit() const { 7581 return GetGroupLimitForLimit(mTemporaryStorageLimit); 7582 } 7583 7584 Maybe<OriginStateMetadata> QuotaManager::GetOriginStateMetadata( 7585 const OriginMetadata& aOriginMetadata) { 7586 AssertIsOnIOThread(); 7587 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 7588 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal); 7589 7590 MutexAutoLock lock(mQuotaMutex); 7591 7592 RefPtr<OriginInfo> originInfo = 7593 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata); 7594 if (originInfo) { 7595 return Some(originInfo->LockedFlattenToOriginStateMetadata()); 7596 } 7597 7598 return Nothing(); 7599 } 7600 7601 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate( 7602 const OriginMetadata& aOriginMetadata) { 7603 AssertIsOnIOThread(); 7604 7605 uint64_t totalGroupUsage = 0; 7606 7607 { 7608 MutexAutoLock lock(mQuotaMutex); 7609 7610 GroupInfoPair* pair; 7611 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 7612 for (const PersistenceType type : kBestEffortPersistenceTypes) { 7613 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type); 7614 if (groupInfo) { 7615 if (type == PERSISTENCE_TYPE_DEFAULT) { 7616 RefPtr<OriginInfo> originInfo = 7617 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 7618 7619 if (originInfo && originInfo->LockedPersisted()) { 7620 return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit); 7621 } 7622 } 7623 7624 AssertNoOverflow(totalGroupUsage, groupInfo->mUsage); 7625 totalGroupUsage += groupInfo->mUsage; 7626 } 7627 } 7628 } 7629 } 7630 7631 return std::pair(totalGroupUsage, GetGroupLimit()); 7632 } 7633 7634 uint64_t QuotaManager::GetOriginUsage( 7635 const PrincipalMetadata& aPrincipalMetadata) { 7636 AssertIsOnIOThread(); 7637 7638 uint64_t usage = 0; 7639 7640 { 7641 MutexAutoLock lock(mQuotaMutex); 7642 7643 GroupInfoPair* pair; 7644 if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) { 7645 for (const PersistenceType type : kBestEffortPersistenceTypes) { 7646 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type); 7647 if (groupInfo) { 7648 RefPtr<OriginInfo> originInfo = 7649 groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin); 7650 if (originInfo) { 7651 AssertNoOverflow(usage, originInfo->LockedUsage()); 7652 usage += originInfo->LockedUsage(); 7653 } 7654 } 7655 } 7656 } 7657 } 7658 7659 return usage; 7660 } 7661 7662 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata( 7663 const OriginMetadata& aOriginMetadata) { 7664 AssertIsOnIOThread(); 7665 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection); 7666 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal); 7667 7668 MutexAutoLock lock(mQuotaMutex); 7669 7670 RefPtr<OriginInfo> originInfo = 7671 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata); 7672 if (originInfo) { 7673 return Some(originInfo->LockedFlattenToFullOriginMetadata()); 7674 } 7675 7676 return Nothing(); 7677 } 7678 7679 uint64_t QuotaManager::TotalDirectoryIterations() const { 7680 AssertIsOnIOThread(); 7681 7682 return mIOThreadAccessible.Access()->mTotalDirectoryIterations; 7683 } 7684 7685 uint64_t QuotaManager::SaveOriginAccessTimeCount() const { 7686 AssertIsOnOwningThread(); 7687 7688 return mBackgroundThreadAccessible.Access()->mSaveOriginAccessTimeCount; 7689 } 7690 7691 uint64_t QuotaManager::SaveOriginAccessTimeCountInternal() const { 7692 AssertIsOnIOThread(); 7693 7694 return mIOThreadAccessible.Access()->mSaveOriginAccessTimeCount; 7695 } 7696 7697 // static 7698 void QuotaManager::GetStorageId(PersistenceType aPersistenceType, 7699 const nsACString& aOrigin, 7700 Client::Type aClientType, 7701 nsACString& aDatabaseId) { 7702 nsAutoCString str; 7703 str.AppendInt(aPersistenceType); 7704 str.Append('*'); 7705 str.Append(aOrigin); 7706 str.Append('*'); 7707 str.AppendInt(aClientType); 7708 7709 aDatabaseId = str; 7710 } 7711 7712 // static 7713 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) { 7714 MOZ_ASSERT(!aOrigin.IsEmpty()); 7715 7716 // The first prompt is not required for these origins. 7717 if (aOrigin.EqualsLiteral(kChromeOrigin) || 7718 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) || 7719 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) || 7720 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) { 7721 return true; 7722 } 7723 7724 return false; 7725 } 7726 7727 // static 7728 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1, 7729 const nsACString& aOrigin2) { 7730 return MakeSanitizedOriginCString(aOrigin1) == 7731 MakeSanitizedOriginCString(aOrigin2); 7732 } 7733 7734 // static 7735 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin( 7736 const nsACString& aOrigin) { 7737 // An origin string either corresponds to a SystemPrincipalInfo or a 7738 // ContentPrincipalInfo, see 7739 // QuotaManager::GetOriginFromValidatedPrincipalInfo. 7740 7741 nsCString spec; 7742 OriginAttributes attrs; 7743 nsCString originalSuffix; 7744 const OriginParser::ResultType result = OriginParser::ParseOrigin( 7745 MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix); 7746 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin)); 7747 7748 QM_TRY_INSPECT( 7749 const auto& principal, 7750 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> { 7751 if (spec.EqualsLiteral(kChromeOrigin)) { 7752 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get()); 7753 } 7754 7755 nsCOMPtr<nsIURI> uri; 7756 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec))); 7757 7758 return nsCOMPtr<nsIPrincipal>( 7759 BasePrincipal::CreateContentPrincipal(uri, attrs)); 7760 }())); 7761 QM_TRY(MOZ_TO_RESULT(principal)); 7762 7763 PrincipalInfo principalInfo; 7764 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo))); 7765 7766 return std::move(principalInfo); 7767 } 7768 7769 // static 7770 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; } 7771 7772 uint64_t QuotaManager::LockedCollectOriginsForEviction( 7773 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) 7774 MOZ_REQUIRES(mQuotaMutex) { 7775 mQuotaMutex.AssertCurrentThreadOwns(); 7776 7777 RefPtr<CollectOriginsHelper> helper = 7778 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed); 7779 7780 // Unlock while calling out to XPCOM (code behind the dispatch method needs 7781 // to acquire its own lock which can potentially lead to a deadlock and it 7782 // also calls an observer that can do various stuff like IO, so it's better 7783 // to not hold our mutex while that happens). 7784 { 7785 MutexAutoUnlock autoUnlock(mQuotaMutex); 7786 7787 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL)); 7788 } 7789 7790 return helper->BlockAndReturnOriginsForEviction(aLocks); 7791 } 7792 7793 void QuotaManager::LockedRemoveQuotaForRepository( 7794 PersistenceType aPersistenceType) { 7795 mQuotaMutex.AssertCurrentThreadOwns(); 7796 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7797 7798 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { 7799 auto& pair = iter.Data(); 7800 7801 if (RefPtr<GroupInfo> groupInfo = 7802 pair->LockedGetGroupInfo(aPersistenceType)) { 7803 groupInfo->LockedRemoveOriginInfos(); 7804 7805 pair->LockedClearGroupInfo(aPersistenceType); 7806 7807 if (!pair->LockedHasGroupInfos()) { 7808 iter.Remove(); 7809 } 7810 } 7811 } 7812 } 7813 7814 void QuotaManager::LockedRemoveQuotaForOrigin( 7815 const OriginMetadata& aOriginMetadata) { 7816 mQuotaMutex.AssertCurrentThreadOwns(); 7817 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7818 7819 GroupInfoPair* pair; 7820 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 7821 return; 7822 } 7823 7824 MOZ_ASSERT(pair); 7825 7826 if (RefPtr<GroupInfo> groupInfo = 7827 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) { 7828 groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin); 7829 7830 if (!groupInfo->LockedHasOriginInfos()) { 7831 pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType); 7832 7833 if (!pair->LockedHasGroupInfos()) { 7834 mGroupInfoPairs.Remove(aOriginMetadata.mGroup); 7835 } 7836 } 7837 } 7838 } 7839 7840 bool QuotaManager::LockedHasGroupInfoPair(const nsACString& aGroup) const { 7841 mQuotaMutex.AssertCurrentThreadOwns(); 7842 7843 return mGroupInfoPairs.Get(aGroup, nullptr); 7844 } 7845 7846 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo( 7847 PersistenceType aPersistenceType, const nsACString& aSuffix, 7848 const nsACString& aGroup) { 7849 mQuotaMutex.AssertCurrentThreadOwns(); 7850 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7851 7852 GroupInfoPair* const pair = 7853 mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup); 7854 7855 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); 7856 if (!groupInfo) { 7857 groupInfo = new GroupInfo(pair, aPersistenceType); 7858 pair->LockedSetGroupInfo(aPersistenceType, groupInfo); 7859 } 7860 7861 return groupInfo.forget(); 7862 } 7863 7864 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo( 7865 PersistenceType aPersistenceType, 7866 const OriginMetadata& aOriginMetadata) const { 7867 mQuotaMutex.AssertCurrentThreadOwns(); 7868 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); 7869 7870 GroupInfoPair* pair; 7871 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) { 7872 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); 7873 if (groupInfo) { 7874 return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin); 7875 } 7876 } 7877 7878 return nullptr; 7879 } 7880 7881 template <typename Collect, typename Pred> 7882 QuotaManager::OriginInfosFlatTraversable 7883 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) { 7884 OriginInfosFlatTraversable originInfos; 7885 7886 std::forward<Collect>(aCollect)(MakeBackInserter(originInfos)); 7887 7888 originInfos.Sort(OriginInfoAccessTimeComparator()); 7889 7890 const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(), 7891 std::forward<Pred>(aPred)); 7892 7893 originInfos.TruncateLength(foundIt - originInfos.cbegin()); 7894 7895 return originInfos; 7896 } 7897 7898 QuotaManager::OriginInfosNestedTraversable 7899 QuotaManager::GetOriginInfosExceedingGroupLimit() const { 7900 MutexAutoLock lock(mQuotaMutex); 7901 7902 OriginInfosNestedTraversable originInfos; 7903 7904 for (const auto& entry : mGroupInfoPairs) { 7905 const auto& pair = entry.GetData(); 7906 7907 MOZ_ASSERT(!entry.GetKey().IsEmpty()); 7908 MOZ_ASSERT(pair); 7909 7910 uint64_t groupUsage = 0; 7911 7912 const RefPtr<GroupInfo> temporaryGroupInfo = 7913 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); 7914 if (temporaryGroupInfo) { 7915 groupUsage += temporaryGroupInfo->mUsage; 7916 } 7917 7918 const RefPtr<GroupInfo> defaultGroupInfo = 7919 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); 7920 if (defaultGroupInfo) { 7921 groupUsage += defaultGroupInfo->mUsage; 7922 } 7923 7924 const RefPtr<GroupInfo> privateGroupInfo = 7925 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE); 7926 if (privateGroupInfo) { 7927 groupUsage += privateGroupInfo->mUsage; 7928 } 7929 7930 if (groupUsage > 0) { 7931 QuotaManager* quotaManager = QuotaManager::Get(); 7932 MOZ_ASSERT(quotaManager, "Shouldn't be null!"); 7933 7934 if (groupUsage > quotaManager->GetGroupLimit()) { 7935 originInfos.AppendElement(CollectLRUOriginInfosUntil( 7936 [&pair](auto inserter) { 7937 pair->MaybeInsertNonPersistedOriginInfos(std::move(inserter)); 7938 }, 7939 [&groupUsage, quotaManager](const auto& originInfo) { 7940 groupUsage -= originInfo->LockedUsage(); 7941 7942 return groupUsage <= quotaManager->GetGroupLimit(); 7943 })); 7944 } 7945 } 7946 } 7947 7948 return originInfos; 7949 } 7950 7951 // Uses the same return type as the methods above, so the result can 7952 // eventually be passed to ClearOrigins to easily clear such origins. 7953 QuotaManager::OriginInfosNestedTraversable 7954 QuotaManager::GetOriginInfosExceedingGlobalLimit() const { 7955 MutexAutoLock lock(mQuotaMutex); 7956 7957 QuotaManager::OriginInfosNestedTraversable res; 7958 res.AppendElement(CollectLRUOriginInfosUntil( 7959 // XXX The lambda only needs to capture this, but due to Bug 1421435 it 7960 // can't. 7961 [&](auto inserter) { 7962 for (const auto& entry : mGroupInfoPairs) { 7963 const auto& pair = entry.GetData(); 7964 7965 MOZ_ASSERT(!entry.GetKey().IsEmpty()); 7966 MOZ_ASSERT(pair); 7967 7968 pair->MaybeInsertNonPersistedOriginInfos(inserter); 7969 } 7970 }, 7971 [temporaryStorageUsage = mTemporaryStorageUsage, 7972 temporaryStorageLimit = mTemporaryStorageLimit, 7973 doomedUsage = uint64_t{0}](const auto& originInfo) mutable { 7974 if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) { 7975 return true; 7976 } 7977 7978 doomedUsage += originInfo->LockedUsage(); 7979 return false; 7980 })); 7981 7982 return res; 7983 } 7984 7985 QuotaManager::OriginInfosNestedTraversable 7986 QuotaManager::GetOriginInfosWithZeroUsage( 7987 const Maybe<int64_t>& aCutoffAccessTime) const { 7988 MutexAutoLock lock(mQuotaMutex); 7989 7990 QuotaManager::OriginInfosNestedTraversable res; 7991 7992 OriginInfosFlatTraversable originInfos; 7993 7994 auto inserter = MakeBackInserter(originInfos); 7995 7996 for (const auto& entry : mGroupInfoPairs) { 7997 const auto& pair = entry.GetData(); 7998 7999 MOZ_ASSERT(!entry.GetKey().IsEmpty()); 8000 MOZ_ASSERT(pair); 8001 8002 pair->MaybeInsertNonPersistedZeroUsageOriginInfos(inserter, 8003 aCutoffAccessTime); 8004 } 8005 8006 res.AppendElement(std::move(originInfos)); 8007 8008 return res; 8009 } 8010 8011 // Based on discussions in 8012 // https://stackoverflow.com/questions/41847525/should-templated-functions-take-lambda-arguments-by-value-or-by-rvalue-reference 8013 // and 8014 // https://stackoverflow.com/questions/56988299/should-i-pass-a-lambda-by-const-reference, 8015 // passing a callable by universal (forwarding) reference is considered a more 8016 // flexible choice from an interface point of view. 8017 // 8018 // In future, we should also ensure this pattern is compatible with clang-tidy 8019 // checks such as modernize-pass-by-value and consider enabling 8020 // cppcoreguidelines-missing-std-forward to catch potential oversights. 8021 template <typename Checker> 8022 void QuotaManager::ClearOrigins( 8023 const OriginInfosNestedTraversable& aDoomedOriginInfos, Checker&& aChecker, 8024 const Maybe<size_t>& aMaxOriginsToClear) { 8025 AssertIsOnIOThread(); 8026 8027 if (QuotaManager::IsShuttingDown()) { 8028 // Don't block shutdown. 8029 return; 8030 } 8031 8032 auto doomedOriginInfos = 8033 Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos); 8034 8035 // If there's nothing to do, exit early. 8036 if (doomedOriginInfos.begin() == doomedOriginInfos.end()) { 8037 return; 8038 } 8039 8040 // It's safer to take ownership of the checker and keep it outside the loop 8041 // (the checker can have side effects or use move). 8042 std::decay_t<Checker> checker = std::forward<Checker>(aChecker); 8043 8044 // If we are in shutdown, we could break off early from clearing origins. 8045 // In such cases, we would like to track the ones that were already cleared 8046 // up, such that other essential cleanup could be performed on clearedOrigins. 8047 // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and 8048 // OriginClearCompleted below. We could have used a collection of OriginInfos 8049 // rather than flattening them to OriginMetadata but groupInfo in OriginInfo 8050 // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and 8051 // as a result, we would not be able to get origin persistence type required 8052 // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call. 8053 nsTArray<OriginMetadata> clearedOrigins; 8054 8055 // XXX Does this need to be done a) in order and/or b) sequentially? 8056 for (const auto& doomedOriginInfo : doomedOriginInfos) { 8057 std::invoke(checker, doomedOriginInfo); 8058 8059 // TODO: We are currently only checking for this flag here which 8060 // means that we cannot break off once we start cleaning an origin. It 8061 // could be better if we could check for shutdown flag while cleaning an 8062 // origin such that we could break off early from the cleaning process if 8063 // we are stuck cleaning on one huge origin. Bug1797098 has been filed to 8064 // track this. 8065 if (QuotaManager::IsShuttingDown()) { 8066 break; 8067 } 8068 8069 auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata(); 8070 8071 DeleteOriginDirectory(originMetadata); 8072 8073 clearedOrigins.AppendElement(std::move(originMetadata)); 8074 8075 if (aMaxOriginsToClear && clearedOrigins.Length() >= *aMaxOriginsToClear) { 8076 break; 8077 } 8078 } 8079 8080 { 8081 MutexAutoLock lock(mQuotaMutex); 8082 8083 for (const auto& clearedOrigin : clearedOrigins) { 8084 LockedRemoveQuotaForOrigin(clearedOrigin); 8085 } 8086 } 8087 8088 for (const auto& clearedOrigin : clearedOrigins) { 8089 OriginClearCompleted(clearedOrigin, ClientStorageScope::CreateFromNull()); 8090 } 8091 } 8092 8093 void QuotaManager::CleanupTemporaryStorage() { 8094 AssertIsOnIOThread(); 8095 8096 if (StaticPrefs:: 8097 dom_quotaManager_temporaryStorage_clearNonPersistedZeroUsageOrigins()) { 8098 // XXX Ideally the clearing would be done asynchronously by storage 8099 // maintenance service once available. 8100 8101 // We hardcode a 7-day cutoff for "recently used" origins. Even if such 8102 // origins have zero usage, skipping them avoids the performance penalty 8103 // of repeatedly recreating origin directories and metadata files. The 8104 // value is fixed for now to keep things simple, but could be made 8105 // configurable in the future if needed. 8106 static_assert(aDefaultCutoffAccessTime == kSecPerWeek * PR_USEC_PER_SEC); 8107 8108 // Calculate cutoff time (one week ago). PR_Now() returns microseconds 8109 // since epoch, so this cannot realistically overflow. 8110 const int64_t cutoffTime = PR_Now() - aDefaultCutoffAccessTime; 8111 8112 #ifdef DEBUG 8113 // Verify that origins being cleared meet our criteria: 8114 // non-persisted, zero usage, and outside cutoff window 8115 auto checker = [&self = *this, cutoffTime](const auto& doomedOriginInfo) { 8116 MutexAutoLock lock(self.mQuotaMutex); 8117 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); 8118 MOZ_ASSERT(doomedOriginInfo->LockedUsage() == 0); 8119 MOZ_ASSERT(doomedOriginInfo->LockedAccessTime() < cutoffTime); 8120 }; 8121 #else 8122 auto checker = [](const auto&) {}; 8123 #endif 8124 8125 const size_t maxOriginsToClear = StaticPrefs:: 8126 dom_quotaManager_temporaryStorage_maxOriginsToClearDuringCleanup(); 8127 8128 ClearOrigins(GetOriginInfosWithZeroUsage(Some(cutoffTime)), 8129 std::move(checker), Some(maxOriginsToClear)); 8130 } 8131 8132 // Evicting origins that exceed their group limit also affects the global 8133 // temporary storage usage, so these steps have to be taken sequentially. 8134 // Combining them doesn't seem worth the added complexity. 8135 8136 #ifdef DEBUG 8137 auto nonPersistedChecker = [&self = *this](const auto& doomedOriginInfo) { 8138 MutexAutoLock lock(self.mQuotaMutex); 8139 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); 8140 }; 8141 #else 8142 auto nonPersistedChecker = [](const auto&) {}; 8143 #endif 8144 8145 ClearOrigins(GetOriginInfosExceedingGroupLimit(), nonPersistedChecker); 8146 ClearOrigins(GetOriginInfosExceedingGlobalLimit(), 8147 std::move(nonPersistedChecker)); 8148 8149 if (mTemporaryStorageUsage > mTemporaryStorageLimit) { 8150 // If disk space is still low after origin clear, notify storage pressure. 8151 NotifyStoragePressure(*this, mTemporaryStorageUsage); 8152 } 8153 } 8154 8155 void QuotaManager::RecordTemporaryStorageMetrics() { 8156 AssertIsOnIOThread(); 8157 8158 { 8159 const auto originInfos = GetOriginInfosWithZeroUsage(); 8160 8161 auto range = Flatten<OriginInfosFlatTraversable::value_type>(originInfos); 8162 8163 size_t count = std::distance(range.begin(), range.end()); 8164 8165 glean::quotamanager_initialize_temporarystorage:: 8166 non_persisted_zero_usage_origins.AccumulateSingleSample(count); 8167 } 8168 } 8169 8170 void QuotaManager::DeleteOriginDirectory( 8171 const OriginMetadata& aOriginMetadata) { 8172 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata), 8173 QM_VOID); 8174 8175 nsresult rv = directory->Remove(true); 8176 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { 8177 // This should never fail if we've closed all storage connections 8178 // correctly... 8179 NS_ERROR("Failed to remove directory!"); 8180 } 8181 } 8182 8183 void QuotaManager::FinalizeOriginEviction( 8184 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) { 8185 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 8186 8187 auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable { 8188 QuotaManager* quotaManager = QuotaManager::Get(); 8189 MOZ_ASSERT(quotaManager); 8190 8191 RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp( 8192 WrapMovingNotNull(quotaManager), std::move(locks)); 8193 8194 op->RunImmediately(); 8195 }; 8196 8197 if (IsOnBackgroundThread()) { 8198 finalizeOriginEviction(); 8199 } else { 8200 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch( 8201 NS_NewRunnableFunction( 8202 "dom::quota::QuotaManager::FinalizeOriginEviction", 8203 std::move(finalizeOriginEviction)), 8204 NS_DISPATCH_NORMAL)); 8205 } 8206 } 8207 8208 Result<Ok, nsresult> QuotaManager::ArchiveOrigins( 8209 const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) { 8210 AssertIsOnIOThread(); 8211 MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty()); 8212 8213 QM_TRY_INSPECT(const auto& storageArchivesDir, 8214 QM_NewLocalFile(*mStorageArchivesPath)); 8215 8216 // Create another subdir, so once we decide to remove all temporary archives, 8217 // we can remove only the subdir and the parent directory can still be used 8218 // for something else or similar in future. Otherwise, we would have to 8219 // figure out a new name for it. 8220 QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns))); 8221 8222 PRExplodedTime now; 8223 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); 8224 8225 const auto dateStr = 8226 nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year, 8227 now.tm_month + 1, now.tm_mday); 8228 8229 QM_TRY_INSPECT( 8230 const auto& storageArchiveDir, 8231 CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr))); 8232 8233 QM_TRY(MOZ_TO_RESULT( 8234 storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700))); 8235 8236 QM_TRY_INSPECT(const auto& defaultStorageArchiveDir, 8237 CloneFileAndAppend(*storageArchiveDir, 8238 nsLiteralString(DEFAULT_DIRECTORY_NAME))); 8239 8240 QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir, 8241 CloneFileAndAppend(*storageArchiveDir, 8242 nsLiteralString(TEMPORARY_DIRECTORY_NAME))); 8243 8244 for (const auto& fullOriginMetadata : aFullOriginMetadatas) { 8245 MOZ_ASSERT( 8246 IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType)); 8247 8248 QM_TRY_INSPECT(const auto& directory, 8249 GetOriginDirectory(fullOriginMetadata)); 8250 8251 // The origin could have been removed, for example due to corruption. 8252 QM_TRY_INSPECT( 8253 const auto& moved, 8254 QM_OR_ELSE_WARN_IF( 8255 // Expression. 8256 MOZ_TO_RESULT( 8257 directory->MoveTo(fullOriginMetadata.mPersistenceType == 8258 PERSISTENCE_TYPE_DEFAULT 8259 ? defaultStorageArchiveDir 8260 : temporaryStorageArchiveDir, 8261 u""_ns)) 8262 .map([](Ok) { return true; }), 8263 // Predicate. 8264 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }), 8265 // Fallback. 8266 ErrToOk<false>)); 8267 8268 if (moved) { 8269 RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType, 8270 fullOriginMetadata); 8271 8272 RemoveTemporaryOrigin(fullOriginMetadata); 8273 } 8274 } 8275 8276 return Ok{}; 8277 } 8278 8279 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) { 8280 AssertIsOnIOThread(); 8281 8282 // Do not parse this sanitized origin string, if we already parsed it. 8283 return mValidOrigins.LookupOrInsertWith( 8284 aSanitizedOrigin, [&aSanitizedOrigin] { 8285 nsCString spec; 8286 OriginAttributes attrs; 8287 nsCString originalSuffix; 8288 const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec, 8289 &attrs, originalSuffix); 8290 8291 return result == OriginParser::ValidOrigin; 8292 }); 8293 } 8294 8295 void QuotaManager::AddTemporaryOrigin( 8296 const FullOriginMetadata& aFullOriginMetadata) { 8297 AssertIsOnIOThread(); 8298 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType)); 8299 8300 auto ioThreadData = mIOThreadAccessible.Access(); 8301 8302 auto& array = ioThreadData->mAllTemporaryOrigins.LookupOrInsert( 8303 aFullOriginMetadata.mGroup); 8304 8305 DebugOnly<bool> containsOrigin = array.Contains( 8306 aFullOriginMetadata, [](const auto& aLeft, const auto& aRight) { 8307 if (aLeft.mPersistenceType == aRight.mPersistenceType) { 8308 return Compare(aLeft.mOrigin, aRight.mOrigin); 8309 } 8310 return aLeft.mPersistenceType > aRight.mPersistenceType ? 1 : -1; 8311 }); 8312 MOZ_ASSERT(!containsOrigin); 8313 8314 array.AppendElement(aFullOriginMetadata); 8315 8316 if (IsThumbnailPrivateIdentityIdKnown() && 8317 IsUserContextSuffix(aFullOriginMetadata.mSuffix, 8318 GetThumbnailPrivateIdentityId())) { 8319 AssertNoOverflow( 8320 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1); 8321 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount++; 8322 } 8323 } 8324 8325 void QuotaManager::RemoveTemporaryOrigin( 8326 const OriginMetadata& aOriginMetadata) { 8327 AssertIsOnIOThread(); 8328 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType)); 8329 8330 auto ioThreadData = mIOThreadAccessible.Access(); 8331 8332 auto entry = 8333 ioThreadData->mAllTemporaryOrigins.Lookup(aOriginMetadata.mGroup); 8334 if (!entry) { 8335 return; 8336 } 8337 8338 auto& array = *entry; 8339 8340 DebugOnly<size_t> count = 8341 array.RemoveElementsBy([&aOriginMetadata](const auto& originMetadata) { 8342 return originMetadata.mPersistenceType == 8343 aOriginMetadata.mPersistenceType && 8344 originMetadata.mOrigin == aOriginMetadata.mOrigin; 8345 }); 8346 MOZ_ASSERT(count <= 1); 8347 8348 if (array.IsEmpty()) { 8349 entry.Remove(); 8350 } 8351 8352 if (IsThumbnailPrivateIdentityIdKnown() && 8353 IsUserContextSuffix(aOriginMetadata.mSuffix, 8354 GetThumbnailPrivateIdentityId())) { 8355 AssertNoUnderflow( 8356 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 1); 8357 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount--; 8358 } 8359 } 8360 8361 void QuotaManager::RemoveTemporaryOrigins(PersistenceType aPersistenceType) { 8362 AssertIsOnIOThread(); 8363 MOZ_ASSERT(IsBestEffortPersistenceType(aPersistenceType)); 8364 8365 auto ioThreadData = mIOThreadAccessible.Access(); 8366 8367 for (auto iter = ioThreadData->mAllTemporaryOrigins.Iter(); !iter.Done(); 8368 iter.Next()) { 8369 auto& array = iter.Data(); 8370 8371 uint32_t thumbnailPrivateIdentityTemporaryOriginCount = 0; 8372 8373 array.RemoveElementsBy([this, aPersistenceType, 8374 &thumbnailPrivateIdentityTemporaryOriginCount]( 8375 const auto& originMetadata) { 8376 const bool match = originMetadata.mPersistenceType == aPersistenceType; 8377 if (!match) { 8378 return false; 8379 } 8380 8381 if (IsThumbnailPrivateIdentityIdKnown() && 8382 IsUserContextSuffix(originMetadata.mSuffix, 8383 GetThumbnailPrivateIdentityId())) { 8384 AssertNoOverflow(thumbnailPrivateIdentityTemporaryOriginCount, 1); 8385 thumbnailPrivateIdentityTemporaryOriginCount++; 8386 } 8387 8388 return true; 8389 }); 8390 8391 if (array.IsEmpty()) { 8392 iter.Remove(); 8393 } 8394 8395 AssertNoUnderflow( 8396 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount, 8397 thumbnailPrivateIdentityTemporaryOriginCount); 8398 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount -= 8399 thumbnailPrivateIdentityTemporaryOriginCount; 8400 } 8401 } 8402 8403 void QuotaManager::RemoveTemporaryOrigins() { 8404 AssertIsOnIOThread(); 8405 8406 auto ioThreadData = mIOThreadAccessible.Access(); 8407 8408 ioThreadData->mAllTemporaryOrigins.Clear(); 8409 ioThreadData->mThumbnailPrivateIdentityTemporaryOriginCount = 0; 8410 } 8411 8412 PrincipalMetadataArray QuotaManager::GetAllTemporaryGroups() const { 8413 AssertIsOnIOThread(); 8414 8415 auto ioThreadData = mIOThreadAccessible.Access(); 8416 8417 PrincipalMetadataArray principalMetadataArray; 8418 8419 std::transform(ioThreadData->mAllTemporaryOrigins.Values().cbegin(), 8420 ioThreadData->mAllTemporaryOrigins.Values().cend(), 8421 MakeBackInserter(principalMetadataArray), 8422 [](const auto& array) { 8423 MOZ_ASSERT(!array.IsEmpty()); 8424 8425 // All items in the array have the same PrincipalMetadata, 8426 // so we can use any item to get it. 8427 return array[0]; 8428 }); 8429 8430 return principalMetadataArray; 8431 } 8432 8433 OriginMetadataArray QuotaManager::GetAllTemporaryOrigins() const { 8434 AssertIsOnIOThread(); 8435 8436 auto ioThreadData = mIOThreadAccessible.Access(); 8437 8438 OriginMetadataArray originMetadataArray; 8439 8440 for (auto iter = ioThreadData->mAllTemporaryOrigins.ConstIter(); !iter.Done(); 8441 iter.Next()) { 8442 const auto& array = iter.Data(); 8443 8444 for (const auto& originMetadata : array) { 8445 originMetadataArray.AppendElement(originMetadata); 8446 } 8447 } 8448 8449 return originMetadataArray; 8450 } 8451 8452 uint32_t QuotaManager::ThumbnailPrivateIdentityTemporaryOriginCount() const { 8453 AssertIsOnIOThread(); 8454 MOZ_ASSERT(IsThumbnailPrivateIdentityIdKnown()); 8455 8456 return mIOThreadAccessible.Access() 8457 ->mThumbnailPrivateIdentityTemporaryOriginCount; 8458 } 8459 8460 void QuotaManager::NoteInitializedOrigin(PersistenceType aPersistenceType, 8461 const nsACString& aOrigin) { 8462 AssertIsOnOwningThread(); 8463 8464 auto& boolArray = mInitializedOrigins.LookupOrInsertWith(aOrigin, []() { 8465 BoolArray boolArray; 8466 boolArray.AppendElements(PERSISTENCE_TYPE_INVALID); 8467 std::fill(boolArray.begin(), boolArray.end(), false); 8468 return boolArray; 8469 }); 8470 8471 boolArray[aPersistenceType] = true; 8472 } 8473 8474 void QuotaManager::NoteUninitializedOrigins( 8475 const OriginMetadataArray& aOriginMetadataArray) { 8476 AssertIsOnOwningThread(); 8477 8478 for (const auto& originMetadata : aOriginMetadataArray) { 8479 auto entry = mInitializedOrigins.Lookup(originMetadata.mOrigin); 8480 if (!entry) { 8481 return; 8482 } 8483 8484 auto& boolArray = *entry; 8485 8486 if (boolArray[originMetadata.mPersistenceType]) { 8487 boolArray[originMetadata.mPersistenceType] = false; 8488 8489 if (std::all_of(boolArray.begin(), boolArray.end(), 8490 [](bool entry) { return !entry; })) { 8491 entry.Remove(); 8492 } 8493 } 8494 } 8495 } 8496 8497 void QuotaManager::NoteUninitializedRepository( 8498 PersistenceType aPersistenceType) { 8499 AssertIsOnOwningThread(); 8500 8501 for (auto iter = mInitializedOrigins.Iter(); !iter.Done(); iter.Next()) { 8502 auto& boolArray = iter.Data(); 8503 8504 if (boolArray[aPersistenceType]) { 8505 boolArray[aPersistenceType] = false; 8506 8507 if (std::all_of(boolArray.begin(), boolArray.end(), 8508 [](bool entry) { return !entry; })) { 8509 iter.Remove(); 8510 } 8511 } 8512 } 8513 } 8514 8515 bool QuotaManager::IsOriginInitialized(PersistenceType aPersistenceType, 8516 const nsACString& aOrigin) const { 8517 AssertIsOnOwningThread(); 8518 8519 const auto entry = mInitializedOrigins.Lookup(aOrigin); 8520 8521 return entry && (*entry)[aPersistenceType]; 8522 } 8523 8524 void QuotaManager::NoteInitializedClient(PersistenceType aPersistenceType, 8525 const nsACString& aOrigin, 8526 Client::Type aClientType) { 8527 AssertIsOnOwningThread(); 8528 8529 auto& bitSetArray = mInitializedClients.LookupOrInsertWith(aOrigin, []() { 8530 BitSetArray bitSetArray; 8531 bitSetArray.AppendElements(PERSISTENCE_TYPE_INVALID); 8532 return bitSetArray; 8533 }); 8534 8535 bitSetArray[aPersistenceType][aClientType] = true; 8536 } 8537 8538 void QuotaManager::NoteUninitializedClients( 8539 const ClientMetadataArray& aClientMetadataArray) { 8540 AssertIsOnOwningThread(); 8541 8542 for (const auto& clientMetadata : aClientMetadataArray) { 8543 auto entry = mInitializedClients.Lookup(clientMetadata.mOrigin); 8544 if (!entry) { 8545 return; 8546 } 8547 8548 auto& bitSetArray = *entry; 8549 8550 if (bitSetArray[clientMetadata.mPersistenceType] 8551 [clientMetadata.mClientType]) { 8552 bitSetArray[clientMetadata.mPersistenceType][clientMetadata.mClientType] = 8553 false; 8554 8555 if (std::all_of(bitSetArray.begin(), bitSetArray.end(), 8556 [](const auto& entry) { return entry.IsEmpty(); })) { 8557 entry.Remove(); 8558 } 8559 } 8560 } 8561 } 8562 8563 void QuotaManager::NoteUninitializedClients( 8564 const OriginMetadataArray& aOriginMetadataArray) { 8565 AssertIsOnOwningThread(); 8566 8567 for (const auto& originMetadata : aOriginMetadataArray) { 8568 auto entry = mInitializedClients.Lookup(originMetadata.mOrigin); 8569 if (!entry) { 8570 return; 8571 } 8572 8573 auto& bitSetArray = *entry; 8574 8575 bitSetArray[originMetadata.mPersistenceType].ResetAll(); 8576 8577 if (std::all_of(bitSetArray.begin(), bitSetArray.end(), 8578 [](const auto& entry) { return entry.IsEmpty(); })) { 8579 entry.Remove(); 8580 } 8581 } 8582 } 8583 8584 void QuotaManager::NoteUninitializedClients(PersistenceType aPersistenceType) { 8585 AssertIsOnOwningThread(); 8586 8587 for (auto iter = mInitializedClients.Iter(); !iter.Done(); iter.Next()) { 8588 auto& bitSetArray = iter.Data(); 8589 8590 bitSetArray[aPersistenceType].ResetAll(); 8591 8592 if (std::all_of(bitSetArray.begin(), bitSetArray.end(), 8593 [](const auto& entry) { return entry.IsEmpty(); })) { 8594 iter.Remove(); 8595 } 8596 } 8597 } 8598 8599 bool QuotaManager::IsClientInitialized(PersistenceType aPersistenceType, 8600 const nsACString& aOrigin, 8601 Client::Type aClientType) const { 8602 AssertIsOnOwningThread(); 8603 8604 const auto entry = mInitializedClients.Lookup(aOrigin); 8605 8606 return entry && (*entry)[aPersistenceType][aClientType]; 8607 } 8608 8609 Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin( 8610 const nsACString& aOrigin) { 8611 MutexAutoLock lock(mQuotaMutex); 8612 8613 QM_TRY_UNWRAP( 8614 auto storageOrigin, 8615 mOriginToStorageOriginMap.TryLookupOrInsertWith( 8616 aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> { 8617 OriginAttributes originAttributes; 8618 8619 nsCString originNoSuffix; 8620 QM_TRY(MOZ_TO_RESULT( 8621 originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix))); 8622 8623 nsCOMPtr<nsIURI> uri; 8624 QM_TRY(MOZ_TO_RESULT( 8625 NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) 8626 .SetSpec(originNoSuffix) 8627 .SetScheme(kUUIDOriginScheme) 8628 .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID())) 8629 .SetPort(-1) 8630 .Finalize(uri))); 8631 8632 nsCOMPtr<nsIPrincipal> principal = 8633 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{}); 8634 QM_TRY(MOZ_TO_RESULT(principal)); 8635 8636 QM_TRY_UNWRAP(auto origin, 8637 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 8638 nsAutoCString, principal, GetOrigin)); 8639 8640 mStorageOriginToOriginMap.WithEntryHandle( 8641 origin, 8642 [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); }); 8643 8644 return nsCString(std::move(origin)); 8645 })); 8646 8647 return nsCString(std::move(storageOrigin)); 8648 } 8649 8650 Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin( 8651 const nsACString& aStorageOrigin) { 8652 MutexAutoLock lock(mQuotaMutex); 8653 8654 auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin); 8655 if (maybeOrigin.isNothing()) { 8656 return Err(NS_ERROR_FAILURE); 8657 } 8658 8659 return maybeOrigin.ref(); 8660 } 8661 8662 int64_t QuotaManager::GenerateDirectoryLockId() { 8663 const int64_t directorylockId = mNextDirectoryLockId; 8664 8665 if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1; 8666 result.isValid()) { 8667 mNextDirectoryLockId = result.value(); 8668 } else { 8669 NS_WARNING("Quota manager has run out of ids for directory locks!"); 8670 8671 // There's very little chance for this to happen given the max size of 8672 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId 8673 // to zero since such old directory locks shouldn't exist anymore. 8674 mNextDirectoryLockId = 0; 8675 } 8676 8677 // TODO: Maybe add an assertion here to check that there is no existing 8678 // directory lock with given id. 8679 8680 return directorylockId; 8681 } 8682 8683 template <typename UpdateCallback> 8684 void QuotaManager::RegisterClientDirectoryLockHandle( 8685 const OriginMetadata& aOriginMetadata, UpdateCallback&& aUpdateCallback) { 8686 AssertIsOnOwningThread(); 8687 8688 auto backgroundThreadData = mBackgroundThreadAccessible.Access(); 8689 8690 auto& openClientDirectoryInfo = 8691 backgroundThreadData->mOpenClientDirectoryInfos.LookupOrInsert( 8692 aOriginMetadata.GetCompositeKey()); 8693 8694 openClientDirectoryInfo.IncreaseClientDirectoryLockHandleCount(); 8695 8696 bool firstHandle = 8697 (openClientDirectoryInfo.ClientDirectoryLockHandleCount() == 1); 8698 8699 if (firstHandle) { 8700 std::forward<UpdateCallback>(aUpdateCallback)(openClientDirectoryInfo); 8701 } 8702 } 8703 8704 template <typename Callback> 8705 auto QuotaManager::WithOpenClientDirectoryInfo( 8706 const OriginMetadata& aOriginMetadata, Callback&& aCallback) 8707 -> std::invoke_result_t<Callback, OpenClientDirectoryInfo&> { 8708 AssertIsOnOwningThread(); 8709 8710 auto backgroundThreadData = mBackgroundThreadAccessible.Access(); 8711 8712 auto entry = backgroundThreadData->mOpenClientDirectoryInfos.Lookup( 8713 aOriginMetadata.GetCompositeKey()); 8714 MOZ_ASSERT(entry); 8715 8716 auto& openClientDirectoryInfo = entry.Data(); 8717 8718 return std::forward<Callback>(aCallback)(openClientDirectoryInfo); 8719 } 8720 8721 template <typename UpdateCallback> 8722 void QuotaManager::UnregisterClientDirectoryLockHandle( 8723 const OriginMetadata& aOriginMetadata, UpdateCallback&& aUpdateCallback) { 8724 AssertIsOnOwningThread(); 8725 8726 auto backgroundThreadData = mBackgroundThreadAccessible.Access(); 8727 8728 auto entry = backgroundThreadData->mOpenClientDirectoryInfos.Lookup( 8729 aOriginMetadata.GetCompositeKey()); 8730 MOZ_ASSERT(entry); 8731 8732 auto& openClientDirectoryInfo = entry.Data(); 8733 8734 openClientDirectoryInfo.DecreaseClientDirectoryLockHandleCount(); 8735 8736 bool lastHandle = 8737 openClientDirectoryInfo.ClientDirectoryLockHandleCount() == 0; 8738 8739 if (lastHandle) { 8740 std::forward<UpdateCallback>(aUpdateCallback)(openClientDirectoryInfo); 8741 8742 entry.Remove(); 8743 } 8744 } 8745 8746 void QuotaManager::ClientDirectoryLockHandleDestroy( 8747 ClientDirectoryLockHandle& aHandle) { 8748 AssertIsOnOwningThread(); 8749 MOZ_ASSERT(aHandle); 8750 8751 if (!aHandle.IsRegistered()) { 8752 return; 8753 } 8754 8755 const OriginMetadata originMetadata = aHandle->OriginMetadata(); 8756 8757 UnregisterClientDirectoryLockHandle( 8758 originMetadata, [&self = *this, &originMetadata]( 8759 OpenClientDirectoryInfo& aOpenClientDirectoryInfo) { 8760 if (originMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT && 8761 aOpenClientDirectoryInfo.HasLastAccessDirectoryLock()) { 8762 self.SaveOriginAccessTime( 8763 originMetadata, 8764 aOpenClientDirectoryInfo.ForgetLastAccessDirectoryLock()); 8765 } 8766 }); 8767 8768 aHandle.SetRegistered(false); 8769 } 8770 8771 template <typename Func> 8772 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization, 8773 Func&& aFunc) 8774 -> std::invoke_result_t<Func, const FirstInitializationAttempt< 8775 Initialization, StringGenerator>&> { 8776 return quota::ExecuteInitialization(mInitializationInfo, aInitialization, 8777 std::forward<Func>(aFunc)); 8778 } 8779 8780 template <typename Func> 8781 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization, 8782 const nsACString& aContext, 8783 Func&& aFunc) 8784 -> std::invoke_result_t<Func, const FirstInitializationAttempt< 8785 Initialization, StringGenerator>&> { 8786 return quota::ExecuteInitialization(mInitializationInfo, aInitialization, 8787 aContext, std::forward<Func>(aFunc)); 8788 } 8789 8790 template <typename Func> 8791 auto QuotaManager::ExecuteGroupInitialization( 8792 const nsACString& aGroup, const GroupInitialization aInitialization, 8793 const nsACString& aContext, Func&& aFunc) 8794 -> std::invoke_result_t<Func, const FirstInitializationAttempt< 8795 Initialization, StringGenerator>&> { 8796 return quota::ExecuteInitialization( 8797 mInitializationInfo.MutableGroupInitializationInfoRef( 8798 aGroup, CreateIfNonExistent{}), 8799 aInitialization, aContext, std::forward<Func>(aFunc)); 8800 } 8801 8802 template <typename Func> 8803 auto QuotaManager::ExecuteOriginInitialization( 8804 const nsACString& aOrigin, const OriginInitialization aInitialization, 8805 const nsACString& aContext, Func&& aFunc) 8806 -> std::invoke_result_t<Func, const FirstInitializationAttempt< 8807 Initialization, StringGenerator>&> { 8808 return quota::ExecuteInitialization( 8809 mInitializationInfo.MutableOriginInitializationInfoRef( 8810 aOrigin, CreateIfNonExistent{}), 8811 aInitialization, aContext, std::forward<Func>(aFunc)); 8812 } 8813 8814 void QuotaManager::IncreaseTotalDirectoryIterations() { 8815 AssertIsOnIOThread(); 8816 8817 auto ioThreadData = mIOThreadAccessible.Access(); 8818 8819 AssertNoOverflow(ioThreadData->mTotalDirectoryIterations, 1); 8820 ioThreadData->mTotalDirectoryIterations++; 8821 } 8822 8823 void QuotaManager::IncreaseSaveOriginAccessTimeCount() { 8824 AssertIsOnOwningThread(); 8825 8826 auto backgroundThreadData = mBackgroundThreadAccessible.Access(); 8827 8828 AssertNoOverflow(backgroundThreadData->mSaveOriginAccessTimeCount, 1); 8829 backgroundThreadData->mSaveOriginAccessTimeCount++; 8830 } 8831 8832 void QuotaManager::IncreaseSaveOriginAccessTimeCountInternal() { 8833 AssertIsOnIOThread(); 8834 8835 auto ioThreadData = mIOThreadAccessible.Access(); 8836 8837 AssertNoOverflow(ioThreadData->mSaveOriginAccessTimeCount, 1); 8838 ioThreadData->mSaveOriginAccessTimeCount++; 8839 } 8840 8841 /******************************************************************************* 8842 * Local class implementations 8843 ******************************************************************************/ 8844 8845 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex, 8846 uint64_t aMinSizeToBeFreed) 8847 : Runnable("dom::quota::CollectOriginsHelper"), 8848 mMinSizeToBeFreed(aMinSizeToBeFreed), 8849 mMutex(aMutex), 8850 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"), 8851 mSizeToBeFreed(0), 8852 mWaiting(true) { 8853 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); 8854 mMutex.AssertCurrentThreadOwns(); 8855 } 8856 8857 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction( 8858 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) { 8859 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); 8860 mMutex.AssertCurrentThreadOwns(); 8861 8862 while (mWaiting) { 8863 mCondVar.Wait(); 8864 } 8865 8866 mLocks.SwapElements(aLocks); 8867 return mSizeToBeFreed; 8868 } 8869 8870 NS_IMETHODIMP 8871 CollectOriginsHelper::Run() { 8872 AssertIsOnBackgroundThread(); 8873 8874 QuotaManager* quotaManager = QuotaManager::Get(); 8875 NS_ASSERTION(quotaManager, "Shouldn't be null!"); 8876 8877 // We use extra stack vars here to avoid race detector warnings (the same 8878 // memory accessed with and without the lock held). 8879 nsTArray<RefPtr<OriginDirectoryLock>> locks; 8880 uint64_t sizeToBeFreed = 8881 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks); 8882 8883 MutexAutoLock lock(mMutex); 8884 8885 NS_ASSERTION(mWaiting, "Huh?!"); 8886 8887 mLocks.SwapElements(locks); 8888 mSizeToBeFreed = sizeToBeFreed; 8889 mWaiting = false; 8890 mCondVar.Notify(); 8891 8892 return NS_OK; 8893 } 8894 8895 TimeStamp RecordTimeDeltaHelper::Start() { 8896 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread()); 8897 8898 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before 8899 // gLastOSWake is set, then this time duration would still be recorded with 8900 // key "Normal". We are assumming this is rather rare to happen. 8901 mStartTime.init(TimeStamp::Now()); 8902 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); 8903 8904 return *mStartTime; 8905 } 8906 8907 TimeStamp RecordTimeDeltaHelper::End() { 8908 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread()); 8909 8910 mEndTime.init(TimeStamp::Now()); 8911 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); 8912 8913 return *mEndTime; 8914 } 8915 8916 NS_IMETHODIMP 8917 RecordTimeDeltaHelper::Run() { 8918 MOZ_ASSERT(NS_IsMainThread()); 8919 8920 if (mInitializedTime.isSome()) { 8921 // Labels for glean::dom_quota::info_load_time and 8922 // glean::dom_quota::shutdown_time: 8923 // Normal: Normal conditions. 8924 // WasSuspended: There was a OS sleep so that it was suspended. 8925 // TimeStampErr1: The recorded start time is unexpectedly greater than the 8926 // end time. 8927 // TimeStampErr2: The initialized time for the recording class is unexpectly 8928 // greater than the last OS wake time. 8929 const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() { 8930 if (wasSuspended) { 8931 return "WasSuspended"_ns; 8932 } 8933 8934 // XXX File a bug if we have data for this key. 8935 // We found negative values in our query in STMO for 8936 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen 8937 // because the documentation for TimeStamp::Now() says it returns a 8938 // monotonically increasing number. 8939 if (*mStartTime > *mEndTime) { 8940 return "TimeStampErr1"_ns; 8941 } 8942 8943 if (*mInitializedTime > gLastOSWake) { 8944 return "TimeStampErr2"_ns; 8945 } 8946 8947 return "Normal"_ns; 8948 }(); 8949 8950 mMetric.Get(key).AccumulateRawDuration(*mEndTime - *mStartTime); 8951 8952 return NS_OK; 8953 } 8954 8955 gLastOSWake = TimeStamp::Now(); 8956 mInitializedTime.init(gLastOSWake); 8957 8958 return NS_OK; 8959 } 8960 8961 // static 8962 nsresult StorageOperationBase::CreateDirectoryMetadata( 8963 nsIFile& aDirectory, int64_t aTimestamp, 8964 const OriginMetadata& aOriginMetadata) { 8965 AssertIsOnIOThread(); 8966 8967 StorageOriginAttributes groupAttributes; 8968 8969 nsCString groupNoSuffix; 8970 QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup, 8971 groupNoSuffix)), 8972 NS_ERROR_FAILURE); 8973 8974 nsCString groupPrefix; 8975 GetJarPrefix(groupAttributes.InIsolatedMozBrowser(), groupPrefix); 8976 8977 nsCString group = groupPrefix + groupNoSuffix; 8978 8979 StorageOriginAttributes originAttributes; 8980 8981 nsCString originNoSuffix; 8982 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin, 8983 originNoSuffix)), 8984 NS_ERROR_FAILURE); 8985 8986 nsCString originPrefix; 8987 GetJarPrefix(originAttributes.InIsolatedMozBrowser(), originPrefix); 8988 8989 nsCString origin = originPrefix + originNoSuffix; 8990 8991 MOZ_ASSERT(groupPrefix == originPrefix); 8992 8993 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 8994 nsCOMPtr<nsIFile>, aDirectory, Clone)); 8995 8996 QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME)))); 8997 8998 QM_TRY_INSPECT(const auto& stream, 8999 GetBinaryOutputStream(*file, FileFlag::Truncate)); 9000 MOZ_ASSERT(stream); 9001 9002 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp))); 9003 9004 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get()))); 9005 9006 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get()))); 9007 9008 // Currently unused (used to be isApp). 9009 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false))); 9010 9011 QM_TRY(MOZ_TO_RESULT(stream->Flush())); 9012 9013 QM_TRY(MOZ_TO_RESULT(stream->Close())); 9014 9015 QM_TRY(MOZ_TO_RESULT( 9016 file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME)))); 9017 9018 return NS_OK; 9019 } 9020 9021 // static 9022 nsresult StorageOperationBase::CreateDirectoryMetadata2( 9023 nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted, 9024 const OriginMetadata& aOriginMetadata) { 9025 AssertIsOnIOThread(); 9026 9027 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9028 nsCOMPtr<nsIFile>, aDirectory, Clone)); 9029 9030 QM_TRY( 9031 MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME)))); 9032 9033 QM_TRY_INSPECT(const auto& stream, 9034 GetBinaryOutputStream(*file, FileFlag::Truncate)); 9035 MOZ_ASSERT(stream); 9036 9037 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp))); 9038 9039 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted))); 9040 9041 // Reserved data 1 9042 QM_TRY(MOZ_TO_RESULT(stream->Write32(0))); 9043 9044 // Reserved data 2 9045 QM_TRY(MOZ_TO_RESULT(stream->Write32(0))); 9046 9047 // The suffix isn't used right now, but we might need it in future. It's 9048 // a bit of redundancy we can live with given how painful is to upgrade 9049 // metadata files. 9050 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mSuffix.get()))); 9051 9052 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mGroup.get()))); 9053 9054 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mOrigin.get()))); 9055 9056 // Currently unused (used to be isApp). 9057 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false))); 9058 9059 QM_TRY(MOZ_TO_RESULT(stream->Flush())); 9060 9061 QM_TRY(MOZ_TO_RESULT(stream->Close())); 9062 9063 QM_TRY(MOZ_TO_RESULT( 9064 file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME)))); 9065 9066 return NS_OK; 9067 } 9068 9069 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory, 9070 int64_t& aTimestamp, 9071 nsACString& aGroup, 9072 nsACString& aOrigin, 9073 Nullable<bool>& aIsApp) { 9074 AssertIsOnIOThread(); 9075 MOZ_ASSERT(aDirectory); 9076 9077 QM_TRY_INSPECT( 9078 const auto& binaryStream, 9079 GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME))); 9080 9081 QM_TRY_INSPECT(const uint64_t& timestamp, 9082 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64)); 9083 9084 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9085 nsCString, binaryStream, ReadCString)); 9086 9087 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9088 nsCString, binaryStream, ReadCString)); 9089 9090 Nullable<bool> isApp; 9091 bool value; 9092 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) { 9093 isApp.SetValue(value); 9094 } 9095 9096 aTimestamp = timestamp; 9097 aGroup = group; 9098 aOrigin = origin; 9099 aIsApp = std::move(isApp); 9100 return NS_OK; 9101 } 9102 9103 nsresult StorageOperationBase::GetDirectoryMetadata2( 9104 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix, 9105 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) { 9106 AssertIsOnIOThread(); 9107 MOZ_ASSERT(aDirectory); 9108 9109 QM_TRY_INSPECT(const auto& binaryStream, 9110 GetBinaryInputStream(*aDirectory, 9111 nsLiteralString(METADATA_V2_FILE_NAME))); 9112 9113 QM_TRY_INSPECT(const uint64_t& timestamp, 9114 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64)); 9115 9116 QM_TRY_INSPECT(const bool& persisted, 9117 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean)); 9118 (void)persisted; 9119 9120 QM_TRY_INSPECT(const bool& reservedData1, 9121 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32)); 9122 (void)reservedData1; 9123 9124 QM_TRY_INSPECT(const bool& reservedData2, 9125 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32)); 9126 (void)reservedData2; 9127 9128 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9129 nsCString, binaryStream, ReadCString)); 9130 9131 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9132 nsCString, binaryStream, ReadCString)); 9133 9134 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9135 nsCString, binaryStream, ReadCString)); 9136 9137 QM_TRY_INSPECT(const bool& isApp, 9138 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean)); 9139 9140 aTimestamp = timestamp; 9141 aSuffix = suffix; 9142 aGroup = group; 9143 aOrigin = origin; 9144 aIsApp = isApp; 9145 return NS_OK; 9146 } 9147 9148 int64_t StorageOperationBase::GetOriginLastModifiedTime( 9149 const OriginProps& aOriginProps) { 9150 return GetLastModifiedTime(*aOriginProps.mPersistenceType, 9151 *aOriginProps.mDirectory); 9152 } 9153 9154 nsresult StorageOperationBase::RemoveObsoleteOrigin( 9155 const OriginProps& aOriginProps) { 9156 AssertIsOnIOThread(); 9157 9158 QM_WARNING( 9159 "Deleting obsolete %s directory that is no longer a legal " 9160 "origin!", 9161 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get()); 9162 9163 QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true))); 9164 9165 return NS_OK; 9166 } 9167 9168 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin( 9169 const OriginProps& aOriginProps) { 9170 AssertIsOnIOThread(); 9171 9172 const nsAString& oldLeafName = aOriginProps.mLeafName; 9173 9174 const auto newLeafName = 9175 MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin); 9176 9177 if (oldLeafName == newLeafName) { 9178 return false; 9179 } 9180 9181 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(*aOriginProps.mDirectory, 9182 aOriginProps.mTimestamp, 9183 aOriginProps.mOriginMetadata))); 9184 9185 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2( 9186 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9187 /* aPersisted */ false, aOriginProps.mOriginMetadata))); 9188 9189 QM_TRY_INSPECT(const auto& newFile, 9190 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9191 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent)); 9192 9193 QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName))); 9194 9195 QM_TRY_INSPECT(const bool& exists, 9196 MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists)); 9197 9198 if (exists) { 9199 QM_WARNING( 9200 "Can't rename %s directory to %s, the target already exists, removing " 9201 "instead of renaming!", 9202 NS_ConvertUTF16toUTF8(oldLeafName).get(), 9203 NS_ConvertUTF16toUTF8(newLeafName).get()); 9204 } 9205 9206 QM_TRY(CallWithDelayedRetriesIfAccessDenied( 9207 [&exists, &aOriginProps, &newLeafName] { 9208 if (exists) { 9209 QM_TRY_RETURN(MOZ_TO_RESULT( 9210 aOriginProps.mDirectory->Remove(/* recursive */ true))); 9211 } 9212 QM_TRY_RETURN(MOZ_TO_RESULT( 9213 aOriginProps.mDirectory->RenameTo(nullptr, newLeafName))); 9214 }, 9215 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(), 9216 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs())); 9217 9218 return true; 9219 } 9220 9221 nsresult StorageOperationBase::ProcessOriginDirectories() { 9222 AssertIsOnIOThread(); 9223 MOZ_ASSERT(!mOriginProps.IsEmpty()); 9224 9225 QuotaManager* quotaManager = QuotaManager::Get(); 9226 MOZ_ASSERT(quotaManager); 9227 9228 for (auto& originProps : mOriginProps) { 9229 switch (originProps.mType) { 9230 case OriginProps::eChrome: { 9231 originProps.mOriginMetadata = {GetInfoForChrome(), 9232 *originProps.mPersistenceType}; 9233 break; 9234 } 9235 9236 case OriginProps::eContent: { 9237 nsCOMPtr<nsIURI> uri; 9238 QM_TRY( 9239 MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec))); 9240 9241 nsCOMPtr<nsIPrincipal> principal = 9242 BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs); 9243 QM_TRY(MOZ_TO_RESULT(principal)); 9244 9245 PrincipalInfo principalInfo; 9246 QM_TRY( 9247 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo))); 9248 9249 QM_WARNONLY_TRY_UNWRAP( 9250 auto valid, MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo))); 9251 9252 if (!valid) { 9253 // Unknown directories during upgrade are allowed. Just warn if we 9254 // find them. 9255 UNKNOWN_FILE_WARNING(originProps.mLeafName); 9256 originProps.mIgnore = true; 9257 break; 9258 } 9259 9260 QM_TRY_UNWRAP( 9261 auto principalMetadata, 9262 GetInfoFromValidatedPrincipalInfo(*quotaManager, principalInfo)); 9263 9264 originProps.mOriginMetadata = {std::move(principalMetadata), 9265 *originProps.mPersistenceType}; 9266 9267 break; 9268 } 9269 9270 case OriginProps::eObsolete: { 9271 // There's no way to get info for obsolete origins. 9272 break; 9273 } 9274 9275 default: 9276 MOZ_CRASH("Bad type!"); 9277 } 9278 } 9279 9280 // Don't try to upgrade obsolete origins, remove them right after we detect 9281 // them. 9282 for (const auto& originProps : mOriginProps) { 9283 if (originProps.mType == OriginProps::eObsolete) { 9284 MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty()); 9285 MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty()); 9286 MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty()); 9287 9288 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps))); 9289 } else if (!originProps.mIgnore) { 9290 MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty()); 9291 MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty()); 9292 9293 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps))); 9294 } 9295 } 9296 9297 return NS_OK; 9298 } 9299 9300 // XXX Do the fallible initialization in a separate non-static member function 9301 // of StorageOperationBase and eventually get rid of this method and use a 9302 // normal constructor instead. 9303 template <typename PersistenceTypeFunc> 9304 nsresult StorageOperationBase::OriginProps::Init( 9305 PersistenceTypeFunc&& aPersistenceTypeFunc) { 9306 AssertIsOnIOThread(); 9307 9308 QM_TRY_INSPECT(const auto& leafName, 9309 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory, 9310 GetLeafName)); 9311 9312 // XXX Consider using QuotaManager::ParseOrigin here. 9313 nsCString spec; 9314 OriginAttributes attrs; 9315 nsCString originalSuffix; 9316 OriginParser::ResultType result = OriginParser::ParseOrigin( 9317 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix); 9318 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) { 9319 mType = OriginProps::eInvalid; 9320 return NS_OK; 9321 } 9322 9323 const auto persistenceType = [&]() -> PersistenceType { 9324 // XXX We shouldn't continue with initialization if OriginParser returned 9325 // anything else but ValidOrigin. Otherwise, we have to deal with empty 9326 // spec when the origin is obsolete, like here. The caller should handle 9327 // the errors. Until it's fixed, we have to treat obsolete origins as 9328 // origins with unknown/invalid persistence type. 9329 if (result != OriginParser::ValidOrigin) { 9330 return PERSISTENCE_TYPE_INVALID; 9331 } 9332 return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec); 9333 }(); 9334 9335 mLeafName = leafName; 9336 mSpec = spec; 9337 mAttrs = attrs; 9338 mOriginalSuffix = originalSuffix; 9339 mPersistenceType.init(persistenceType); 9340 if (result == OriginParser::ObsoleteOrigin) { 9341 mType = eObsolete; 9342 } else if (mSpec.EqualsLiteral(kChromeOrigin)) { 9343 mType = eChrome; 9344 } else { 9345 mType = eContent; 9346 } 9347 9348 return NS_OK; 9349 } 9350 9351 nsresult RepositoryOperationBase::ProcessRepository() { 9352 AssertIsOnIOThread(); 9353 9354 #ifdef DEBUG 9355 { 9356 QM_TRY_INSPECT(const bool& exists, 9357 MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists), 9358 QM_ASSERT_UNREACHABLE); 9359 MOZ_ASSERT(exists); 9360 } 9361 #endif 9362 9363 QM_TRY(CollectEachFileEntry( 9364 *mDirectory, 9365 [](const auto& originFile) -> Result<mozilla::Ok, nsresult> { 9366 QM_TRY_INSPECT(const auto& leafName, 9367 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9368 nsAutoString, originFile, GetLeafName)); 9369 9370 // Unknown files during upgrade are allowed. Just warn if we find 9371 // them. 9372 if (!IsOSMetadata(leafName)) { 9373 UNKNOWN_FILE_WARNING(leafName); 9374 } 9375 9376 return mozilla::Ok{}; 9377 }, 9378 [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> { 9379 OriginProps originProps(WrapMovingNotNullUnchecked(originDir)); 9380 QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) { 9381 return self.PersistenceTypeFromSpec(aSpec); 9382 }))); 9383 // Bypass invalid origins while upgrading 9384 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{}); 9385 9386 if (originProps.mType != OriginProps::eObsolete) { 9387 QM_TRY_INSPECT(const bool& removed, 9388 MOZ_TO_RESULT_INVOKE_MEMBER( 9389 self, PrepareOriginDirectory, originProps)); 9390 if (removed) { 9391 return mozilla::Ok{}; 9392 } 9393 } 9394 9395 self.mOriginProps.AppendElement(std::move(originProps)); 9396 9397 return mozilla::Ok{}; 9398 })); 9399 9400 if (mOriginProps.IsEmpty()) { 9401 return NS_OK; 9402 } 9403 9404 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories())); 9405 9406 return NS_OK; 9407 } 9408 9409 template <typename UpgradeMethod> 9410 nsresult RepositoryOperationBase::MaybeUpgradeClients( 9411 const OriginProps& aOriginProps, UpgradeMethod aMethod) { 9412 AssertIsOnIOThread(); 9413 MOZ_ASSERT(aMethod); 9414 9415 QuotaManager* quotaManager = QuotaManager::Get(); 9416 MOZ_ASSERT(quotaManager); 9417 9418 QM_TRY(CollectEachFileEntry( 9419 *aOriginProps.mDirectory, 9420 [](const auto& file) -> Result<mozilla::Ok, nsresult> { 9421 QM_TRY_INSPECT( 9422 const auto& leafName, 9423 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName)); 9424 9425 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) { 9426 UNKNOWN_FILE_WARNING(leafName); 9427 } 9428 9429 return mozilla::Ok{}; 9430 }, 9431 [quotaManager, &aMethod, 9432 &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> { 9433 QM_TRY_INSPECT( 9434 const auto& leafName, 9435 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName)); 9436 9437 QM_TRY_INSPECT(const bool& removed, 9438 MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory, 9439 dir, leafName)); 9440 if (removed) { 9441 return mozilla::Ok{}; 9442 } 9443 9444 Client::Type clientType; 9445 bool ok = Client::TypeFromText(leafName, clientType, fallible); 9446 if (!ok) { 9447 UNKNOWN_FILE_WARNING(leafName); 9448 return mozilla::Ok{}; 9449 } 9450 9451 Client* client = quotaManager->GetClient(clientType); 9452 MOZ_ASSERT(client); 9453 9454 QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir))); 9455 9456 return mozilla::Ok{}; 9457 })); 9458 9459 return NS_OK; 9460 } 9461 9462 nsresult RepositoryOperationBase::PrepareClientDirectory( 9463 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) { 9464 AssertIsOnIOThread(); 9465 9466 aRemoved = false; 9467 return NS_OK; 9468 } 9469 9470 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() { 9471 AssertIsOnIOThread(); 9472 MOZ_ASSERT(mDirectory); 9473 9474 const auto maybeLegacyPersistenceType = 9475 LegacyPersistenceTypeFromFile(*mDirectory, fallible); 9476 QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE)); 9477 9478 mLegacyPersistenceType.init(maybeLegacyPersistenceType.value()); 9479 9480 return NS_OK; 9481 } 9482 9483 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType> 9484 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile( 9485 nsIFile& aFile, const fallible_t&) { 9486 nsAutoString leafName; 9487 MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName)); 9488 9489 if (leafName.Equals(u"persistent"_ns)) { 9490 return Some(LegacyPersistenceType::Persistent); 9491 } 9492 9493 if (leafName.Equals(u"temporary"_ns)) { 9494 return Some(LegacyPersistenceType::Temporary); 9495 } 9496 9497 return Nothing(); 9498 } 9499 9500 PersistenceType 9501 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec( 9502 const nsCString& aSpec) { 9503 if (QuotaManager::IsOriginInternal(aSpec)) { 9504 return PERSISTENCE_TYPE_PERSISTENT; 9505 } 9506 9507 return PERSISTENCE_TYPE_DEFAULT; 9508 } 9509 9510 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec( 9511 const nsCString& aSpec) { 9512 switch (*mLegacyPersistenceType) { 9513 case LegacyPersistenceType::Persistent: 9514 return PersistenceTypeFromLegacyPersistentSpec(aSpec); 9515 case LegacyPersistenceType::Temporary: 9516 return PERSISTENCE_TYPE_TEMPORARY; 9517 } 9518 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!"); 9519 } 9520 9521 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory( 9522 nsIFile* aDirectory) { 9523 AssertIsOnIOThread(); 9524 MOZ_ASSERT(aDirectory); 9525 9526 QM_TRY_INSPECT( 9527 const auto& metadataFile, 9528 CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME))); 9529 9530 QM_TRY_INSPECT(const bool& exists, 9531 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists)); 9532 9533 if (!exists) { 9534 // Directory structure upgrade needed. 9535 // Move all files to IDB specific directory. 9536 9537 nsString idbDirectoryName; 9538 QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)), 9539 NS_ERROR_FAILURE); 9540 9541 QM_TRY_INSPECT(const auto& idbDirectory, 9542 CloneFileAndAppend(*aDirectory, idbDirectoryName)); 9543 9544 // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF 9545 // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the 9546 // idb directory shouldn't exist during the upgrade and the upgrade runs 9547 // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok 9548 // here. 9549 QM_TRY(QM_OR_ELSE_WARN_IF( 9550 // Expression. 9551 MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)), 9552 // Predicate. 9553 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 9554 // Fallback. 9555 ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> { 9556 QM_TRY_INSPECT( 9557 const bool& isDirectory, 9558 MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory)); 9559 9560 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED)); 9561 9562 return Ok{}; 9563 }))); 9564 9565 QM_TRY(CollectEachFile( 9566 *aDirectory, 9567 [&idbDirectory, &idbDirectoryName]( 9568 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 9569 QM_TRY_INSPECT(const auto& leafName, 9570 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, 9571 GetLeafName)); 9572 9573 if (!leafName.Equals(idbDirectoryName)) { 9574 QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns))); 9575 } 9576 9577 return Ok{}; 9578 })); 9579 9580 QM_TRY( 9581 MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); 9582 } 9583 9584 return NS_OK; 9585 } 9586 9587 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory( 9588 OriginProps& aOriginProps, bool* aRemoved) { 9589 AssertIsOnIOThread(); 9590 MOZ_ASSERT(aRemoved); 9591 9592 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) { 9593 QM_TRY(MOZ_TO_RESULT( 9594 MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get()))); 9595 9596 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9597 } else { 9598 int64_t timestamp; 9599 nsCString group; 9600 nsCString origin; 9601 Nullable<bool> isApp; 9602 9603 QM_WARNONLY_TRY_UNWRAP( 9604 const auto maybeDirectoryMetadata, 9605 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(), 9606 timestamp, group, origin, isApp))); 9607 if (!maybeDirectoryMetadata) { 9608 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9609 aOriginProps.mNeedsRestore = true; 9610 } else if (!isApp.IsNull()) { 9611 aOriginProps.mIgnore = true; 9612 } 9613 } 9614 9615 *aRemoved = false; 9616 return NS_OK; 9617 } 9618 9619 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory( 9620 const OriginProps& aOriginProps) { 9621 AssertIsOnIOThread(); 9622 9623 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) { 9624 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9625 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9626 aOriginProps.mOriginMetadata))); 9627 9628 // Move internal origins to new persistent storage. 9629 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) == 9630 PERSISTENCE_TYPE_PERSISTENT) { 9631 if (!mPermanentStorageDir) { 9632 QuotaManager* quotaManager = QuotaManager::Get(); 9633 MOZ_ASSERT(quotaManager); 9634 9635 const nsString& permanentStoragePath = 9636 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT); 9637 9638 QM_TRY_UNWRAP(mPermanentStorageDir, 9639 QM_NewLocalFile(permanentStoragePath)); 9640 } 9641 9642 const nsAString& leafName = aOriginProps.mLeafName; 9643 9644 QM_TRY_INSPECT(const auto& newDirectory, 9645 CloneFileAndAppend(*mPermanentStorageDir, leafName)); 9646 9647 QM_TRY_INSPECT(const bool& exists, 9648 MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists)); 9649 9650 if (exists) { 9651 QM_WARNING("Found %s in storage/persistent and storage/permanent !", 9652 NS_ConvertUTF16toUTF8(leafName).get()); 9653 9654 QM_TRY(MOZ_TO_RESULT( 9655 aOriginProps.mDirectory->Remove(/* recursive */ true))); 9656 } else { 9657 QM_TRY(MOZ_TO_RESULT( 9658 aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns))); 9659 } 9660 } 9661 } else if (aOriginProps.mNeedsRestore) { 9662 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9663 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9664 aOriginProps.mOriginMetadata))); 9665 } else if (!aOriginProps.mIgnore) { 9666 QM_TRY_INSPECT(const auto& file, 9667 CloneFileAndAppend(*aOriginProps.mDirectory, 9668 nsLiteralString(METADATA_FILE_NAME))); 9669 9670 QM_TRY_INSPECT(const auto& stream, 9671 GetBinaryOutputStream(*file, FileFlag::Append)); 9672 9673 MOZ_ASSERT(stream); 9674 9675 // Currently unused (used to be isApp). 9676 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false))); 9677 } 9678 9679 return NS_OK; 9680 } 9681 9682 nsresult UpgradeStorageHelperBase::Init() { 9683 AssertIsOnIOThread(); 9684 MOZ_ASSERT(mDirectory); 9685 9686 const auto maybePersistenceType = 9687 PersistenceTypeFromFile(*mDirectory, fallible); 9688 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE)); 9689 9690 mPersistenceType.init(maybePersistenceType.value()); 9691 9692 return NS_OK; 9693 } 9694 9695 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec( 9696 const nsCString& aSpec) { 9697 // There's no moving of origin directories between repositories like in the 9698 // CreateOrUpgradeDirectoryMetadataHelper 9699 return *mPersistenceType; 9700 } 9701 9702 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory( 9703 OriginProps& aOriginProps, bool* aRemoved) { 9704 AssertIsOnIOThread(); 9705 MOZ_ASSERT(aRemoved); 9706 9707 int64_t timestamp; 9708 nsCString group; 9709 nsCString origin; 9710 Nullable<bool> isApp; 9711 9712 QM_WARNONLY_TRY_UNWRAP( 9713 const auto maybeDirectoryMetadata, 9714 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(), 9715 timestamp, group, origin, isApp))); 9716 if (!maybeDirectoryMetadata || isApp.IsNull()) { 9717 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9718 aOriginProps.mNeedsRestore = true; 9719 } else { 9720 aOriginProps.mTimestamp = timestamp; 9721 } 9722 9723 *aRemoved = false; 9724 return NS_OK; 9725 } 9726 9727 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory( 9728 const OriginProps& aOriginProps) { 9729 AssertIsOnIOThread(); 9730 9731 // This handles changes in origin string generation from nsIPrincipal, 9732 // especially the change from: appId+inMozBrowser+originNoSuffix 9733 // to: origin (with origin suffix). 9734 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps)); 9735 if (renamed) { 9736 return NS_OK; 9737 } 9738 9739 if (aOriginProps.mNeedsRestore) { 9740 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9741 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9742 aOriginProps.mOriginMetadata))); 9743 } 9744 9745 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2( 9746 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9747 /* aPersisted */ false, aOriginProps.mOriginMetadata))); 9748 9749 return NS_OK; 9750 } 9751 9752 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory( 9753 const OriginProps& aOriginProps) { 9754 AssertIsOnIOThread(); 9755 9756 // The Cache API was creating top level morgue directories by accident for 9757 // a short time in nightly. This unfortunately prevents all storage from 9758 // working. So recover these profiles permanently by removing these corrupt 9759 // directories as part of this upgrade. 9760 9761 QM_TRY_INSPECT(const auto& morgueDir, 9762 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 9763 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone)); 9764 9765 QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns))); 9766 9767 QM_TRY_INSPECT(const bool& exists, 9768 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists)); 9769 9770 if (exists) { 9771 QM_WARNING("Deleting accidental morgue directory!"); 9772 9773 QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true))); 9774 } 9775 9776 return NS_OK; 9777 } 9778 9779 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData( 9780 const OriginProps& aOriginProps) { 9781 AssertIsOnIOThread(); 9782 9783 // TODO: This method was empty for some time due to accidental changes done 9784 // in bug 1320404. This led to renaming of origin directories like: 9785 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1 9786 // to: 9787 // https+++developer.cdn.mozilla.net^inBrowser=1 9788 // instead of just removing them. 9789 9790 const nsCString& originalSuffix = aOriginProps.mOriginalSuffix; 9791 if (!originalSuffix.IsEmpty()) { 9792 MOZ_ASSERT(originalSuffix[0] == '^'); 9793 9794 if (!URLParams::Parse( 9795 Substring(originalSuffix, 1, originalSuffix.Length() - 1), true, 9796 [](const nsACString& aName, const nsACString& aValue) { 9797 if (aName.EqualsLiteral("appId")) { 9798 return false; 9799 } 9800 return true; 9801 })) { 9802 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps))); 9803 9804 return true; 9805 } 9806 } 9807 9808 return false; 9809 } 9810 9811 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory( 9812 OriginProps& aOriginProps, bool* aRemoved) { 9813 AssertIsOnIOThread(); 9814 MOZ_ASSERT(aRemoved); 9815 9816 QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps))); 9817 9818 QM_TRY(MOZ_TO_RESULT( 9819 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0))); 9820 9821 QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps)); 9822 if (removed) { 9823 *aRemoved = true; 9824 return NS_OK; 9825 } 9826 9827 int64_t timestamp; 9828 nsCString group; 9829 nsCString origin; 9830 Nullable<bool> isApp; 9831 QM_WARNONLY_TRY_UNWRAP( 9832 const auto maybeDirectoryMetadata, 9833 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(), 9834 timestamp, group, origin, isApp))); 9835 if (!maybeDirectoryMetadata || isApp.IsNull()) { 9836 aOriginProps.mNeedsRestore = true; 9837 } 9838 9839 nsCString suffix; 9840 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2, 9841 MOZ_TO_RESULT(GetDirectoryMetadata2( 9842 aOriginProps.mDirectory.get(), timestamp, suffix, 9843 group, origin, isApp.SetValue()))); 9844 if (!maybeDirectoryMetadata2) { 9845 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9846 aOriginProps.mNeedsRestore2 = true; 9847 } else { 9848 aOriginProps.mTimestamp = timestamp; 9849 } 9850 9851 *aRemoved = false; 9852 return NS_OK; 9853 } 9854 9855 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory( 9856 const OriginProps& aOriginProps) { 9857 AssertIsOnIOThread(); 9858 9859 // This handles changes in origin string generation from nsIPrincipal, 9860 // especially the stripping of obsolete origin attributes like addonId. 9861 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps)); 9862 if (renamed) { 9863 return NS_OK; 9864 } 9865 9866 if (aOriginProps.mNeedsRestore) { 9867 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9868 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9869 aOriginProps.mOriginMetadata))); 9870 } 9871 9872 if (aOriginProps.mNeedsRestore2) { 9873 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2( 9874 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9875 /* aPersisted */ false, aOriginProps.mOriginMetadata))); 9876 } 9877 9878 return NS_OK; 9879 } 9880 9881 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory( 9882 OriginProps& aOriginProps, bool* aRemoved) { 9883 AssertIsOnIOThread(); 9884 MOZ_ASSERT(aRemoved); 9885 9886 QM_TRY(MOZ_TO_RESULT( 9887 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1))); 9888 9889 int64_t timestamp; 9890 nsCString group; 9891 nsCString origin; 9892 Nullable<bool> isApp; 9893 QM_WARNONLY_TRY_UNWRAP( 9894 const auto maybeDirectoryMetadata, 9895 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(), 9896 timestamp, group, origin, isApp))); 9897 if (!maybeDirectoryMetadata || isApp.IsNull()) { 9898 aOriginProps.mNeedsRestore = true; 9899 } 9900 9901 nsCString suffix; 9902 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2, 9903 MOZ_TO_RESULT(GetDirectoryMetadata2( 9904 aOriginProps.mDirectory.get(), timestamp, suffix, 9905 group, origin, isApp.SetValue()))); 9906 if (!maybeDirectoryMetadata2) { 9907 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9908 aOriginProps.mNeedsRestore2 = true; 9909 } else { 9910 aOriginProps.mTimestamp = timestamp; 9911 } 9912 9913 *aRemoved = false; 9914 return NS_OK; 9915 } 9916 9917 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory( 9918 const OriginProps& aOriginProps) { 9919 AssertIsOnIOThread(); 9920 9921 if (aOriginProps.mNeedsRestore) { 9922 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9923 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9924 aOriginProps.mOriginMetadata))); 9925 } 9926 9927 if (aOriginProps.mNeedsRestore2) { 9928 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2( 9929 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9930 /* aPersisted */ false, aOriginProps.mOriginMetadata))); 9931 } 9932 9933 return NS_OK; 9934 } 9935 9936 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory( 9937 OriginProps& aOriginProps, bool* aRemoved) { 9938 AssertIsOnIOThread(); 9939 MOZ_ASSERT(aRemoved); 9940 9941 QM_TRY(MOZ_TO_RESULT( 9942 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2))); 9943 9944 int64_t timestamp; 9945 nsCString group; 9946 nsCString origin; 9947 Nullable<bool> isApp; 9948 QM_WARNONLY_TRY_UNWRAP( 9949 const auto maybeDirectoryMetadata, 9950 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(), 9951 timestamp, group, origin, isApp))); 9952 if (!maybeDirectoryMetadata || isApp.IsNull()) { 9953 aOriginProps.mNeedsRestore = true; 9954 } 9955 9956 nsCString suffix; 9957 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2, 9958 MOZ_TO_RESULT(GetDirectoryMetadata2( 9959 aOriginProps.mDirectory.get(), timestamp, suffix, 9960 group, origin, isApp.SetValue()))); 9961 if (!maybeDirectoryMetadata2) { 9962 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps); 9963 aOriginProps.mNeedsRestore2 = true; 9964 } else { 9965 aOriginProps.mTimestamp = timestamp; 9966 } 9967 9968 *aRemoved = false; 9969 return NS_OK; 9970 } 9971 9972 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory( 9973 const OriginProps& aOriginProps) { 9974 AssertIsOnIOThread(); 9975 9976 if (aOriginProps.mNeedsRestore) { 9977 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata( 9978 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9979 aOriginProps.mOriginMetadata))); 9980 } 9981 9982 if (aOriginProps.mNeedsRestore2) { 9983 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2( 9984 *aOriginProps.mDirectory, aOriginProps.mTimestamp, 9985 /* aPersisted */ false, aOriginProps.mOriginMetadata))); 9986 } 9987 9988 return NS_OK; 9989 } 9990 9991 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory( 9992 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) { 9993 AssertIsOnIOThread(); 9994 9995 if (Client::IsDeprecatedClient(aLeafName)) { 9996 QM_WARNING("Deleting deprecated %s client!", 9997 NS_ConvertUTF16toUTF8(aLeafName).get()); 9998 9999 QM_TRY(MOZ_TO_RESULT(aFile->Remove(true))); 10000 10001 aRemoved = true; 10002 } else { 10003 aRemoved = false; 10004 } 10005 10006 return NS_OK; 10007 } 10008 10009 nsresult RestoreDirectoryMetadata2Helper::Init() { 10010 AssertIsOnIOThread(); 10011 MOZ_ASSERT(mDirectory); 10012 10013 nsCOMPtr<nsIFile> parentDir; 10014 QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir)))); 10015 10016 const auto maybePersistenceType = 10017 PersistenceTypeFromFile(*parentDir, fallible); 10018 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE)); 10019 10020 mPersistenceType.init(maybePersistenceType.value()); 10021 10022 return NS_OK; 10023 } 10024 10025 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() { 10026 OriginProps originProps(WrapMovingNotNull(mDirectory)); 10027 QM_TRY(MOZ_TO_RESULT(originProps.Init( 10028 [&self = *this](const auto& aSpec) { return *self.mPersistenceType; }))); 10029 10030 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE); 10031 10032 originProps.mTimestamp = GetOriginLastModifiedTime(originProps); 10033 10034 mOriginProps.AppendElement(std::move(originProps)); 10035 10036 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories())); 10037 10038 return NS_OK; 10039 } 10040 10041 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory( 10042 const OriginProps& aOriginProps) { 10043 AssertIsOnIOThread(); 10044 10045 // We don't have any approach to restore aPersisted, so reset it to false. 10046 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2( 10047 *aOriginProps.mDirectory, 10048 FullOriginMetadata{ 10049 aOriginProps.mOriginMetadata, 10050 OriginStateMetadata{ 10051 aOriginProps.mTimestamp, 10052 /* aLastMaintenanceDate */ 10053 Date::FromTimestamp(aOriginProps.mTimestamp).ToDays(), 10054 /* aAccessed */ true, 10055 /* aPersisted */ false}, 10056 ClientUsageArray(), /* aUsage */ 0, kNoQuotaVersion}))); 10057 10058 return NS_OK; 10059 } 10060 10061 } // namespace mozilla::dom::quota