Bug 1698238 return default dictionary from GetUserMediaRequest#getConstraints() if...
[gecko.git] / dom / quota / ActorsParent.cpp
blob3c07b780d23829d6c52bd19e0ecc849839a16cd3
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/. */
7 #include "ActorsParent.h"
9 // Local includes
10 #include "Flatten.h"
11 #include "InitializationTypes.h"
12 #include "OriginScope.h"
13 #include "QuotaCommon.h"
14 #include "QuotaManager.h"
15 #include "QuotaObject.h"
16 #include "UsageInfo.h"
18 // Global includes
19 #include <cinttypes>
20 #include <cstdlib>
21 #include <cstring>
22 #include <algorithm>
23 #include <cstdint>
24 #include <functional>
25 #include <new>
26 #include <numeric>
27 #include <tuple>
28 #include <type_traits>
29 #include <utility>
30 #include "DirectoryLockImpl.h"
31 #include "ErrorList.h"
32 #include "MainThreadUtils.h"
33 #include "mozIStorageAsyncConnection.h"
34 #include "mozIStorageConnection.h"
35 #include "mozIStorageService.h"
36 #include "mozIStorageStatement.h"
37 #include "mozStorageCID.h"
38 #include "mozStorageHelper.h"
39 #include "mozilla/AlreadyAddRefed.h"
40 #include "mozilla/Assertions.h"
41 #include "mozilla/Atomics.h"
42 #include "mozilla/Attributes.h"
43 #include "mozilla/AutoRestore.h"
44 #include "mozilla/BasePrincipal.h"
45 #include "mozilla/CheckedInt.h"
46 #include "mozilla/CondVar.h"
47 #include "mozilla/InitializedOnce.h"
48 #include "mozilla/Logging.h"
49 #include "mozilla/MacroForEach.h"
50 #include "mozilla/Maybe.h"
51 #include "mozilla/Mutex.h"
52 #include "mozilla/NotNull.h"
53 #include "mozilla/OriginAttributes.h"
54 #include "mozilla/Preferences.h"
55 #include "mozilla/ProfilerLabels.h"
56 #include "mozilla/RefPtr.h"
57 #include "mozilla/Result.h"
58 #include "mozilla/ResultExtensions.h"
59 #include "mozilla/ScopeExit.h"
60 #include "mozilla/Services.h"
61 #include "mozilla/SpinEventLoopUntil.h"
62 #include "mozilla/StaticPrefs_dom.h"
63 #include "mozilla/StaticPtr.h"
64 #include "mozilla/Telemetry.h"
65 #include "mozilla/TelemetryHistogramEnums.h"
66 #include "mozilla/TextUtils.h"
67 #include "mozilla/TimeStamp.h"
68 #include "mozilla/UniquePtr.h"
69 #include "mozilla/Unused.h"
70 #include "mozilla/Variant.h"
71 #include "mozilla/dom/FlippedOnce.h"
72 #include "mozilla/dom/LocalStorageCommon.h"
73 #include "mozilla/dom/StorageActivityService.h"
74 #include "mozilla/dom/StorageDBUpdater.h"
75 #include "mozilla/dom/StorageTypeBinding.h"
76 #include "mozilla/dom/cache/QuotaClient.h"
77 #include "mozilla/dom/indexedDB/ActorsParent.h"
78 #include "mozilla/dom/ipc/IdType.h"
79 #include "mozilla/dom/localstorage/ActorsParent.h"
80 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
81 #include "mozilla/dom/quota/Client.h"
82 #include "mozilla/dom/quota/DirectoryLock.h"
83 #include "mozilla/dom/quota/PersistenceType.h"
84 #include "mozilla/dom/quota/PQuota.h"
85 #include "mozilla/dom/quota/PQuotaParent.h"
86 #include "mozilla/dom/quota/PQuotaRequest.h"
87 #include "mozilla/dom/quota/PQuotaRequestParent.h"
88 #include "mozilla/dom/quota/PQuotaUsageRequest.h"
89 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
90 #include "mozilla/dom/simpledb/ActorsParent.h"
91 #include "mozilla/fallible.h"
92 #include "mozilla/ipc/BackgroundChild.h"
93 #include "mozilla/ipc/BackgroundParent.h"
94 #include "mozilla/ipc/PBackgroundChild.h"
95 #include "mozilla/ipc/PBackgroundSharedTypes.h"
96 #include "mozilla/ipc/ProtocolUtils.h"
97 #include "mozilla/net/MozURL.h"
98 #include "nsAppDirectoryServiceDefs.h"
99 #include "nsBaseHashtable.h"
100 #include "nsCOMPtr.h"
101 #include "nsCRTGlue.h"
102 #include "nsCharSeparatedTokenizer.h"
103 #include "nsClassHashtable.h"
104 #include "nsComponentManagerUtils.h"
105 #include "nsContentUtils.h"
106 #include "nsTHashMap.h"
107 #include "nsDebug.h"
108 #include "nsDirectoryServiceUtils.h"
109 #include "nsError.h"
110 #include "nsHashKeys.h"
111 #include "nsIBinaryInputStream.h"
112 #include "nsIBinaryOutputStream.h"
113 #include "nsIConsoleService.h"
114 #include "nsIDirectoryEnumerator.h"
115 #include "nsIEventTarget.h"
116 #include "nsIFile.h"
117 #include "nsIFileStreams.h"
118 #include "nsIInputStream.h"
119 #include "nsIObjectInputStream.h"
120 #include "nsIObjectOutputStream.h"
121 #include "nsIObserver.h"
122 #include "nsIObserverService.h"
123 #include "nsIOutputStream.h"
124 #include "nsIPlatformInfo.h"
125 #include "nsIPrincipal.h"
126 #include "nsIRunnable.h"
127 #include "nsIScriptObjectPrincipal.h"
128 #include "nsISupports.h"
129 #include "nsISupportsPrimitives.h"
130 #include "nsIThread.h"
131 #include "nsITimer.h"
132 #include "nsIURI.h"
133 #include "nsIWidget.h"
134 #include "nsLiteralString.h"
135 #include "nsNetUtil.h"
136 #include "nsPIDOMWindow.h"
137 #include "nsPrintfCString.h"
138 #include "nsServiceManagerUtils.h"
139 #include "nsString.h"
140 #include "nsStringFlags.h"
141 #include "nsStringFwd.h"
142 #include "nsTArray.h"
143 #include "nsTHashtable.h"
144 #include "nsTLiteralString.h"
145 #include "nsTPromiseFlatString.h"
146 #include "nsTStringRepr.h"
147 #include "nsThreadUtils.h"
148 #include "nsURLHelper.h"
149 #include "nsXPCOM.h"
150 #include "nsXPCOMCID.h"
151 #include "nsXULAppAPI.h"
152 #include "prinrval.h"
153 #include "prio.h"
154 #include "prthread.h"
155 #include "prtime.h"
157 #define DISABLE_ASSERTS_FOR_FUZZING 0
159 #if DISABLE_ASSERTS_FOR_FUZZING
160 # define ASSERT_UNLESS_FUZZING(...) \
161 do { \
162 } while (0)
163 #else
164 # define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
165 #endif
167 // As part of bug 1536596 in order to identify the remaining sources of
168 // principal info inconsistencies, we have added anonymized crash logging and
169 // are temporarily making these checks occur on both debug and optimized
170 // nightly, dev-edition, and early beta builds through use of
171 // EARLY_BETA_OR_EARLIER during Firefox 82. The plan is to return this
172 // condition to MOZ_DIAGNOSTIC_ASSERT_ENABLED during Firefox 84 at the latest.
173 // The analysis and disabling is tracked by bug 1536596.
175 #ifdef EARLY_BETA_OR_EARLIER
176 # define QM_PRINCIPALINFO_VERIFICATION_ENABLED
177 #endif
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
184 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
185 * the crash timeout.
187 #define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
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.
199 #define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
201 // profile-before-change, when we need to shut down quota manager
202 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
204 #define KB *1024ULL
205 #define MB *1024ULL KB
206 #define GB *1024ULL MB
208 namespace mozilla::dom::quota {
210 using namespace mozilla::ipc;
211 using mozilla::net::MozURL;
213 // We want profiles to be platform-independent so we always need to replace
214 // the same characters on every platform. Windows has the most extensive set
215 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
216 // FILE_PATH_SEPARATOR.
217 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
219 namespace {
221 template <typename T>
222 void AssertNoOverflow(uint64_t aDest, T aArg);
224 /*******************************************************************************
225 * Constants
226 ******************************************************************************/
228 const uint32_t kSQLitePageSizeOverride = 512;
230 // Important version history:
231 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
232 // which caused Firefox 57 release concerns because the major schema upgrade
233 // means anyone downgrading to Firefox 56 will experience a non-operational
234 // QuotaManager and all of its clients.
235 // - Bug 1404344 got very concerned about that and so we decided to effectively
236 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
237 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
238 // increases. It also works because all the upgrade did was give the DOM
239 // Cache API QuotaClient an opportunity to create its newly added .padding
240 // files during initialization/upgrade, which isn't functionally necessary as
241 // that can be done on demand.
243 // Major storage version. Bump for backwards-incompatible changes.
244 // (The next major version should be 4 to distinguish from the Bug 1290481
245 // downgrade snafu.)
246 const uint32_t kMajorStorageVersion = 2;
248 // Minor storage version. Bump for backwards-compatible changes.
249 const uint32_t kMinorStorageVersion = 3;
251 // The storage version we store in the SQLite database is a (signed) 32-bit
252 // integer. The major version is left-shifted 16 bits so the max value is
253 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
254 static_assert(kMajorStorageVersion <= 0xFFFF,
255 "Major version needs to fit in 16 bits.");
256 static_assert(kMinorStorageVersion <= 0xFFFF,
257 "Minor version needs to fit in 16 bits.");
259 const int32_t kStorageVersion =
260 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
262 // See comments above about why these are a thing.
263 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
264 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
266 static_assert(static_cast<uint32_t>(StorageType::Persistent) ==
267 static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
268 "Enum values should match.");
270 static_assert(static_cast<uint32_t>(StorageType::Temporary) ==
271 static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
272 "Enum values should match.");
274 static_assert(static_cast<uint32_t>(StorageType::Default) ==
275 static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
276 "Enum values should match.");
278 const char kChromeOrigin[] = "chrome";
279 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
280 const char kIndexedDBOriginPrefix[] = "indexeddb://";
281 const char kResourceOriginPrefix[] = "resource://";
283 constexpr auto kPersistentOriginTelemetryKey = "PersistentOrigin"_ns;
284 constexpr auto kTemporaryOriginTelemetryKey = "TemporaryOrigin"_ns;
286 constexpr auto kStorageName = u"storage"_ns;
287 constexpr auto kSQLiteSuffix = u".sqlite"_ns;
289 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
290 #define PERSISTENT_DIRECTORY_NAME u"persistent"
291 #define PERMANENT_DIRECTORY_NAME u"permanent"
292 #define TEMPORARY_DIRECTORY_NAME u"temporary"
293 #define DEFAULT_DIRECTORY_NAME u"default"
295 // The name of the file that we use to load/save the last access time of an
296 // origin.
297 // XXX We should get rid of old metadata files at some point, bug 1343576.
298 #define METADATA_FILE_NAME u".metadata"
299 #define METADATA_TMP_FILE_NAME u".metadata-tmp"
300 #define METADATA_V2_FILE_NAME u".metadata-v2"
301 #define METADATA_V2_TMP_FILE_NAME u".metadata-v2-tmp"
303 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
304 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
305 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
307 const int32_t kLocalStorageArchiveVersion = 4;
309 const char kProfileDoChangeTopic[] = "profile-do-change";
311 const int32_t kCacheVersion = 2;
313 /******************************************************************************
314 * SQLite functions
315 ******************************************************************************/
317 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
318 uint32_t aMinorStorageVersion) {
319 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
322 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
323 return uint32_t(aStorageVersion >> 16);
326 nsresult CreateTables(mozIStorageConnection* aConnection) {
327 AssertIsOnIOThread();
328 MOZ_ASSERT(aConnection);
330 // Table `database`
331 QM_TRY(
332 aConnection->ExecuteSimpleSQL("CREATE TABLE database"
333 "( cache_version INTEGER NOT NULL DEFAULT 0"
334 ");"_ns));
336 #ifdef DEBUG
338 QM_TRY_INSPECT(const int32_t& storageVersion,
339 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
341 MOZ_ASSERT(storageVersion == 0);
343 #endif
345 QM_TRY(aConnection->SetSchemaVersion(kStorageVersion));
347 return NS_OK;
350 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
351 AssertIsOnIOThread();
353 QM_TRY_INSPECT(const auto& stmt,
354 CreateAndExecuteSingleStepStatement<
355 SingleStepResult::ReturnNullIfNoResult>(
356 aConnection, "SELECT cache_version FROM database"_ns));
358 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
360 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 0));
363 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
364 int32_t aVersion) {
365 AssertIsOnIOThread();
367 QM_TRY_INSPECT(
368 const auto& stmt,
369 MOZ_TO_RESULT_INVOKE_TYPED(
370 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
371 "UPDATE database SET cache_version = :version;"_ns));
373 QM_TRY(stmt->BindInt32ByName("version"_ns, aVersion));
375 QM_TRY(stmt->Execute());
377 return NS_OK;
380 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
381 AssertIsOnIOThread();
383 // Table `cache`
384 QM_TRY(
385 aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
386 "( valid INTEGER NOT NULL DEFAULT 0"
387 ", build_id TEXT NOT NULL DEFAULT ''"
388 ");"_ns));
390 // Table `repository`
391 QM_TRY(
392 aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
393 "( id INTEGER PRIMARY KEY"
394 ", name TEXT NOT NULL"
395 ");"_ns));
397 // Table `origin`
398 QM_TRY(
399 aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
400 "( repository_id INTEGER NOT NULL"
401 ", suffix TEXT"
402 ", group_ TEXT NOT NULL"
403 ", origin TEXT NOT NULL"
404 ", client_usages TEXT NOT NULL"
405 ", usage INTEGER NOT NULL"
406 ", last_access_time INTEGER NOT NULL"
407 ", accessed INTEGER NOT NULL"
408 ", persisted INTEGER NOT NULL"
409 ", PRIMARY KEY (repository_id, origin)"
410 ", FOREIGN KEY (repository_id) "
411 "REFERENCES repository(id) "
412 ");"_ns));
414 #ifdef DEBUG
416 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
417 MOZ_ASSERT(cacheVersion == 0);
419 #endif
421 QM_TRY(SaveCacheVersion(aConnection, kCacheVersion));
423 return NS_OK;
426 nsresult InvalidateCache(mozIStorageConnection& aConnection) {
427 AssertIsOnIOThread();
429 static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
430 static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
432 QM_TRY(QM_OR_ELSE_WARN(
433 ([&]() -> Result<Ok, nsresult> {
434 mozStorageTransaction transaction(&aConnection, false);
436 QM_TRY(transaction.Start());
437 QM_TRY(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery));
438 QM_TRY(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery));
439 QM_TRY(transaction.Commit());
441 return Ok{};
442 }()),
443 ([&](const nsresult rv) -> Result<Ok, nsresult> {
444 QM_TRY(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery));
446 return Ok{};
447 })));
448 return NS_OK;
451 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
452 AssertIsOnIOThread();
454 QM_TRY(aConnection.ExecuteSimpleSQL(
455 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns));
457 QM_TRY(InvalidateCache(aConnection));
459 #ifdef DEBUG
461 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
463 MOZ_ASSERT(cacheVersion == 1);
465 #endif
467 QM_TRY(SaveCacheVersion(aConnection, 2));
469 return NS_OK;
472 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
473 mozIStorageConnection& aConnection) {
474 bool cacheUsable = true;
476 QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
478 if (cacheVersion > kCacheVersion) {
479 cacheUsable = false;
480 } else if (cacheVersion != kCacheVersion) {
481 const bool newCache = !cacheVersion;
483 mozStorageTransaction transaction(
484 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
486 QM_TRY(transaction.Start());
488 if (newCache) {
489 QM_TRY(CreateCacheTables(aConnection));
491 #ifdef DEBUG
493 QM_TRY_INSPECT(const int32_t& cacheVersion,
494 LoadCacheVersion(aConnection));
495 MOZ_ASSERT(cacheVersion == kCacheVersion);
497 #endif
499 QM_TRY(aConnection.ExecuteSimpleSQL(
500 nsLiteralCString("INSERT INTO cache (valid, build_id) "
501 "VALUES (0, '')")));
503 nsCOMPtr<mozIStorageStatement> insertStmt;
505 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
506 if (insertStmt) {
507 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
508 } else {
509 QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_TYPED(
510 nsCOMPtr<mozIStorageStatement>,
511 aConnection, CreateStatement,
512 "INSERT INTO repository (id, name) "
513 "VALUES (:id, :name)"_ns));
516 QM_TRY(insertStmt->BindInt32ByName("id"_ns, persistenceType));
518 QM_TRY(insertStmt->BindUTF8StringByName(
519 "name"_ns, PersistenceTypeToString(persistenceType)));
521 QM_TRY(insertStmt->Execute());
523 } else {
524 // This logic needs to change next time we change the cache!
525 static_assert(kCacheVersion == 2,
526 "Upgrade function needed due to cache version increase.");
528 while (cacheVersion != kCacheVersion) {
529 if (cacheVersion == 1) {
530 QM_TRY(UpgradeCacheFrom1To2(aConnection));
531 } else {
532 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
533 QM_WARNING(
534 "Unable to initialize cache, no upgrade path is "
535 "available!");
539 QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
542 MOZ_ASSERT(cacheVersion == kCacheVersion);
545 QM_TRY(transaction.Commit());
548 return cacheUsable;
551 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
552 nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
553 AssertIsOnIOThread();
555 // Check if the old database exists at all.
556 QM_TRY_INSPECT(const bool& exists,
557 MOZ_TO_RESULT_INVOKE(aWebAppsStoreFile, Exists));
559 if (!exists) {
560 // webappsstore.sqlite doesn't exist, return a null connection.
561 return nsCOMPtr<mozIStorageConnection>{};
564 QM_TRY_INSPECT(const bool& isDirectory,
565 MOZ_TO_RESULT_INVOKE(aWebAppsStoreFile, IsDirectory));
567 if (isDirectory) {
568 QM_WARNING("webappsstore.sqlite is not a file!");
569 return nsCOMPtr<mozIStorageConnection>{};
572 QM_TRY_INSPECT(const auto& connection,
573 QM_OR_ELSE_WARN(
574 MOZ_TO_RESULT_INVOKE_TYPED(
575 nsCOMPtr<mozIStorageConnection>, aStorageService,
576 OpenUnsharedDatabase, &aWebAppsStoreFile),
577 ([](const nsresult rv)
578 -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
579 if (IsDatabaseCorruptionError(rv)) {
580 // Don't throw an error, leave a corrupted webappsstore
581 // database as it is.
582 return nsCOMPtr<mozIStorageConnection>{};
585 return Err(rv);
586 })));
588 if (connection) {
589 // Don't propagate an error, leave a non-updateable webappsstore database as
590 // it is.
591 QM_TRY(StorageDBUpdater::Update(connection),
592 nsCOMPtr<mozIStorageConnection>{});
595 return connection;
598 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveFile(
599 const nsAString& aDirectoryPath) {
600 AssertIsOnIOThread();
601 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
603 QM_TRY_UNWRAP(auto lsArchiveFile, QM_NewLocalFile(aDirectoryPath));
605 QM_TRY(lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME)));
607 return lsArchiveFile;
610 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
611 const nsAString& aDirectoryPath) {
612 AssertIsOnIOThread();
613 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
615 QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
617 QM_TRY(lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME)));
619 return lsArchiveTmpFile;
622 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
623 mozIStorageConnection& aConnection) {
624 AssertIsOnIOThread();
626 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aConnection, TableExists, "database"_ns));
629 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
630 AssertIsOnIOThread();
631 MOZ_ASSERT(aConnection);
633 #ifdef DEBUG
635 QM_TRY_INSPECT(const auto& initialized,
636 IsLocalStorageArchiveInitialized(*aConnection));
637 MOZ_ASSERT(!initialized);
639 #endif
641 QM_TRY(aConnection->ExecuteSimpleSQL(
642 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns));
644 QM_TRY_INSPECT(
645 const auto& stmt,
646 MOZ_TO_RESULT_INVOKE_TYPED(
647 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
648 "INSERT INTO database (version) VALUES (:version)"_ns));
650 QM_TRY(stmt->BindInt32ByName("version"_ns, 0));
651 QM_TRY(stmt->Execute());
653 return NS_OK;
656 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
657 mozIStorageConnection& aConnection) {
658 AssertIsOnIOThread();
660 QM_TRY_INSPECT(const auto& stmt,
661 CreateAndExecuteSingleStepStatement<
662 SingleStepResult::ReturnNullIfNoResult>(
663 aConnection, "SELECT version FROM database"_ns));
665 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
667 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 0));
670 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
671 int32_t aVersion) {
672 AssertIsOnIOThread();
673 MOZ_ASSERT(aConnection);
675 nsCOMPtr<mozIStorageStatement> stmt;
676 nsresult rv = aConnection->CreateStatement(
677 "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
678 if (NS_WARN_IF(NS_FAILED(rv))) {
679 return rv;
682 rv = stmt->BindInt32ByName("version"_ns, aVersion);
683 if (NS_WARN_IF(NS_FAILED(rv))) {
684 return rv;
687 rv = stmt->Execute();
688 if (NS_WARN_IF(NS_FAILED(rv))) {
689 return rv;
692 return NS_OK;
695 template <typename FileFunc, typename DirectoryFunc>
696 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
697 nsIFile& aDirectory, const FileFunc& aFileFunc,
698 const DirectoryFunc& aDirectoryFunc) {
699 AssertIsOnIOThread();
701 return CollectEachFile(
702 aDirectory,
703 [&aFileFunc, &aDirectoryFunc](
704 const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
705 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
707 switch (dirEntryKind) {
708 case nsIFileKind::ExistsAsDirectory:
709 return aDirectoryFunc(file);
711 case nsIFileKind::ExistsAsFile:
712 return aFileFunc(file);
714 case nsIFileKind::DoesNotExist:
715 // Ignore files that got removed externally while iterating.
716 break;
719 return Ok{};
723 /******************************************************************************
724 * Quota manager class declarations
725 ******************************************************************************/
727 } // namespace
729 class QuotaManager::Observer final : public nsIObserver {
730 static Observer* sInstance;
732 bool mPendingProfileChange;
733 bool mShutdownComplete;
735 public:
736 static nsresult Initialize();
738 static void ShutdownCompleted();
740 private:
741 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
742 MOZ_ASSERT(NS_IsMainThread());
745 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
747 nsresult Init();
749 nsresult Shutdown();
751 NS_DECL_ISUPPORTS
752 NS_DECL_NSIOBSERVER
755 namespace {
757 /*******************************************************************************
758 * Local class declarations
759 ******************************************************************************/
761 } // namespace
763 // XXX Change this not to derive from AutoTArray.
764 class ClientUsageArray final
765 : public AutoTArray<Maybe<uint64_t>, Client::TYPE_MAX> {
766 public:
767 ClientUsageArray() { SetLength(Client::TypeMax()); }
769 void Serialize(nsACString& aText) const;
771 nsresult Deserialize(const nsACString& aText);
773 ClientUsageArray Clone() const {
774 ClientUsageArray res;
775 res.Assign(*this);
776 return res;
780 class OriginInfo final {
781 friend class GroupInfo;
782 friend class QuotaManager;
783 friend class QuotaObject;
785 public:
786 OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
787 const ClientUsageArray& aClientUsages, uint64_t aUsage,
788 int64_t aAccessTime, bool aPersisted, bool aDirectoryExists);
790 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
792 GroupInfo* GetGroupInfo() const { return mGroupInfo; }
794 const nsCString& Origin() const { return mOrigin; }
796 int64_t LockedUsage() const {
797 AssertCurrentThreadOwnsQuotaMutex();
799 #ifdef DEBUG
800 QuotaManager* quotaManager = QuotaManager::Get();
801 MOZ_ASSERT(quotaManager);
803 uint64_t usage = 0;
804 for (Client::Type type : quotaManager->AllClientTypes()) {
805 AssertNoOverflow(usage, mClientUsages[type].valueOr(0));
806 usage += mClientUsages[type].valueOr(0);
808 MOZ_ASSERT(mUsage == usage);
809 #endif
811 return mUsage;
814 int64_t LockedAccessTime() const {
815 AssertCurrentThreadOwnsQuotaMutex();
817 return mAccessTime;
820 bool LockedPersisted() const {
821 AssertCurrentThreadOwnsQuotaMutex();
823 return mPersisted;
826 OriginMetadata FlattenToOriginMetadata() const;
828 nsresult LockedBindToStatement(mozIStorageStatement* aStatement) const;
830 private:
831 // Private destructor, to discourage deletion outside of Release():
832 ~OriginInfo() {
833 MOZ_COUNT_DTOR(OriginInfo);
835 MOZ_ASSERT(!mQuotaObjects.Count());
838 void LockedDecreaseUsage(Client::Type aClientType, int64_t aSize);
840 void LockedResetUsageForClient(Client::Type aClientType);
842 UsageInfo LockedGetUsageForClient(Client::Type aClientType);
844 void LockedUpdateAccessTime(int64_t aAccessTime) {
845 AssertCurrentThreadOwnsQuotaMutex();
847 mAccessTime = aAccessTime;
848 if (!mAccessed) {
849 mAccessed = true;
853 void LockedPersist();
855 nsTHashMap<nsStringHashKey, NotNull<QuotaObject*>> mQuotaObjects;
856 ClientUsageArray mClientUsages;
857 GroupInfo* mGroupInfo;
858 const nsCString mOrigin;
859 uint64_t mUsage;
860 int64_t mAccessTime;
861 bool mAccessed;
862 bool mPersisted;
864 * In some special cases like the LocalStorage client where it's possible to
865 * create a Quota-using representation but not actually write any data, we
866 * want to be able to track quota for an origin without creating its origin
867 * directory or the per-client files until they are actually needed to store
868 * data. In those cases, the OriginInfo will be created by
869 * EnsureQuotaForOrigin and the resulting mDirectoryExists will be false until
870 * the origin actually needs to be created. It is possible for mUsage to be
871 * greater than zero while mDirectoryExists is false, representing a state
872 * where a client like LocalStorage has reserved quota for disk writes, but
873 * has not yet flushed the data to disk.
875 bool mDirectoryExists;
878 class OriginInfoAccessTimeComparator {
879 public:
880 bool Equals(const NotNull<RefPtr<const OriginInfo>>& a,
881 const NotNull<RefPtr<const OriginInfo>>& b) const {
882 return a->LockedAccessTime() == b->LockedAccessTime();
885 bool LessThan(const NotNull<RefPtr<const OriginInfo>>& a,
886 const NotNull<RefPtr<const OriginInfo>>& b) const {
887 return a->LockedAccessTime() < b->LockedAccessTime();
891 class GroupInfo final {
892 friend class GroupInfoPair;
893 friend class OriginInfo;
894 friend class QuotaManager;
895 friend class QuotaObject;
897 public:
898 GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType)
899 : mGroupInfoPair(aGroupInfoPair),
900 mPersistenceType(aPersistenceType),
901 mUsage(0) {
902 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
904 MOZ_COUNT_CTOR(GroupInfo);
907 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
909 PersistenceType GetPersistenceType() const { return mPersistenceType; }
911 private:
912 // Private destructor, to discourage deletion outside of Release():
913 MOZ_COUNTED_DTOR(GroupInfo)
915 already_AddRefed<OriginInfo> LockedGetOriginInfo(const nsACString& aOrigin);
917 void LockedAddOriginInfo(NotNull<RefPtr<OriginInfo>>&& aOriginInfo);
919 void LockedAdjustUsageForRemovedOriginInfo(const OriginInfo& aOriginInfo);
921 void LockedRemoveOriginInfo(const nsACString& aOrigin);
923 void LockedRemoveOriginInfos();
925 bool LockedHasOriginInfos() {
926 AssertCurrentThreadOwnsQuotaMutex();
928 return !mOriginInfos.IsEmpty();
931 nsTArray<NotNull<RefPtr<OriginInfo>>> mOriginInfos;
933 GroupInfoPair* mGroupInfoPair;
934 PersistenceType mPersistenceType;
935 uint64_t mUsage;
938 // XXX Consider a new name for this class, it has other data members now
939 // (besides two GroupInfo objects).
940 class GroupInfoPair {
941 public:
942 GroupInfoPair(const nsACString& aSuffix, const nsACString& aGroup)
943 : mSuffix(aSuffix), mGroup(aGroup) {
944 MOZ_COUNT_CTOR(GroupInfoPair);
947 MOZ_COUNTED_DTOR(GroupInfoPair)
949 const nsCString& Suffix() const { return mSuffix; }
951 const nsCString& Group() const { return mGroup; }
953 RefPtr<GroupInfo> LockedGetGroupInfo(PersistenceType aPersistenceType) {
954 AssertCurrentThreadOwnsQuotaMutex();
955 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
957 return GetGroupInfoForPersistenceType(aPersistenceType);
960 void LockedSetGroupInfo(PersistenceType aPersistenceType,
961 GroupInfo* aGroupInfo) {
962 AssertCurrentThreadOwnsQuotaMutex();
963 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
965 RefPtr<GroupInfo>& groupInfo =
966 GetGroupInfoForPersistenceType(aPersistenceType);
967 groupInfo = aGroupInfo;
970 void LockedClearGroupInfo(PersistenceType aPersistenceType) {
971 AssertCurrentThreadOwnsQuotaMutex();
972 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
974 RefPtr<GroupInfo>& groupInfo =
975 GetGroupInfoForPersistenceType(aPersistenceType);
976 groupInfo = nullptr;
979 bool LockedHasGroupInfos() {
980 AssertCurrentThreadOwnsQuotaMutex();
982 return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo;
985 private:
986 RefPtr<GroupInfo>& GetGroupInfoForPersistenceType(
987 PersistenceType aPersistenceType);
989 const nsCString mSuffix;
990 const nsCString mGroup;
991 RefPtr<GroupInfo> mTemporaryStorageGroupInfo;
992 RefPtr<GroupInfo> mDefaultStorageGroupInfo;
995 namespace {
997 class CollectOriginsHelper final : public Runnable {
998 uint64_t mMinSizeToBeFreed;
1000 Mutex& mMutex;
1001 CondVar mCondVar;
1003 // The members below are protected by mMutex.
1004 nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
1005 uint64_t mSizeToBeFreed;
1006 bool mWaiting;
1008 public:
1009 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
1011 // Blocks the current thread until origins are collected on the main thread.
1012 // The returned value contains an aggregate size of those origins.
1013 int64_t BlockAndReturnOriginsForEviction(
1014 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
1016 private:
1017 ~CollectOriginsHelper() = default;
1019 NS_IMETHOD
1020 Run() override;
1023 class OriginOperationBase : public BackgroundThreadObject, public Runnable {
1024 protected:
1025 nsresult mResultCode;
1027 enum State {
1028 // Not yet run.
1029 State_Initial,
1031 // Running quota manager initialization on the owning thread.
1032 State_CreatingQuotaManager,
1034 // Running on the owning thread in the listener for OpenDirectory.
1035 State_DirectoryOpenPending,
1037 // Running on the IO thread.
1038 State_DirectoryWorkOpen,
1040 // Running on the owning thread after all work is done.
1041 State_UnblockingOpen,
1043 // All done.
1044 State_Complete
1047 private:
1048 State mState;
1049 bool mActorDestroyed;
1051 protected:
1052 bool mNeedsQuotaManagerInit;
1053 bool mNeedsStorageInit;
1055 public:
1056 void NoteActorDestroyed() {
1057 AssertIsOnOwningThread();
1059 mActorDestroyed = true;
1062 bool IsActorDestroyed() const {
1063 AssertIsOnOwningThread();
1065 return mActorDestroyed;
1068 protected:
1069 explicit OriginOperationBase(
1070 nsIEventTarget* aOwningThread = GetCurrentEventTarget())
1071 : BackgroundThreadObject(aOwningThread),
1072 Runnable("dom::quota::OriginOperationBase"),
1073 mResultCode(NS_OK),
1074 mState(State_Initial),
1075 mActorDestroyed(false),
1076 mNeedsQuotaManagerInit(false),
1077 mNeedsStorageInit(false) {}
1079 // Reference counted.
1080 virtual ~OriginOperationBase() {
1081 MOZ_ASSERT(mState == State_Complete);
1082 MOZ_ASSERT(mActorDestroyed);
1085 #ifdef DEBUG
1086 State GetState() const { return mState; }
1087 #endif
1089 void SetState(State aState) {
1090 MOZ_ASSERT(mState == State_Initial);
1091 mState = aState;
1094 void AdvanceState() {
1095 switch (mState) {
1096 case State_Initial:
1097 mState = State_CreatingQuotaManager;
1098 return;
1099 case State_CreatingQuotaManager:
1100 mState = State_DirectoryOpenPending;
1101 return;
1102 case State_DirectoryOpenPending:
1103 mState = State_DirectoryWorkOpen;
1104 return;
1105 case State_DirectoryWorkOpen:
1106 mState = State_UnblockingOpen;
1107 return;
1108 case State_UnblockingOpen:
1109 mState = State_Complete;
1110 return;
1111 default:
1112 MOZ_CRASH("Bad state!");
1116 NS_IMETHOD
1117 Run() override;
1119 virtual void Open() = 0;
1121 nsresult DirectoryOpen();
1123 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) = 0;
1125 void Finish(nsresult aResult);
1127 virtual void UnblockOpen() = 0;
1129 private:
1130 nsresult Init();
1132 nsresult FinishInit();
1134 nsresult QuotaManagerOpen();
1136 nsresult DirectoryWork();
1139 class FinalizeOriginEvictionOp : public OriginOperationBase {
1140 nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
1142 public:
1143 FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread,
1144 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks)
1145 : OriginOperationBase(aBackgroundThread), mLocks(std::move(aLocks)) {
1146 MOZ_ASSERT(!NS_IsMainThread());
1149 void Dispatch();
1151 void RunOnIOThreadImmediately();
1153 private:
1154 ~FinalizeOriginEvictionOp() = default;
1156 virtual void Open() override;
1158 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1160 virtual void UnblockOpen() override;
1163 class NormalOriginOperationBase
1164 : public OriginOperationBase,
1165 public OpenDirectoryListener,
1166 public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
1167 RefPtr<DirectoryLock> mDirectoryLock;
1169 protected:
1170 Nullable<PersistenceType> mPersistenceType;
1171 OriginScope mOriginScope;
1172 Nullable<Client::Type> mClientType;
1173 mozilla::Atomic<bool> mCanceled;
1174 const bool mExclusive;
1175 bool mNeedsDirectoryLocking;
1177 public:
1178 void RunImmediately() {
1179 MOZ_ASSERT(GetState() == State_Initial);
1181 MOZ_ALWAYS_SUCCEEDS(this->Run());
1184 protected:
1185 NormalOriginOperationBase(const Nullable<PersistenceType>& aPersistenceType,
1186 const OriginScope& aOriginScope, bool aExclusive)
1187 : mPersistenceType(aPersistenceType),
1188 mOriginScope(aOriginScope),
1189 mExclusive(aExclusive),
1190 mNeedsDirectoryLocking(true) {
1191 AssertIsOnOwningThread();
1194 ~NormalOriginOperationBase() = default;
1196 private:
1197 // Need to declare refcounting unconditionally, because
1198 // OpenDirectoryListener has pure-virtual refcounting.
1199 NS_DECL_ISUPPORTS_INHERITED
1201 virtual void Open() override;
1203 virtual void UnblockOpen() override;
1205 // OpenDirectoryListener overrides.
1206 virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
1208 virtual void DirectoryLockFailed() override;
1210 // Used to send results before unblocking open.
1211 virtual void SendResults() = 0;
1214 class SaveOriginAccessTimeOp : public NormalOriginOperationBase {
1215 int64_t mTimestamp;
1217 public:
1218 SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
1219 const nsACString& aOrigin, int64_t aTimestamp)
1220 : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
1221 OriginScope::FromOrigin(aOrigin),
1222 /* aExclusive */ false),
1223 mTimestamp(aTimestamp) {
1224 AssertIsOnOwningThread();
1227 private:
1228 ~SaveOriginAccessTimeOp() = default;
1230 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1232 virtual void SendResults() override;
1235 /*******************************************************************************
1236 * Actor class declarations
1237 ******************************************************************************/
1239 class Quota final : public PQuotaParent {
1240 #ifdef DEBUG
1241 bool mActorDestroyed;
1242 #endif
1244 public:
1245 Quota();
1247 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
1249 private:
1250 ~Quota();
1252 void StartIdleMaintenance();
1254 bool VerifyRequestParams(const UsageRequestParams& aParams) const;
1256 bool VerifyRequestParams(const RequestParams& aParams) const;
1258 // IPDL methods.
1259 virtual void ActorDestroy(ActorDestroyReason aWhy) override;
1261 virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(
1262 const UsageRequestParams& aParams) override;
1264 virtual mozilla::ipc::IPCResult RecvPQuotaUsageRequestConstructor(
1265 PQuotaUsageRequestParent* aActor,
1266 const UsageRequestParams& aParams) override;
1268 virtual bool DeallocPQuotaUsageRequestParent(
1269 PQuotaUsageRequestParent* aActor) override;
1271 virtual PQuotaRequestParent* AllocPQuotaRequestParent(
1272 const RequestParams& aParams) override;
1274 virtual mozilla::ipc::IPCResult RecvPQuotaRequestConstructor(
1275 PQuotaRequestParent* aActor, const RequestParams& aParams) override;
1277 virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
1279 virtual mozilla::ipc::IPCResult RecvStartIdleMaintenance() override;
1281 virtual mozilla::ipc::IPCResult RecvStopIdleMaintenance() override;
1283 virtual mozilla::ipc::IPCResult RecvAbortOperationsForProcess(
1284 const ContentParentId& aContentParentId) override;
1287 class QuotaUsageRequestBase : public NormalOriginOperationBase,
1288 public PQuotaUsageRequestParent {
1289 public:
1290 // May be overridden by subclasses if they need to perform work on the
1291 // background thread before being run.
1292 virtual void Init(Quota& aQuota);
1294 protected:
1295 QuotaUsageRequestBase()
1296 : NormalOriginOperationBase(Nullable<PersistenceType>(),
1297 OriginScope::FromNull(),
1298 /* aExclusive */ false) {}
1300 mozilla::Result<UsageInfo, nsresult> GetUsageForOrigin(
1301 QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
1302 const OriginMetadata& aOriginMetadata);
1304 // Subclasses use this override to set the IPDL response value.
1305 virtual void GetResponse(UsageRequestResponse& aResponse) = 0;
1307 private:
1308 mozilla::Result<UsageInfo, nsresult> GetUsageForOriginEntries(
1309 QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
1310 const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
1311 bool aInitialized);
1313 void SendResults() override;
1315 // IPDL methods.
1316 void ActorDestroy(ActorDestroyReason aWhy) override;
1318 mozilla::ipc::IPCResult RecvCancel() final;
1321 // A mix-in class to simplify operations that need to process every origin in
1322 // one or more repositories. Sub-classes should call TraverseRepository in their
1323 // DoDirectoryWork and implement a ProcessOrigin method for their per-origin
1324 // logic.
1325 class TraverseRepositoryHelper {
1326 public:
1327 TraverseRepositoryHelper() = default;
1329 protected:
1330 virtual ~TraverseRepositoryHelper() = default;
1332 // If ProcessOrigin returns an error, TraverseRepository will immediately
1333 // terminate and return the received error code to its caller.
1334 nsresult TraverseRepository(QuotaManager& aQuotaManager,
1335 PersistenceType aPersistenceType);
1337 private:
1338 virtual const Atomic<bool>& GetIsCanceledFlag() = 0;
1340 virtual nsresult ProcessOrigin(QuotaManager& aQuotaManager,
1341 nsIFile& aOriginDir, const bool aPersistent,
1342 const PersistenceType aPersistenceType) = 0;
1345 class GetUsageOp final : public QuotaUsageRequestBase,
1346 public TraverseRepositoryHelper {
1347 nsTArray<OriginUsage> mOriginUsages;
1348 nsTHashMap<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
1350 bool mGetAll;
1352 public:
1353 explicit GetUsageOp(const UsageRequestParams& aParams);
1355 private:
1356 ~GetUsageOp() = default;
1358 void ProcessOriginInternal(QuotaManager* aQuotaManager,
1359 const PersistenceType aPersistenceType,
1360 const nsACString& aOrigin,
1361 const int64_t aTimestamp, const bool aPersisted,
1362 const uint64_t aUsage);
1364 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1366 const Atomic<bool>& GetIsCanceledFlag() override;
1368 nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
1369 const bool aPersistent,
1370 const PersistenceType aPersistenceType) override;
1372 void GetResponse(UsageRequestResponse& aResponse) override;
1375 class GetOriginUsageOp final : public QuotaUsageRequestBase {
1376 nsCString mSuffix;
1377 nsCString mGroup;
1378 uint64_t mUsage;
1379 uint64_t mFileUsage;
1380 bool mFromMemory;
1382 public:
1383 explicit GetOriginUsageOp(const UsageRequestParams& aParams);
1385 private:
1386 ~GetOriginUsageOp() = default;
1388 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1390 void GetResponse(UsageRequestResponse& aResponse) override;
1393 class QuotaRequestBase : public NormalOriginOperationBase,
1394 public PQuotaRequestParent {
1395 public:
1396 // May be overridden by subclasses if they need to perform work on the
1397 // background thread before being run.
1398 virtual void Init(Quota& aQuota);
1400 protected:
1401 explicit QuotaRequestBase(bool aExclusive)
1402 : NormalOriginOperationBase(Nullable<PersistenceType>(),
1403 OriginScope::FromNull(), aExclusive) {}
1405 // Subclasses use this override to set the IPDL response value.
1406 virtual void GetResponse(RequestResponse& aResponse) = 0;
1408 private:
1409 virtual void SendResults() override;
1411 // IPDL methods.
1412 virtual void ActorDestroy(ActorDestroyReason aWhy) override;
1415 class StorageNameOp final : public QuotaRequestBase {
1416 nsString mName;
1418 public:
1419 StorageNameOp();
1421 void Init(Quota& aQuota) override;
1423 private:
1424 ~StorageNameOp() = default;
1426 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1428 void GetResponse(RequestResponse& aResponse) override;
1431 class InitializedRequestBase : public QuotaRequestBase {
1432 protected:
1433 bool mInitialized;
1435 public:
1436 void Init(Quota& aQuota) override;
1438 protected:
1439 InitializedRequestBase();
1442 class StorageInitializedOp final : public InitializedRequestBase {
1443 private:
1444 ~StorageInitializedOp() = default;
1446 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1448 void GetResponse(RequestResponse& aResponse) override;
1451 class TemporaryStorageInitializedOp final : public InitializedRequestBase {
1452 private:
1453 ~TemporaryStorageInitializedOp() = default;
1455 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1457 void GetResponse(RequestResponse& aResponse) override;
1460 class InitOp final : public QuotaRequestBase {
1461 public:
1462 InitOp();
1464 void Init(Quota& aQuota) override;
1466 private:
1467 ~InitOp() = default;
1469 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1471 void GetResponse(RequestResponse& aResponse) override;
1474 class InitTemporaryStorageOp final : public QuotaRequestBase {
1475 public:
1476 InitTemporaryStorageOp();
1478 void Init(Quota& aQuota) override;
1480 private:
1481 ~InitTemporaryStorageOp() = default;
1483 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1485 void GetResponse(RequestResponse& aResponse) override;
1488 class InitializeOriginRequestBase : public QuotaRequestBase {
1489 protected:
1490 nsCString mSuffix;
1491 nsCString mGroup;
1492 bool mCreated;
1494 public:
1495 void Init(Quota& aQuota) override;
1497 protected:
1498 InitializeOriginRequestBase(PersistenceType aPersistenceType,
1499 const PrincipalInfo& aPrincipalInfo);
1502 class InitializePersistentOriginOp final : public InitializeOriginRequestBase {
1503 public:
1504 explicit InitializePersistentOriginOp(const RequestParams& aParams);
1506 private:
1507 ~InitializePersistentOriginOp() = default;
1509 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1511 void GetResponse(RequestResponse& aResponse) override;
1514 class InitializeTemporaryOriginOp final : public InitializeOriginRequestBase {
1515 public:
1516 explicit InitializeTemporaryOriginOp(const RequestParams& aParams);
1518 private:
1519 ~InitializeTemporaryOriginOp() = default;
1521 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1523 void GetResponse(RequestResponse& aResponse) override;
1526 class ResetOrClearOp final : public QuotaRequestBase {
1527 const bool mClear;
1529 public:
1530 explicit ResetOrClearOp(bool aClear);
1532 void Init(Quota& aQuota) override;
1534 private:
1535 ~ResetOrClearOp() = default;
1537 void DeleteFiles(QuotaManager& aQuotaManager);
1539 void DeleteStorageFile(QuotaManager& aQuotaManager);
1541 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1543 virtual void GetResponse(RequestResponse& aResponse) override;
1546 class ClearRequestBase : public QuotaRequestBase {
1547 protected:
1548 explicit ClearRequestBase(bool aExclusive) : QuotaRequestBase(aExclusive) {
1549 AssertIsOnOwningThread();
1552 void DeleteFiles(QuotaManager& aQuotaManager,
1553 PersistenceType aPersistenceType);
1555 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1558 class ClearOriginOp final : public ClearRequestBase {
1559 const ClearResetOriginParams mParams;
1560 const bool mMatchAll;
1562 public:
1563 explicit ClearOriginOp(const RequestParams& aParams);
1565 void Init(Quota& aQuota) override;
1567 private:
1568 ~ClearOriginOp() = default;
1570 void GetResponse(RequestResponse& aResponse) override;
1573 class ClearDataOp final : public ClearRequestBase {
1574 const ClearDataParams mParams;
1576 public:
1577 explicit ClearDataOp(const RequestParams& aParams);
1579 void Init(Quota& aQuota) override;
1581 private:
1582 ~ClearDataOp() = default;
1584 void GetResponse(RequestResponse& aResponse) override;
1587 class ResetOriginOp final : public QuotaRequestBase {
1588 public:
1589 explicit ResetOriginOp(const RequestParams& aParams);
1591 void Init(Quota& aQuota) override;
1593 private:
1594 ~ResetOriginOp() = default;
1596 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1598 void GetResponse(RequestResponse& aResponse) override;
1601 class PersistRequestBase : public QuotaRequestBase {
1602 const PrincipalInfo mPrincipalInfo;
1604 protected:
1605 nsCString mSuffix;
1606 nsCString mGroup;
1608 public:
1609 void Init(Quota& aQuota) override;
1611 protected:
1612 explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
1615 class PersistedOp final : public PersistRequestBase {
1616 bool mPersisted;
1618 public:
1619 explicit PersistedOp(const RequestParams& aParams);
1621 private:
1622 ~PersistedOp() = default;
1624 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1626 void GetResponse(RequestResponse& aResponse) override;
1629 class PersistOp final : public PersistRequestBase {
1630 public:
1631 explicit PersistOp(const RequestParams& aParams);
1633 private:
1634 ~PersistOp() = default;
1636 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1638 void GetResponse(RequestResponse& aResponse) override;
1641 class EstimateOp final : public QuotaRequestBase {
1642 nsCString mGroup;
1643 uint64_t mUsage;
1644 uint64_t mLimit;
1646 public:
1647 explicit EstimateOp(const RequestParams& aParams);
1649 private:
1650 ~EstimateOp() = default;
1652 virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1654 void GetResponse(RequestResponse& aResponse) override;
1657 class ListOriginsOp final : public QuotaRequestBase,
1658 public TraverseRepositoryHelper {
1659 // XXX Bug 1521541 will make each origin has it's own state.
1660 nsTArray<nsCString> mOrigins;
1662 public:
1663 ListOriginsOp();
1665 void Init(Quota& aQuota) override;
1667 private:
1668 ~ListOriginsOp() = default;
1670 nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1672 const Atomic<bool>& GetIsCanceledFlag() override;
1674 nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
1675 const bool aPersistent,
1676 const PersistenceType aPersistenceType) override;
1678 void GetResponse(RequestResponse& aResponse) override;
1681 /*******************************************************************************
1682 * Other class declarations
1683 ******************************************************************************/
1685 class StoragePressureRunnable final : public Runnable {
1686 const uint64_t mUsage;
1688 public:
1689 explicit StoragePressureRunnable(uint64_t aUsage)
1690 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
1691 mUsage(aUsage) {}
1693 private:
1694 ~StoragePressureRunnable() = default;
1696 NS_DECL_NSIRUNNABLE
1699 class RecordQuotaInfoLoadTimeHelper final : public Runnable {
1700 // TimeStamps that are set on the IO thread.
1701 LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
1702 LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
1704 // A TimeStamp that is set on the main thread.
1705 LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
1707 public:
1708 RecordQuotaInfoLoadTimeHelper()
1709 : Runnable("dom::quota::RecordQuotaInfoLoadTimeHelper") {}
1711 void Start();
1713 void End();
1715 private:
1716 ~RecordQuotaInfoLoadTimeHelper() = default;
1718 NS_DECL_NSIRUNNABLE
1721 /*******************************************************************************
1722 * Helper classes
1723 ******************************************************************************/
1725 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1727 class PrincipalVerifier final : public Runnable {
1728 nsTArray<PrincipalInfo> mPrincipalInfos;
1730 public:
1731 static already_AddRefed<PrincipalVerifier> CreateAndDispatch(
1732 nsTArray<PrincipalInfo>&& aPrincipalInfos);
1734 private:
1735 explicit PrincipalVerifier(nsTArray<PrincipalInfo>&& aPrincipalInfos)
1736 : Runnable("dom::quota::PrincipalVerifier"),
1737 mPrincipalInfos(std::move(aPrincipalInfos)) {
1738 AssertIsOnIOThread();
1741 virtual ~PrincipalVerifier() = default;
1743 Result<Ok, nsCString> CheckPrincipalInfoValidity(
1744 const PrincipalInfo& aPrincipalInfo);
1746 NS_DECL_NSIRUNNABLE
1749 #endif
1751 /*******************************************************************************
1752 * Helper Functions
1753 ******************************************************************************/
1755 template <typename T, bool = std::is_unsigned_v<T>>
1756 struct IntChecker {
1757 static void Assert(T aInt) {
1758 static_assert(std::is_integral_v<T>, "Not an integer!");
1759 MOZ_ASSERT(aInt >= 0);
1763 template <typename T>
1764 struct IntChecker<T, true> {
1765 static void Assert(T aInt) {
1766 static_assert(std::is_integral_v<T>, "Not an integer!");
1770 template <typename T>
1771 void AssertNoOverflow(uint64_t aDest, T aArg) {
1772 IntChecker<T>::Assert(aDest);
1773 IntChecker<T>::Assert(aArg);
1774 MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
1777 template <typename T, typename U>
1778 void AssertNoUnderflow(T aDest, U aArg) {
1779 IntChecker<T>::Assert(aDest);
1780 IntChecker<T>::Assert(aArg);
1781 MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
1784 inline bool IsDotFile(const nsAString& aFileName) {
1785 return QuotaManager::IsDotFile(aFileName);
1788 inline bool IsOSMetadata(const nsAString& aFileName) {
1789 return QuotaManager::IsOSMetadata(aFileName);
1792 bool IsOriginMetadata(const nsAString& aFileName) {
1793 return aFileName.EqualsLiteral(METADATA_FILE_NAME) ||
1794 aFileName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
1795 IsOSMetadata(aFileName);
1798 bool IsTempMetadata(const nsAString& aFileName) {
1799 return aFileName.EqualsLiteral(METADATA_TMP_FILE_NAME) ||
1800 aFileName.EqualsLiteral(METADATA_V2_TMP_FILE_NAME);
1803 // Return whether the group was actually updated.
1804 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
1805 OriginMetadata& aOriginMetadata) {
1806 MOZ_ASSERT(!NS_IsMainThread());
1808 bool updated = false;
1810 if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
1811 if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
1812 aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
1813 updated = true;
1815 } else {
1816 OriginAttributes originAttributes;
1817 nsCString originNoSuffix;
1818 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
1819 originNoSuffix)),
1820 Err(NS_ERROR_FAILURE));
1822 RefPtr<MozURL> url;
1823 QM_TRY(MozURL::Init(getter_AddRefs(url), originNoSuffix), QM_PROPAGATE,
1824 [&originNoSuffix](const nsresult) {
1825 QM_WARNING("A URL %s is not recognized by MozURL",
1826 originNoSuffix.get());
1829 QM_TRY_INSPECT(const auto& baseDomain,
1830 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString, *url, BaseDomain));
1832 const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
1834 if (aOriginMetadata.mGroup != upToDateGroup) {
1835 aOriginMetadata.mGroup = upToDateGroup;
1836 updated = true;
1838 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1839 ContentPrincipalInfo contentPrincipalInfo;
1840 contentPrincipalInfo.attrs() = originAttributes;
1841 contentPrincipalInfo.originNoSuffix() = originNoSuffix;
1842 contentPrincipalInfo.spec() = originNoSuffix;
1843 contentPrincipalInfo.baseDomain() = baseDomain;
1845 PrincipalInfo principalInfo(contentPrincipalInfo);
1847 nsTArray<PrincipalInfo> principalInfos;
1848 principalInfos.AppendElement(principalInfo);
1850 RefPtr<PrincipalVerifier> principalVerifier =
1851 PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
1852 #endif
1856 return updated;
1859 } // namespace
1861 BackgroundThreadObject::BackgroundThreadObject()
1862 : mOwningThread(GetCurrentEventTarget()) {
1863 AssertIsOnOwningThread();
1866 BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread)
1867 : mOwningThread(aOwningThread) {}
1869 #ifdef DEBUG
1871 void BackgroundThreadObject::AssertIsOnOwningThread() const {
1872 AssertIsOnBackgroundThread();
1873 MOZ_ASSERT(mOwningThread);
1874 bool current;
1875 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
1876 MOZ_ASSERT(current);
1879 #endif // DEBUG
1881 nsIEventTarget* BackgroundThreadObject::OwningThread() const {
1882 MOZ_ASSERT(mOwningThread);
1883 return mOwningThread;
1886 bool IsOnIOThread() {
1887 QuotaManager* quotaManager = QuotaManager::Get();
1888 NS_ASSERTION(quotaManager, "Must have a manager here!");
1890 bool currentThread;
1891 return NS_SUCCEEDED(
1892 quotaManager->IOThread()->IsOnCurrentThread(&currentThread)) &&
1893 currentThread;
1896 void AssertIsOnIOThread() {
1897 NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
1900 void AssertCurrentThreadOwnsQuotaMutex() {
1901 #ifdef DEBUG
1902 QuotaManager* quotaManager = QuotaManager::Get();
1903 NS_ASSERTION(quotaManager, "Must have a manager here!");
1905 quotaManager->AssertCurrentThreadOwnsQuotaMutex();
1906 #endif
1909 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
1910 // Get leaf of file path
1911 for (const char* p = aFile; *p; ++p) {
1912 if (*p == '/' && *(p + 1)) {
1913 aFile = p + 1;
1917 nsContentUtils::LogSimpleConsoleError(
1918 NS_ConvertUTF8toUTF16(
1919 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
1920 "quota", false /* Quota Manager is not active in private browsing mode */,
1921 true /* Quota Manager runs always in a chrome context */);
1924 namespace {
1926 bool gInvalidateQuotaCache = false;
1927 StaticAutoPtr<nsString> gBasePath;
1928 StaticAutoPtr<nsString> gStorageName;
1929 StaticAutoPtr<nsCString> gBuildId;
1931 #ifdef DEBUG
1932 bool gQuotaManagerInitialized = false;
1933 #endif
1935 StaticRefPtr<QuotaManager> gInstance;
1936 mozilla::Atomic<bool> gShutdown(false);
1938 // A time stamp that can only be accessed on the main thread.
1939 TimeStamp gLastOSWake;
1941 typedef nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>
1942 NormalOriginOpArray;
1943 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
1945 // Constants for temporary storage limit computing.
1946 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
1948 void RegisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
1949 AssertIsOnBackgroundThread();
1951 if (!gNormalOriginOps) {
1952 gNormalOriginOps = new NormalOriginOpArray();
1955 gNormalOriginOps->AppendElement(&aNormalOriginOp);
1958 void UnregisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
1959 AssertIsOnBackgroundThread();
1960 MOZ_ASSERT(gNormalOriginOps);
1962 gNormalOriginOps->RemoveElement(&aNormalOriginOp);
1964 if (gNormalOriginOps->IsEmpty()) {
1965 gNormalOriginOps = nullptr;
1969 class StorageOperationBase {
1970 protected:
1971 struct OriginProps {
1972 enum Type { eChrome, eContent, eObsolete, eInvalid };
1974 NotNull<nsCOMPtr<nsIFile>> mDirectory;
1975 nsString mLeafName;
1976 nsCString mSpec;
1977 OriginAttributes mAttrs;
1978 int64_t mTimestamp;
1979 OriginMetadata mOriginMetadata;
1980 nsCString mOriginalSuffix;
1982 LazyInitializedOnceEarlyDestructible<const PersistenceType>
1983 mPersistenceType;
1984 Type mType;
1985 bool mNeedsRestore;
1986 bool mNeedsRestore2;
1987 bool mIgnore;
1989 public:
1990 explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
1991 : mDirectory(std::move(aDirectory)),
1992 mTimestamp(0),
1993 mType(eContent),
1994 mNeedsRestore(false),
1995 mNeedsRestore2(false),
1996 mIgnore(false) {}
1998 template <typename PersistenceTypeFunc>
1999 nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
2002 nsTArray<OriginProps> mOriginProps;
2004 nsCOMPtr<nsIFile> mDirectory;
2006 public:
2007 explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
2008 AssertIsOnIOThread();
2011 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
2013 protected:
2014 virtual ~StorageOperationBase() = default;
2016 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
2017 nsACString& aGroup, nsACString& aOrigin,
2018 Nullable<bool>& aIsApp);
2020 // Upgrade helper to load the contents of ".metadata-v2" files from previous
2021 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
2022 // method, it is only intended to read current version ".metadata-v2" files.
2023 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
2024 // because our "storage.sqlite" lets us track the overall version of the
2025 // storage directory.
2026 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
2027 nsACString& aSuffix, nsACString& aGroup,
2028 nsACString& aOrigin, bool& aIsApp);
2030 int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
2032 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
2035 * Rename the origin if the origin string generation from nsIPrincipal
2036 * changed. This consists of renaming the origin in the metadata files and
2037 * renaming the origin directory itself. For simplicity, the origin in
2038 * metadata files is not actually updated, but the metadata files are
2039 * recreated instead.
2041 * @param aOriginProps the properties of the origin to check.
2043 * @return whether origin was renamed.
2045 Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
2047 nsresult ProcessOriginDirectories();
2049 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
2052 class MOZ_STACK_CLASS OriginParser final {
2053 public:
2054 enum ResultType { InvalidOrigin, ObsoleteOrigin, ValidOrigin };
2056 private:
2057 using Tokenizer =
2058 nsCCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>;
2060 enum SchemeType { eNone, eFile, eAbout, eChrome };
2062 enum State {
2063 eExpectingAppIdOrScheme,
2064 eExpectingInMozBrowser,
2065 eExpectingScheme,
2066 eExpectingEmptyToken1,
2067 eExpectingEmptyToken2,
2068 eExpectingEmptyTokenOrUniversalFileOrigin,
2069 eExpectingHost,
2070 eExpectingPort,
2071 eExpectingEmptyTokenOrDriveLetterOrPathnameComponent,
2072 eExpectingEmptyTokenOrPathnameComponent,
2073 eExpectingEmptyToken1OrHost,
2075 // We transit from eExpectingHost to this state when we encounter a host
2076 // beginning with "[" which indicates an IPv6 literal. Because we mangle the
2077 // IPv6 ":" delimiter to be a "+", we will receive separate tokens for each
2078 // portion of the IPv6 address, including a final token that ends with "]".
2079 // (Note that we do not mangle "[" or "]".) Note that the URL spec
2080 // explicitly disclaims support for "<zone_id>" and so we don't have to deal
2081 // with that.
2082 eExpectingIPV6Token,
2083 eComplete,
2084 eHandledTrailingSeparator
2087 const nsCString mOrigin;
2088 Tokenizer mTokenizer;
2090 nsCString mScheme;
2091 nsCString mHost;
2092 Nullable<uint32_t> mPort;
2093 nsTArray<nsCString> mPathnameComponents;
2094 nsCString mHandledTokens;
2096 SchemeType mSchemeType;
2097 State mState;
2098 bool mInIsolatedMozBrowser;
2099 bool mUniversalFileOrigin;
2100 bool mMaybeDriveLetter;
2101 bool mError;
2102 bool mMaybeObsolete;
2104 // Number of group which a IPv6 address has. Should be less than 9.
2105 uint8_t mIPGroup;
2107 public:
2108 explicit OriginParser(const nsACString& aOrigin)
2109 : mOrigin(aOrigin),
2110 mTokenizer(aOrigin, '+'),
2111 mPort(),
2112 mSchemeType(eNone),
2113 mState(eExpectingAppIdOrScheme),
2114 mInIsolatedMozBrowser(false),
2115 mUniversalFileOrigin(false),
2116 mMaybeDriveLetter(false),
2117 mError(false),
2118 mMaybeObsolete(false),
2119 mIPGroup(0) {}
2121 static ResultType ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
2122 OriginAttributes* aAttrs,
2123 nsCString& aOriginalSuffix);
2125 ResultType Parse(nsACString& aSpec);
2127 private:
2128 void HandleScheme(const nsDependentCSubstring& aToken);
2130 void HandlePathnameComponent(const nsDependentCSubstring& aToken);
2132 void HandleToken(const nsDependentCSubstring& aToken);
2134 void HandleTrailingSeparator();
2137 class RepositoryOperationBase : public StorageOperationBase {
2138 public:
2139 explicit RepositoryOperationBase(nsIFile* aDirectory)
2140 : StorageOperationBase(aDirectory) {}
2142 nsresult ProcessRepository();
2144 protected:
2145 virtual ~RepositoryOperationBase() = default;
2147 template <typename UpgradeMethod>
2148 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
2149 UpgradeMethod aMethod);
2151 private:
2152 virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
2154 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2155 bool* aRemoved) = 0;
2157 virtual nsresult PrepareClientDirectory(nsIFile* aFile,
2158 const nsAString& aLeafName,
2159 bool& aRemoved);
2162 class CreateOrUpgradeDirectoryMetadataHelper final
2163 : public RepositoryOperationBase {
2164 nsCOMPtr<nsIFile> mPermanentStorageDir;
2166 // The legacy PersistenceType, before the default repository introduction.
2167 enum class LegacyPersistenceType {
2168 Persistent = 0,
2169 Temporary
2170 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
2171 // it here.
2174 LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
2176 public:
2177 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
2178 : RepositoryOperationBase(aDirectory) {}
2180 nsresult Init();
2182 private:
2183 Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
2184 const fallible_t&);
2186 PersistenceType PersistenceTypeFromLegacyPersistentSpec(
2187 const nsCString& aSpec);
2189 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
2191 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
2193 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2194 bool* aRemoved) override;
2196 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2199 class UpgradeStorageHelperBase : public RepositoryOperationBase {
2200 LazyInitializedOnce<const PersistenceType> mPersistenceType;
2202 public:
2203 explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
2204 : RepositoryOperationBase(aDirectory) {}
2206 nsresult Init();
2208 private:
2209 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
2212 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
2213 public:
2214 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
2215 : UpgradeStorageHelperBase(aDirectory) {}
2217 private:
2218 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2219 bool* aRemoved) override;
2221 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2224 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
2225 public:
2226 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
2227 : UpgradeStorageHelperBase(aDirectory) {}
2229 private:
2230 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
2233 * Remove the origin directory if appId is present in origin attributes.
2235 * @param aOriginProps the properties of the origin to check.
2237 * @return whether the origin directory was removed.
2239 Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
2241 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2242 bool* aRemoved) override;
2244 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2247 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
2248 public:
2249 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
2250 : UpgradeStorageHelperBase(aDirectory) {}
2252 private:
2253 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2254 bool* aRemoved) override;
2256 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2259 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
2260 public:
2261 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
2262 : UpgradeStorageHelperBase(aDirectory) {}
2264 private:
2265 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2266 bool* aRemoved) override;
2268 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2270 nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
2271 bool& aRemoved) override;
2274 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
2275 LazyInitializedOnce<const PersistenceType> mPersistenceType;
2277 public:
2278 explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
2279 : StorageOperationBase(aDirectory) {}
2281 nsresult Init();
2283 nsresult RestoreMetadata2File();
2285 private:
2286 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2289 auto MakeSanitizedOriginCString(const nsACString& aOrigin) {
2290 #ifdef XP_WIN
2291 NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars,
2292 FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
2293 "Illegal file characters have changed!");
2294 #endif
2296 nsAutoCString res{aOrigin};
2298 res.ReplaceChar(QuotaManager::kReplaceChars, '+');
2300 return res;
2303 auto MakeSanitizedOriginString(const nsACString& aOrigin) {
2304 // An origin string is ASCII-only, since it is obtained via
2305 // nsIPrincipal::GetOrigin, which returns an ACString.
2306 return NS_ConvertASCIItoUTF16(MakeSanitizedOriginCString(aOrigin));
2309 Result<nsAutoString, nsresult> GetPathForStorage(
2310 nsIFile& aBaseDir, const nsAString& aStorageName) {
2311 QM_TRY_INSPECT(const auto& storageDir,
2312 CloneFileAndAppend(aBaseDir, aStorageName));
2314 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, storageDir, GetPath));
2317 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
2318 AssertIsOnIOThread();
2320 class MOZ_STACK_CLASS Helper final {
2321 public:
2322 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
2323 AssertIsOnIOThread();
2324 MOZ_ASSERT(aFile);
2325 MOZ_ASSERT(aTimestamp);
2327 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
2329 switch (dirEntryKind) {
2330 case nsIFileKind::ExistsAsDirectory:
2331 QM_TRY(CollectEachFile(*aFile,
2332 [&aTimestamp](const nsCOMPtr<nsIFile>& file)
2333 -> Result<mozilla::Ok, nsresult> {
2334 QM_TRY(
2335 GetLastModifiedTime(file, aTimestamp));
2337 return Ok{};
2338 }));
2339 break;
2341 case nsIFileKind::ExistsAsFile: {
2342 QM_TRY_INSPECT(
2343 const auto& leafName,
2344 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, aFile, GetLeafName));
2346 // Bug 1595445 will handle unknown files here.
2348 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
2349 IsDotFile(leafName)) {
2350 return NS_OK;
2353 QM_TRY_UNWRAP(int64_t timestamp,
2354 MOZ_TO_RESULT_INVOKE(aFile, GetLastModifiedTime));
2356 // Need to convert from milliseconds to microseconds.
2357 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
2358 timestamp *= int64_t(PR_USEC_PER_MSEC);
2360 if (timestamp > *aTimestamp) {
2361 *aTimestamp = timestamp;
2363 break;
2366 case nsIFileKind::DoesNotExist:
2367 // Ignore files that got removed externally while iterating.
2368 break;
2371 return NS_OK;
2375 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
2376 return PR_Now();
2379 int64_t timestamp = INT64_MIN;
2380 nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
2381 if (NS_FAILED(rv)) {
2382 timestamp = PR_Now();
2385 return timestamp;
2388 // Returns a bool indicating whether the directory was newly created.
2389 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
2390 AssertIsOnIOThread();
2392 // Callers call this function without checking if the file already exists
2393 // (idempotent usage). QM_OR_ELSE_WARN is not used here since we want to
2394 // ignore NS_ERROR_FILE_ALREADY_EXISTS completely.
2395 QM_TRY_INSPECT(
2396 const auto& exists,
2397 MOZ_TO_RESULT_INVOKE(aDirectory, Create, nsIFile::DIRECTORY_TYPE, 0755)
2398 .map([](Ok) { return false; })
2399 .orElse(ErrToOkOrErr<NS_ERROR_FILE_ALREADY_EXISTS, true>));
2401 if (exists) {
2402 QM_TRY_INSPECT(const bool& isDirectory,
2403 MOZ_TO_RESULT_INVOKE(aDirectory, IsDirectory));
2404 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
2407 return !exists;
2410 enum FileFlag { Truncate, Update, Append };
2412 Result<nsCOMPtr<nsIOutputStream>, nsresult> GetOutputStream(
2413 nsIFile& aFile, FileFlag aFileFlag) {
2414 AssertIsOnIOThread();
2416 switch (aFileFlag) {
2417 case FileFlag::Truncate:
2418 QM_TRY_RETURN(NS_NewLocalFileOutputStream(&aFile));
2420 case FileFlag::Update: {
2421 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(&aFile, Exists));
2423 if (!exists) {
2424 return nsCOMPtr<nsIOutputStream>();
2427 QM_TRY_INSPECT(const auto& stream, NS_NewLocalFileStream(&aFile));
2429 nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream);
2430 QM_TRY(OkIf(outputStream), Err(NS_ERROR_FAILURE));
2432 return outputStream;
2435 case FileFlag::Append:
2436 QM_TRY_RETURN(NS_NewLocalFileOutputStream(
2437 &aFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND));
2439 default:
2440 MOZ_CRASH("Should never get here!");
2444 Result<nsCOMPtr<nsIBinaryOutputStream>, nsresult> GetBinaryOutputStream(
2445 nsIFile& aFile, FileFlag aFileFlag) {
2446 QM_TRY_UNWRAP(auto outputStream, GetOutputStream(aFile, aFileFlag));
2448 QM_TRY(OkIf(outputStream), Err(NS_ERROR_UNEXPECTED));
2450 return nsCOMPtr<nsIBinaryOutputStream>(
2451 NS_NewObjectOutputStream(outputStream));
2454 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
2455 aJarPrefix.Truncate();
2457 // Fallback.
2458 if (!aInIsolatedMozBrowser) {
2459 return;
2462 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
2463 // 1320404).
2464 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
2465 aJarPrefix.AppendInt(0); // TODO: this is the appId, to be removed.
2466 aJarPrefix.Append('+');
2467 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
2468 aJarPrefix.Append('+');
2471 nsresult CreateDirectoryMetadata(nsIFile& aDirectory, int64_t aTimestamp,
2472 const OriginMetadata& aOriginMetadata) {
2473 AssertIsOnIOThread();
2475 OriginAttributes groupAttributes;
2477 nsCString groupNoSuffix;
2478 QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
2479 groupNoSuffix)),
2480 NS_ERROR_FAILURE);
2482 nsCString groupPrefix;
2483 GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
2485 nsCString group = groupPrefix + groupNoSuffix;
2487 OriginAttributes originAttributes;
2489 nsCString originNoSuffix;
2490 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
2491 originNoSuffix)),
2492 NS_ERROR_FAILURE);
2494 nsCString originPrefix;
2495 GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
2497 nsCString origin = originPrefix + originNoSuffix;
2499 MOZ_ASSERT(groupPrefix == originPrefix);
2501 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_TYPED(
2502 nsCOMPtr<nsIFile>, aDirectory, Clone));
2504 QM_TRY(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME)));
2506 QM_TRY_INSPECT(const auto& stream,
2507 GetBinaryOutputStream(*file, FileFlag::Truncate));
2508 MOZ_ASSERT(stream);
2510 QM_TRY(stream->Write64(aTimestamp));
2512 QM_TRY(stream->WriteStringZ(group.get()));
2514 QM_TRY(stream->WriteStringZ(origin.get()));
2516 // Currently unused (used to be isApp).
2517 QM_TRY(stream->WriteBoolean(false));
2519 QM_TRY(stream->Flush());
2521 QM_TRY(stream->Close());
2523 QM_TRY(file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME)));
2525 return NS_OK;
2528 nsresult CreateDirectoryMetadata2(nsIFile& aDirectory, int64_t aTimestamp,
2529 bool aPersisted,
2530 const OriginMetadata& aOriginMetadata) {
2531 AssertIsOnIOThread();
2533 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_TYPED(
2534 nsCOMPtr<nsIFile>, aDirectory, Clone));
2536 QM_TRY(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME)));
2538 QM_TRY_INSPECT(const auto& stream,
2539 GetBinaryOutputStream(*file, FileFlag::Truncate));
2540 MOZ_ASSERT(stream);
2542 QM_TRY(stream->Write64(aTimestamp));
2544 QM_TRY(stream->WriteBoolean(aPersisted));
2546 // Reserved data 1
2547 QM_TRY(stream->Write32(0));
2549 // Reserved data 2
2550 QM_TRY(stream->Write32(0));
2552 // The suffix isn't used right now, but we might need it in future. It's
2553 // a bit of redundancy we can live with given how painful is to upgrade
2554 // metadata files.
2555 QM_TRY(stream->WriteStringZ(aOriginMetadata.mSuffix.get()));
2557 QM_TRY(stream->WriteStringZ(aOriginMetadata.mGroup.get()));
2559 QM_TRY(stream->WriteStringZ(aOriginMetadata.mOrigin.get()));
2561 // Currently unused (used to be isApp).
2562 QM_TRY(stream->WriteBoolean(false));
2564 QM_TRY(stream->Flush());
2566 QM_TRY(stream->Close());
2568 QM_TRY(file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME)));
2570 return NS_OK;
2573 Result<nsCOMPtr<nsIBinaryInputStream>, nsresult> GetBinaryInputStream(
2574 nsIFile& aDirectory, const nsAString& aFilename) {
2575 MOZ_ASSERT(!NS_IsMainThread());
2577 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_TYPED(
2578 nsCOMPtr<nsIFile>, aDirectory, Clone));
2580 QM_TRY(file->Append(aFilename));
2582 QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file));
2584 QM_TRY_INSPECT(const auto& bufferedStream,
2585 NS_NewBufferedInputStream(stream.forget(), 512));
2587 QM_TRY(OkIf(bufferedStream), Err(NS_ERROR_FAILURE));
2589 return nsCOMPtr<nsIBinaryInputStream>(
2590 NS_NewObjectInputStream(bufferedStream));
2593 // This method computes and returns our best guess for the temporary storage
2594 // limit (in bytes), based on available space.
2595 uint64_t GetTemporaryStorageLimit(uint64_t aAvailableSpaceBytes) {
2596 // The fixed limit pref can be used to override temporary storage limit
2597 // calculation.
2598 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
2599 return static_cast<uint64_t>(
2600 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
2601 1024;
2604 uint64_t availableSpaceKB = aAvailableSpaceBytes / 1024;
2606 // Prevent division by zero below.
2607 uint32_t chunkSizeKB;
2608 if (StaticPrefs::dom_quotaManager_temporaryStorage_chunkSize()) {
2609 chunkSizeKB = StaticPrefs::dom_quotaManager_temporaryStorage_chunkSize();
2610 } else {
2611 chunkSizeKB = kDefaultChunkSizeKB;
2614 // Grow/shrink in chunkSizeKB units, deliberately, so that in the common case
2615 // we don't shrink temporary storage and evict origin data every time we
2616 // initialize.
2617 availableSpaceKB = (availableSpaceKB / chunkSizeKB) * chunkSizeKB;
2619 // Allow temporary storage to consume up to half the available space.
2620 return availableSpaceKB * .50 * 1024;
2623 } // namespace
2625 /*******************************************************************************
2626 * Exported functions
2627 ******************************************************************************/
2629 void InitializeQuotaManager() {
2630 MOZ_ASSERT(XRE_IsParentProcess());
2631 MOZ_ASSERT(NS_IsMainThread());
2632 MOZ_ASSERT(!gQuotaManagerInitialized);
2634 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
2635 ScopedLogExtraInfo::Initialize();
2636 #endif
2638 if (!QuotaManager::IsRunningGTests()) {
2639 // This service has to be started on the main thread currently.
2640 const nsCOMPtr<mozIStorageService> ss =
2641 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
2643 QM_WARNONLY_TRY(OkIf(ss));
2646 QM_WARNONLY_TRY(QuotaManager::Initialize());
2648 #ifdef DEBUG
2649 gQuotaManagerInitialized = true;
2650 #endif
2653 PQuotaParent* AllocPQuotaParent() {
2654 AssertIsOnBackgroundThread();
2656 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
2657 return nullptr;
2660 auto actor = MakeRefPtr<Quota>();
2662 return actor.forget().take();
2665 bool DeallocPQuotaParent(PQuotaParent* aActor) {
2666 AssertIsOnBackgroundThread();
2667 MOZ_ASSERT(aActor);
2669 RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
2670 return true;
2673 bool RecvShutdownQuotaManager() {
2674 AssertIsOnBackgroundThread();
2676 QuotaManager::ShutdownInstance();
2678 return true;
2681 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
2683 // static
2684 nsresult QuotaManager::Observer::Initialize() {
2685 MOZ_ASSERT(NS_IsMainThread());
2687 RefPtr<Observer> observer = new Observer();
2689 nsresult rv = observer->Init();
2690 if (NS_WARN_IF(NS_FAILED(rv))) {
2691 return rv;
2694 sInstance = observer;
2696 return NS_OK;
2699 // static
2700 void QuotaManager::Observer::ShutdownCompleted() {
2701 MOZ_ASSERT(NS_IsMainThread());
2702 MOZ_ASSERT(sInstance);
2704 sInstance->mShutdownComplete = true;
2707 nsresult QuotaManager::Observer::Init() {
2708 MOZ_ASSERT(NS_IsMainThread());
2710 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2711 if (NS_WARN_IF(!obs)) {
2712 return NS_ERROR_FAILURE;
2715 // XXX: Improve the way that we remove observer in failure cases.
2716 nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
2717 if (NS_WARN_IF(NS_FAILED(rv))) {
2718 return rv;
2721 rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
2722 if (NS_WARN_IF(NS_FAILED(rv))) {
2723 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2724 return rv;
2727 rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
2728 if (NS_WARN_IF(NS_FAILED(rv))) {
2729 obs->RemoveObserver(this, kProfileDoChangeTopic);
2730 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2731 return rv;
2734 rv = obs->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
2735 if (NS_WARN_IF(NS_FAILED(rv))) {
2736 obs->RemoveObserver(this, kProfileDoChangeTopic);
2737 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2738 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
2739 return rv;
2742 return NS_OK;
2745 nsresult QuotaManager::Observer::Shutdown() {
2746 MOZ_ASSERT(NS_IsMainThread());
2748 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2749 if (NS_WARN_IF(!obs)) {
2750 return NS_ERROR_FAILURE;
2753 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
2754 MOZ_ALWAYS_SUCCEEDS(
2755 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
2756 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
2757 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
2759 sInstance = nullptr;
2761 // In general, the instance will have died after the latter removal call, so
2762 // it's not safe to do anything after that point.
2763 // However, Shutdown is currently called from Observe which is called by the
2764 // Observer Service which holds a strong reference to the observer while the
2765 // Observe method is being called.
2767 return NS_OK;
2770 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
2772 NS_IMETHODIMP
2773 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
2774 const char16_t* aData) {
2775 MOZ_ASSERT(NS_IsMainThread());
2777 nsresult rv;
2779 if (!strcmp(aTopic, kProfileDoChangeTopic)) {
2780 if (NS_WARN_IF(gBasePath)) {
2781 NS_WARNING(
2782 "profile-before-change-qm must precede repeated "
2783 "profile-do-change!");
2784 return NS_OK;
2787 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, true);
2789 gBasePath = new nsString();
2791 nsCOMPtr<nsIFile> baseDir;
2792 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
2793 getter_AddRefs(baseDir));
2794 if (NS_FAILED(rv)) {
2795 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
2796 getter_AddRefs(baseDir));
2798 if (NS_WARN_IF(NS_FAILED(rv))) {
2799 return rv;
2802 rv = baseDir->GetPath(*gBasePath);
2803 if (NS_WARN_IF(NS_FAILED(rv))) {
2804 return rv;
2807 gStorageName = new nsString();
2809 rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
2810 if (NS_FAILED(rv)) {
2811 *gStorageName = kStorageName;
2814 gBuildId = new nsCString();
2816 nsCOMPtr<nsIPlatformInfo> platformInfo =
2817 do_GetService("@mozilla.org/xre/app-info;1");
2818 if (NS_WARN_IF(!platformInfo)) {
2819 return NS_ERROR_FAILURE;
2822 rv = platformInfo->GetPlatformBuildID(*gBuildId);
2823 if (NS_WARN_IF(NS_FAILED(rv))) {
2824 return rv;
2827 return NS_OK;
2830 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
2831 if (NS_WARN_IF(!gBasePath)) {
2832 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
2833 return NS_OK;
2836 // mPendingProfileChange is our re-entrancy guard (the nested event loop
2837 // below may cause re-entrancy).
2838 if (mPendingProfileChange) {
2839 return NS_OK;
2842 AutoRestore<bool> pending(mPendingProfileChange);
2843 mPendingProfileChange = true;
2845 mShutdownComplete = false;
2847 PBackgroundChild* backgroundActor =
2848 BackgroundChild::GetOrCreateForCurrentThread();
2849 if (NS_WARN_IF(!backgroundActor)) {
2850 return NS_ERROR_FAILURE;
2853 if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
2854 return NS_ERROR_FAILURE;
2857 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
2859 gBasePath = nullptr;
2861 gStorageName = nullptr;
2863 gBuildId = nullptr;
2865 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, false);
2867 return NS_OK;
2870 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2871 rv = Shutdown();
2872 if (NS_WARN_IF(NS_FAILED(rv))) {
2873 return rv;
2876 return NS_OK;
2879 if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
2880 gLastOSWake = TimeStamp::Now();
2882 return NS_OK;
2885 NS_WARNING("Unknown observer topic!");
2886 return NS_OK;
2889 /*******************************************************************************
2890 * Quota object
2891 ******************************************************************************/
2893 void QuotaObject::AddRef() {
2894 QuotaManager* quotaManager = QuotaManager::Get();
2895 if (!quotaManager) {
2896 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2898 ++mRefCnt;
2900 return;
2903 MutexAutoLock lock(quotaManager->mQuotaMutex);
2905 ++mRefCnt;
2908 void QuotaObject::Release() {
2909 QuotaManager* quotaManager = QuotaManager::Get();
2910 if (!quotaManager) {
2911 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2913 nsrefcnt count = --mRefCnt;
2914 if (count == 0) {
2915 mRefCnt = 1;
2916 delete this;
2919 return;
2923 MutexAutoLock lock(quotaManager->mQuotaMutex);
2925 --mRefCnt;
2927 if (mRefCnt > 0) {
2928 return;
2931 if (mOriginInfo) {
2932 mOriginInfo->mQuotaObjects.Remove(mPath);
2936 delete this;
2939 bool QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) {
2940 QuotaManager* quotaManager = QuotaManager::Get();
2941 MOZ_ASSERT(quotaManager);
2943 MutexAutoLock lock(quotaManager->mQuotaMutex);
2945 return LockedMaybeUpdateSize(aSize, aTruncate);
2948 bool QuotaObject::IncreaseSize(int64_t aDelta) {
2949 MOZ_ASSERT(aDelta >= 0);
2951 QuotaManager* quotaManager = QuotaManager::Get();
2952 MOZ_ASSERT(quotaManager);
2954 MutexAutoLock lock(quotaManager->mQuotaMutex);
2956 AssertNoOverflow(mSize, aDelta);
2957 int64_t size = mSize + aDelta;
2959 return LockedMaybeUpdateSize(size, /* aTruncate */ false);
2962 void QuotaObject::DisableQuotaCheck() {
2963 QuotaManager* quotaManager = QuotaManager::Get();
2964 MOZ_ASSERT(quotaManager);
2966 MutexAutoLock lock(quotaManager->mQuotaMutex);
2968 mQuotaCheckDisabled = true;
2971 void QuotaObject::EnableQuotaCheck() {
2972 QuotaManager* quotaManager = QuotaManager::Get();
2973 MOZ_ASSERT(quotaManager);
2975 MutexAutoLock lock(quotaManager->mQuotaMutex);
2977 mQuotaCheckDisabled = false;
2980 bool QuotaObject::LockedMaybeUpdateSize(int64_t aSize, bool aTruncate) {
2981 QuotaManager* quotaManager = QuotaManager::Get();
2982 MOZ_ASSERT(quotaManager);
2984 quotaManager->mQuotaMutex.AssertCurrentThreadOwns();
2986 if (mWritingDone == false && mOriginInfo) {
2987 mWritingDone = true;
2988 StorageActivityService::SendActivity(mOriginInfo->mOrigin);
2991 if (mQuotaCheckDisabled) {
2992 return true;
2995 if (mSize == aSize) {
2996 return true;
2999 if (!mOriginInfo) {
3000 mSize = aSize;
3001 return true;
3004 GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
3005 MOZ_ASSERT(groupInfo);
3007 if (mSize > aSize) {
3008 if (aTruncate) {
3009 const int64_t delta = mSize - aSize;
3011 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
3012 quotaManager->mTemporaryStorageUsage -= delta;
3014 if (!mOriginInfo->LockedPersisted()) {
3015 AssertNoUnderflow(groupInfo->mUsage, delta);
3016 groupInfo->mUsage -= delta;
3019 AssertNoUnderflow(mOriginInfo->mUsage, delta);
3020 mOriginInfo->mUsage -= delta;
3022 MOZ_ASSERT(mOriginInfo->mClientUsages[mClientType].isSome());
3023 AssertNoUnderflow(mOriginInfo->mClientUsages[mClientType].value(), delta);
3024 mOriginInfo->mClientUsages[mClientType] =
3025 Some(mOriginInfo->mClientUsages[mClientType].value() - delta);
3027 mSize = aSize;
3029 return true;
3032 MOZ_ASSERT(mSize < aSize);
3034 RefPtr<GroupInfo> complementaryGroupInfo =
3035 groupInfo->mGroupInfoPair->LockedGetGroupInfo(
3036 ComplementaryPersistenceType(groupInfo->mPersistenceType));
3038 uint64_t delta = aSize - mSize;
3040 AssertNoOverflow(mOriginInfo->mClientUsages[mClientType].valueOr(0), delta);
3041 uint64_t newClientUsage =
3042 mOriginInfo->mClientUsages[mClientType].valueOr(0) + delta;
3044 AssertNoOverflow(mOriginInfo->mUsage, delta);
3045 uint64_t newUsage = mOriginInfo->mUsage + delta;
3047 // Temporary storage has no limit for origin usage (there's a group and the
3048 // global limit though).
3050 uint64_t newGroupUsage = groupInfo->mUsage;
3051 if (!mOriginInfo->LockedPersisted()) {
3052 AssertNoOverflow(groupInfo->mUsage, delta);
3053 newGroupUsage += delta;
3055 uint64_t groupUsage = groupInfo->mUsage;
3056 if (complementaryGroupInfo) {
3057 AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
3058 groupUsage += complementaryGroupInfo->mUsage;
3061 // Temporary storage has a hard limit for group usage (20 % of the global
3062 // limit).
3063 AssertNoOverflow(groupUsage, delta);
3064 if (groupUsage + delta > quotaManager->GetGroupLimit()) {
3065 return false;
3069 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
3070 uint64_t newTemporaryStorageUsage =
3071 quotaManager->mTemporaryStorageUsage + delta;
3073 if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
3074 // This will block the thread without holding the lock while waitting.
3076 AutoTArray<RefPtr<OriginDirectoryLock>, 10> locks;
3077 uint64_t sizeToBeFreed;
3079 if (IsOnBackgroundThread()) {
3080 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3082 sizeToBeFreed = quotaManager->CollectOriginsForEviction(delta, locks);
3083 } else {
3084 sizeToBeFreed =
3085 quotaManager->LockedCollectOriginsForEviction(delta, locks);
3088 if (!sizeToBeFreed) {
3089 uint64_t usage = quotaManager->mTemporaryStorageUsage;
3091 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3093 quotaManager->NotifyStoragePressure(usage);
3095 return false;
3098 NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
3101 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3103 for (const auto& lock : locks) {
3104 quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType(),
3105 lock->Origin());
3109 // Relocked.
3111 NS_ASSERTION(mOriginInfo, "How come?!");
3113 for (const auto& lock : locks) {
3114 MOZ_ASSERT(!(lock->GetPersistenceType() == groupInfo->mPersistenceType &&
3115 lock->Origin() == mOriginInfo->mOrigin),
3116 "Deleted itself!");
3118 quotaManager->LockedRemoveQuotaForOrigin(lock->GetPersistenceType(),
3119 lock->OriginMetadata());
3122 // We unlocked and relocked several times so we need to recompute all the
3123 // essential variables and recheck the group limit.
3125 AssertNoUnderflow(aSize, mSize);
3126 delta = aSize - mSize;
3128 AssertNoOverflow(mOriginInfo->mClientUsages[mClientType].valueOr(0), delta);
3129 newClientUsage = mOriginInfo->mClientUsages[mClientType].valueOr(0) + delta;
3131 AssertNoOverflow(mOriginInfo->mUsage, delta);
3132 newUsage = mOriginInfo->mUsage + delta;
3134 newGroupUsage = groupInfo->mUsage;
3135 if (!mOriginInfo->LockedPersisted()) {
3136 AssertNoOverflow(groupInfo->mUsage, delta);
3137 newGroupUsage += delta;
3139 uint64_t groupUsage = groupInfo->mUsage;
3140 if (complementaryGroupInfo) {
3141 AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
3142 groupUsage += complementaryGroupInfo->mUsage;
3145 AssertNoOverflow(groupUsage, delta);
3146 if (groupUsage + delta > quotaManager->GetGroupLimit()) {
3147 // Unfortunately some other thread increased the group usage in the
3148 // meantime and we are not below the group limit anymore.
3150 // However, the origin eviction must be finalized in this case too.
3151 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3153 quotaManager->FinalizeOriginEviction(std::move(locks));
3155 return false;
3159 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
3160 newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
3162 NS_ASSERTION(
3163 newTemporaryStorageUsage <= quotaManager->mTemporaryStorageLimit,
3164 "How come?!");
3166 // Ok, we successfully freed enough space and the operation can continue
3167 // without throwing the quota error.
3168 mOriginInfo->mClientUsages[mClientType] = Some(newClientUsage);
3170 mOriginInfo->mUsage = newUsage;
3171 if (!mOriginInfo->LockedPersisted()) {
3172 groupInfo->mUsage = newGroupUsage;
3174 quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
3177 // Some other thread could increase the size in the meantime, but no more
3178 // than this one.
3179 MOZ_ASSERT(mSize < aSize);
3180 mSize = aSize;
3182 // Finally, release IO thread only objects and allow next synchronized
3183 // ops for the evicted origins.
3184 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3186 quotaManager->FinalizeOriginEviction(std::move(locks));
3188 return true;
3191 mOriginInfo->mClientUsages[mClientType] = Some(newClientUsage);
3193 mOriginInfo->mUsage = newUsage;
3194 if (!mOriginInfo->LockedPersisted()) {
3195 groupInfo->mUsage = newGroupUsage;
3197 quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
3199 mSize = aSize;
3201 return true;
3204 /*******************************************************************************
3205 * Quota manager
3206 ******************************************************************************/
3208 QuotaManager::QuotaManager(const nsAString& aBasePath,
3209 const nsAString& aStorageName)
3210 : mQuotaMutex("QuotaManager.mQuotaMutex"),
3211 mBasePath(aBasePath),
3212 mStorageName(aStorageName),
3213 mTemporaryStorageUsage(0),
3214 mNextDirectoryLockId(0),
3215 mTemporaryStorageInitialized(false),
3216 mCacheUsable(false) {
3217 AssertIsOnOwningThread();
3218 MOZ_ASSERT(!gInstance);
3221 QuotaManager::~QuotaManager() {
3222 AssertIsOnOwningThread();
3223 MOZ_ASSERT(!gInstance || gInstance == this);
3226 // static
3227 nsresult QuotaManager::Initialize() {
3228 MOZ_ASSERT(NS_IsMainThread());
3230 nsresult rv = Observer::Initialize();
3231 if (NS_WARN_IF(NS_FAILED(rv))) {
3232 return rv;
3235 return NS_OK;
3238 // static
3239 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
3240 QuotaManager::GetOrCreate() {
3241 AssertIsOnBackgroundThread();
3243 if (gInstance) {
3244 return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
3247 QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
3248 NS_WARNING(
3249 "Trying to create QuotaManager before profile-do-change! "
3250 "Forgot to call do_get_profile()?");
3253 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
3254 MOZ_ASSERT(false,
3255 "Trying to create QuotaManager after profile-before-change-qm!");
3258 auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
3260 QM_TRY(instance->Init());
3262 gInstance = instance;
3264 return WrapMovingNotNullUnchecked(std::move(instance));
3267 void QuotaManager::GetOrCreate(nsIRunnable* aCallback) {
3268 AssertIsOnBackgroundThread();
3270 Unused << GetOrCreate();
3272 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
3275 // static
3276 QuotaManager* QuotaManager::Get() {
3277 // Does not return an owning reference.
3278 return gInstance;
3281 // static
3282 QuotaManager& QuotaManager::GetRef() {
3283 MOZ_ASSERT(gInstance);
3285 return *gInstance;
3288 // static
3289 bool QuotaManager::IsShuttingDown() { return gShutdown; }
3291 // static
3292 void QuotaManager::ShutdownInstance() {
3293 AssertIsOnBackgroundThread();
3295 if (gInstance) {
3296 gInstance->Shutdown();
3298 gInstance = nullptr;
3301 RefPtr<Runnable> runnable =
3302 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
3303 []() { Observer::ShutdownCompleted(); });
3304 MOZ_ASSERT(runnable);
3306 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
3309 // static
3310 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
3311 return aFileName.EqualsLiteral(DSSTORE_FILE_NAME) ||
3312 aFileName.EqualsLiteral(DESKTOP_FILE_NAME) ||
3313 aFileName.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME) ||
3314 aFileName.LowerCaseEqualsLiteral(THUMBS_DB_FILE_NAME);
3317 // static
3318 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
3319 return aFileName.First() == char16_t('.');
3322 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
3323 AssertIsOnOwningThread();
3325 mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
3327 if (aLock.ShouldUpdateLockIdTable()) {
3328 MutexAutoLock lock(mQuotaMutex);
3330 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
3331 mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
3332 WrapNotNullUnchecked(&aLock));
3335 if (aLock.ShouldUpdateLockTable()) {
3336 DirectoryLockTable& directoryLockTable =
3337 GetDirectoryLockTable(aLock.GetPersistenceType());
3339 // XXX It seems that the contents of the array are never actually used, we
3340 // just use that like an inefficient use counter. Can't we just change
3341 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
3342 directoryLockTable
3343 .LookupOrInsertWith(
3344 aLock.Origin(),
3345 [this, &aLock] {
3346 if (!IsShuttingDown()) {
3347 UpdateOriginAccessTime(aLock.GetPersistenceType(),
3348 aLock.OriginMetadata());
3350 return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
3352 ->AppendElement(WrapNotNullUnchecked(&aLock));
3355 aLock.SetRegistered(true);
3358 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
3359 AssertIsOnOwningThread();
3361 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
3363 if (aLock.ShouldUpdateLockIdTable()) {
3364 MutexAutoLock lock(mQuotaMutex);
3366 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
3367 mDirectoryLockIdTable.Remove(aLock.Id());
3370 if (aLock.ShouldUpdateLockTable()) {
3371 DirectoryLockTable& directoryLockTable =
3372 GetDirectoryLockTable(aLock.GetPersistenceType());
3374 nsTArray<NotNull<DirectoryLockImpl*>>* array;
3375 MOZ_ALWAYS_TRUE(directoryLockTable.Get(aLock.Origin(), &array));
3377 MOZ_ALWAYS_TRUE(array->RemoveElement(&aLock));
3378 if (array->IsEmpty()) {
3379 directoryLockTable.Remove(aLock.Origin());
3381 if (!IsShuttingDown()) {
3382 UpdateOriginAccessTime(aLock.GetPersistenceType(),
3383 aLock.OriginMetadata());
3388 aLock.SetRegistered(false);
3391 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
3392 AssertIsOnOwningThread();
3394 mPendingDirectoryLocks.AppendElement(&aLock);
3397 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
3398 AssertIsOnOwningThread();
3400 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
3403 uint64_t QuotaManager::CollectOriginsForEviction(
3404 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
3405 AssertIsOnOwningThread();
3406 MOZ_ASSERT(aLocks.IsEmpty());
3408 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
3409 // or maybe a generalization if that.
3411 struct MOZ_STACK_CLASS Helper final {
3412 static void GetInactiveOriginInfos(
3413 const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
3414 const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
3415 OriginInfosFlatTraversable& aInactiveOriginInfos) {
3416 for (const auto& originInfo : aOriginInfos) {
3417 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
3418 PERSISTENCE_TYPE_PERSISTENT);
3420 if (originInfo->LockedPersisted()) {
3421 continue;
3424 const auto originScope = OriginScope::FromOrigin(originInfo->mOrigin);
3426 const bool match =
3427 std::any_of(aLocks.begin(), aLocks.end(),
3428 [&originScope](const DirectoryLockImpl* const lock) {
3429 return originScope.Matches(lock->GetOriginScope());
3432 if (!match) {
3433 MOZ_ASSERT(!originInfo->mQuotaObjects.Count(),
3434 "Inactive origin shouldn't have open files!");
3435 aInactiveOriginInfos.InsertElementSorted(
3436 originInfo, OriginInfoAccessTimeComparator());
3442 // Split locks into separate arrays and filter out locks for persistent
3443 // storage, they can't block us.
3444 const auto [temporaryStorageLocks, defaultStorageLocks] = [this] {
3445 nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
3446 nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
3447 for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
3448 const Nullable<PersistenceType>& persistenceType =
3449 lock->NullablePersistenceType();
3451 if (persistenceType.IsNull()) {
3452 temporaryStorageLocks.AppendElement(lock);
3453 defaultStorageLocks.AppendElement(lock);
3454 } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3455 temporaryStorageLocks.AppendElement(lock);
3456 } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
3457 defaultStorageLocks.AppendElement(lock);
3458 } else {
3459 MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
3461 // Do nothing here, persistent origins don't need to be collected ever.
3465 return std::pair(std::move(temporaryStorageLocks),
3466 std::move(defaultStorageLocks));
3467 }();
3469 // Enumerate and process inactive origins. This must be protected by the
3470 // mutex.
3471 MutexAutoLock lock(mQuotaMutex);
3473 const auto [inactiveOrigins, sizeToBeFreed] =
3474 [this, &temporaryStorageLocks = temporaryStorageLocks,
3475 &defaultStorageLocks = defaultStorageLocks, aMinSizeToBeFreed] {
3476 nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
3477 for (const auto& entry : mGroupInfoPairs) {
3478 const auto& pair = entry.GetData();
3480 MOZ_ASSERT(!entry.GetKey().IsEmpty());
3481 MOZ_ASSERT(pair);
3483 RefPtr<GroupInfo> groupInfo =
3484 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3485 if (groupInfo) {
3486 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
3487 temporaryStorageLocks,
3488 inactiveOrigins);
3491 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3492 if (groupInfo) {
3493 Helper::GetInactiveOriginInfos(
3494 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
3498 #ifdef DEBUG
3499 // Make sure the array is sorted correctly.
3500 const bool inactiveOriginsSorted =
3501 std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
3502 [](const auto& lhs, const auto& rhs) {
3503 return lhs->mAccessTime < rhs->mAccessTime;
3505 MOZ_ASSERT(inactiveOriginsSorted);
3506 #endif
3508 // Create a list of inactive and the least recently used origins
3509 // whose aggregate size is greater or equals the minimal size to be
3510 // freed.
3511 uint64_t sizeToBeFreed = 0;
3512 for (uint32_t count = inactiveOrigins.Length(), index = 0;
3513 index < count; index++) {
3514 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3515 inactiveOrigins.TruncateLength(index);
3516 break;
3519 sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
3522 return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
3523 }();
3525 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3526 // Success, add directory locks for these origins, so any other
3527 // operations for them will be delayed (until origin eviction is finalized).
3529 for (const auto& originInfo : inactiveOrigins) {
3530 auto lock = DirectoryLockImpl::CreateForEviction(
3531 WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
3532 originInfo->FlattenToOriginMetadata());
3534 lock->AcquireImmediately();
3536 aLocks.AppendElement(lock.forget());
3539 return sizeToBeFreed;
3542 return 0;
3545 template <typename P>
3546 void QuotaManager::CollectPendingOriginsForListing(P aPredicate) {
3547 MutexAutoLock lock(mQuotaMutex);
3549 for (const auto& entry : mGroupInfoPairs) {
3550 const auto& pair = entry.GetData();
3552 MOZ_ASSERT(!entry.GetKey().IsEmpty());
3553 MOZ_ASSERT(pair);
3555 RefPtr<GroupInfo> groupInfo =
3556 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3557 if (groupInfo) {
3558 for (const auto& originInfo : groupInfo->mOriginInfos) {
3559 if (!originInfo->mDirectoryExists) {
3560 aPredicate(originInfo);
3567 nsresult QuotaManager::Init() {
3568 AssertIsOnOwningThread();
3570 #ifdef XP_WIN
3571 CacheUseDOSDevicePathSyntaxPrefValue();
3572 #endif
3574 QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
3576 QM_TRY_UNWRAP(
3577 do_Init(mIndexedDBPath),
3578 GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
3580 QM_TRY(baseDir->Append(mStorageName));
3582 QM_TRY_UNWRAP(do_Init(mStoragePath),
3583 MOZ_TO_RESULT_INVOKE_TYPED(nsString, baseDir, GetPath));
3585 QM_TRY_UNWRAP(
3586 do_Init(mPermanentStoragePath),
3587 GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
3589 QM_TRY_UNWRAP(
3590 do_Init(mTemporaryStoragePath),
3591 GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
3593 QM_TRY_UNWRAP(
3594 do_Init(mDefaultStoragePath),
3595 GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
3597 QM_TRY_UNWRAP(do_Init(mIOThread),
3598 ToResultInvoke<nsCOMPtr<nsIThread>>(
3599 MOZ_SELECT_OVERLOAD(NS_NewNamedThread), "QuotaManager IO"));
3601 // Make a timer here to avoid potential failures later. We don't actually
3602 // initialize the timer until shutdown.
3603 nsCOMPtr shutdownTimer = NS_NewTimer();
3604 QM_TRY(OkIf(shutdownTimer), Err(NS_ERROR_FAILURE));
3606 mShutdownTimer.init(WrapNotNullUnchecked(std::move(shutdownTimer)));
3608 static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
3609 Client::LS == 3 && Client::TYPE_MAX == 4,
3610 "Fix the registration!");
3612 // Register clients.
3613 auto clients = decltype(mClients)::ValueType{};
3614 clients.AppendElement(indexedDB::CreateQuotaClient());
3615 clients.AppendElement(cache::CreateQuotaClient());
3616 clients.AppendElement(simpledb::CreateQuotaClient());
3617 if (NextGenLocalStorageEnabled()) {
3618 clients.AppendElement(localstorage::CreateQuotaClient());
3619 } else {
3620 clients.SetLength(Client::TypeMax());
3623 mClients.init(std::move(clients));
3625 MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
3626 "Should be using an auto array with correct capacity!");
3628 mAllClientTypes.init(ClientTypesArray{Client::Type::IDB,
3629 Client::Type::DOMCACHE,
3630 Client::Type::SDB, Client::Type::LS});
3631 mAllClientTypesExceptLS.init(ClientTypesArray{
3632 Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB});
3634 return NS_OK;
3637 void QuotaManager::MaybeRecordShutdownStep(const Client::Type aClientType,
3638 const nsACString& aStepDescription) {
3639 // Callable on any thread.
3641 MaybeRecordShutdownStep(Some(aClientType), aStepDescription);
3644 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
3645 const nsACString& aStepDescription) {
3646 // Callable on any thread.
3648 MaybeRecordShutdownStep(Nothing{}, aStepDescription);
3651 void QuotaManager::MaybeRecordShutdownStep(
3652 const Maybe<Client::Type> aClientType, const nsACString& aStepDescription) {
3653 if (!mShutdownStarted) {
3654 // We are not shutting down yet, we intentionally ignore this here to avoid
3655 // that every caller has to make a distinction for shutdown vs. non-shutdown
3656 // situations.
3657 return;
3660 const TimeDuration elapsedSinceShutdownStart =
3661 TimeStamp::NowLoRes() - *mShutdownStartedAt;
3663 const auto stepString =
3664 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
3665 nsPromiseFlatCString(aStepDescription).get());
3667 if (aClientType) {
3668 AssertIsOnBackgroundThread();
3670 mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
3671 } else {
3672 // Callable on any thread.
3673 MutexAutoLock lock(mQuotaMutex);
3675 mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
3678 #ifdef DEBUG
3679 // XXX Probably this isn't the mechanism that should be used here.
3681 NS_DebugBreak(
3682 NS_DEBUG_WARNING,
3683 nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
3684 : "quota manager"_ns + " shutdown step"_ns)
3685 .get(),
3686 stepString.get(), __FILE__, __LINE__);
3687 #endif
3690 void QuotaManager::Shutdown() {
3691 AssertIsOnOwningThread();
3692 MOZ_ASSERT(!mShutdownStarted);
3694 // Setting this flag prevents the service from being recreated and prevents
3695 // further storagess from being created.
3696 if (gShutdown.exchange(true)) {
3697 NS_ERROR("Shutdown more than once?!");
3700 StopIdleMaintenance();
3702 mShutdownStartedAt.init(TimeStamp::NowLoRes());
3703 mShutdownStarted = true;
3705 const auto& allClientTypes = AllClientTypes();
3707 bool needsToWait = false;
3708 for (Client::Type type : allClientTypes) {
3709 needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
3711 needsToWait |= static_cast<bool>(gNormalOriginOps);
3713 // If any clients cannot shutdown immediately, spin the event loop while we
3714 // wait on all the threads to close. Our timer may fire during that loop.
3715 if (needsToWait) {
3716 MOZ_ALWAYS_SUCCEEDS(
3717 (*mShutdownTimer)
3718 ->InitWithNamedFuncCallback(
3719 [](nsITimer* aTimer, void* aClosure) {
3720 auto* const quotaManager =
3721 static_cast<QuotaManager*>(aClosure);
3723 for (Client::Type type : quotaManager->AllClientTypes()) {
3724 // XXX This is a workaround to unblock shutdown, which ought
3725 // to be removed by Bug 1682326.
3726 if (type == Client::IDB) {
3727 (*quotaManager->mClients)[type]->AbortAllOperations();
3730 (*quotaManager->mClients)[type]->ForceKillActors();
3733 MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
3734 [](nsITimer* aTimer, void* aClosure) {
3735 auto* const quotaManager =
3736 static_cast<QuotaManager*>(aClosure);
3738 nsCString annotation;
3741 for (Client::Type type :
3742 quotaManager->AllClientTypes()) {
3743 auto& quotaClient =
3744 *(*quotaManager->mClients)[type];
3746 if (!quotaClient.IsShutdownCompleted()) {
3747 annotation.AppendPrintf(
3748 "%s: %s\nIntermediate steps:\n%s\n\n",
3749 Client::TypeToText(type).get(),
3750 quotaClient.GetShutdownStatus().get(),
3751 quotaManager->mShutdownSteps[type].get());
3755 if (gNormalOriginOps) {
3756 MutexAutoLock lock(quotaManager->mQuotaMutex);
3758 annotation.AppendPrintf(
3759 "QM: %zu normal origin ops "
3760 "pending\nIntermediate "
3761 "steps:\n%s\n",
3762 gNormalOriginOps->Length(),
3763 quotaManager->mQuotaManagerShutdownSteps.get());
3767 // We expect that at least one quota client didn't
3768 // complete its shutdown.
3769 MOZ_DIAGNOSTIC_ASSERT(!annotation.IsEmpty());
3771 CrashReporter::AnnotateCrashReport(
3772 CrashReporter::Annotation::
3773 QuotaManagerShutdownTimeout,
3774 annotation);
3776 MOZ_CRASH("Quota manager shutdown timed out");
3778 aClosure, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS,
3779 nsITimer::TYPE_ONE_SHOT,
3780 "quota::QuotaManager::ForceCrashTimer"));
3782 this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
3783 "quota::QuotaManager::ForceKillTimer"));
3785 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([this, &allClientTypes] {
3786 return !gNormalOriginOps &&
3787 std::all_of(allClientTypes.cbegin(), allClientTypes.cend(),
3788 [&self = *this](const auto type) {
3789 return (*self.mClients)[type]->IsShutdownCompleted();
3791 }));
3794 for (Client::Type type : allClientTypes) {
3795 (*mClients)[type]->FinalizeShutdownWorkThreads();
3798 // Cancel the timer regardless of whether it actually fired.
3799 QM_WARNONLY_TRY((*mShutdownTimer)->Cancel());
3801 // NB: It's very important that runnable is destroyed on this thread
3802 // (i.e. after we join the IO thread) because we can't release the
3803 // QuotaManager on the IO thread. This should probably use
3804 // NewNonOwningRunnableMethod ...
3805 RefPtr<Runnable> runnable =
3806 NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
3807 &QuotaManager::ShutdownStorage);
3808 MOZ_ASSERT(runnable);
3810 // Give clients a chance to cleanup IO thread only objects.
3811 QM_WARNONLY_TRY((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL));
3813 // Make sure to join with our IO thread.
3814 QM_WARNONLY_TRY((*mIOThread)->Shutdown());
3816 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
3817 lock->Invalidate();
3821 void QuotaManager::InitQuotaForOrigin(
3822 const FullOriginMetadata& aFullOriginMetadata,
3823 const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
3824 AssertIsOnIOThread();
3825 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
3827 MutexAutoLock lock(mQuotaMutex);
3829 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3830 aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
3831 aFullOriginMetadata.mGroup);
3833 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3834 groupInfo, aFullOriginMetadata.mOrigin, aClientUsages, aUsageBytes,
3835 aFullOriginMetadata.mLastAccessTime, aFullOriginMetadata.mPersisted,
3836 /* aDirectoryExists */ true));
3839 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata& aOriginMetadata) {
3840 AssertIsOnIOThread();
3841 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
3843 MutexAutoLock lock(mQuotaMutex);
3845 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3846 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
3847 aOriginMetadata.mGroup);
3849 RefPtr<OriginInfo> originInfo =
3850 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3851 if (!originInfo) {
3852 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3853 groupInfo, aOriginMetadata.mOrigin, ClientUsageArray(),
3854 /* aUsageBytes */ 0,
3855 /* aAccessTime */ PR_Now(), /* aPersisted */ false,
3856 /* aDirectoryExists */ false));
3860 int64_t QuotaManager::NoteOriginDirectoryCreated(
3861 const OriginMetadata& aOriginMetadata, bool aPersisted) {
3862 AssertIsOnIOThread();
3863 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
3865 int64_t timestamp;
3867 MutexAutoLock lock(mQuotaMutex);
3869 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3870 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
3871 aOriginMetadata.mGroup);
3873 RefPtr<OriginInfo> originInfo =
3874 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3875 if (originInfo) {
3876 timestamp = originInfo->LockedAccessTime();
3877 originInfo->mPersisted = aPersisted;
3878 originInfo->mDirectoryExists = true;
3879 } else {
3880 timestamp = PR_Now();
3881 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3882 groupInfo, aOriginMetadata.mOrigin, ClientUsageArray(),
3883 /* aUsageBytes */ 0,
3884 /* aAccessTime */ timestamp, aPersisted, /* aDirectoryExists */ true));
3887 return timestamp;
3890 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
3891 int64_t aSize) {
3892 MOZ_ASSERT(!NS_IsMainThread());
3893 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
3895 MutexAutoLock lock(mQuotaMutex);
3897 GroupInfoPair* pair;
3898 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
3899 return;
3902 RefPtr<GroupInfo> groupInfo =
3903 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
3904 if (!groupInfo) {
3905 return;
3908 RefPtr<OriginInfo> originInfo =
3909 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
3910 if (originInfo) {
3911 originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
3915 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
3916 MOZ_ASSERT(!NS_IsMainThread());
3917 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
3919 MutexAutoLock lock(mQuotaMutex);
3921 GroupInfoPair* pair;
3922 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
3923 return;
3926 RefPtr<GroupInfo> groupInfo =
3927 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
3928 if (!groupInfo) {
3929 return;
3932 RefPtr<OriginInfo> originInfo =
3933 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
3934 if (originInfo) {
3935 originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
3939 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
3940 const OriginMetadata& aOriginMetadata,
3941 Client::Type aClientType) {
3942 MOZ_ASSERT(!NS_IsMainThread());
3943 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3945 MutexAutoLock lock(mQuotaMutex);
3947 GroupInfoPair* pair;
3948 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
3949 return UsageInfo{};
3952 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3953 if (!groupInfo) {
3954 return UsageInfo{};
3957 RefPtr<OriginInfo> originInfo =
3958 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3959 if (!originInfo) {
3960 return UsageInfo{};
3963 return originInfo->LockedGetUsageForClient(aClientType);
3966 void QuotaManager::UpdateOriginAccessTime(
3967 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
3968 AssertIsOnOwningThread();
3969 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3970 MOZ_ASSERT(!IsShuttingDown());
3972 MutexAutoLock lock(mQuotaMutex);
3974 GroupInfoPair* pair;
3975 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
3976 return;
3979 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3980 if (!groupInfo) {
3981 return;
3984 RefPtr<OriginInfo> originInfo =
3985 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3986 if (originInfo) {
3987 int64_t timestamp = PR_Now();
3988 originInfo->LockedUpdateAccessTime(timestamp);
3990 MutexAutoUnlock autoUnlock(mQuotaMutex);
3992 auto op = MakeRefPtr<SaveOriginAccessTimeOp>(
3993 aPersistenceType, aOriginMetadata.mOrigin, timestamp);
3995 RegisterNormalOriginOp(*op);
3997 op->RunImmediately();
4001 void QuotaManager::RemoveQuota() {
4002 AssertIsOnIOThread();
4004 MutexAutoLock lock(mQuotaMutex);
4006 for (const auto& entry : mGroupInfoPairs) {
4007 const auto& pair = entry.GetData();
4009 MOZ_ASSERT(!entry.GetKey().IsEmpty());
4010 MOZ_ASSERT(pair);
4012 RefPtr<GroupInfo> groupInfo =
4013 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
4014 if (groupInfo) {
4015 groupInfo->LockedRemoveOriginInfos();
4018 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
4019 if (groupInfo) {
4020 groupInfo->LockedRemoveOriginInfos();
4024 mGroupInfoPairs.Clear();
4026 MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
4029 nsresult QuotaManager::LoadQuota() {
4030 AssertIsOnIOThread();
4031 MOZ_ASSERT(mStorageConnection);
4032 MOZ_ASSERT(!mTemporaryStorageInitialized);
4034 auto recordQuotaInfoLoadTimeHelper =
4035 MakeRefPtr<RecordQuotaInfoLoadTimeHelper>();
4036 recordQuotaInfoLoadTimeHelper->Start();
4038 auto LoadQuotaFromCache = [&]() -> nsresult {
4039 QM_TRY_INSPECT(
4040 const auto& stmt,
4041 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>,
4042 mStorageConnection, CreateStatement,
4043 "SELECT repository_id, suffix, group_, "
4044 "origin, client_usages, usage, "
4045 "last_access_time, accessed, persisted "
4046 "FROM origin"_ns));
4048 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
4050 QM_TRY(quota::CollectWhileHasResult(
4051 *stmt, [this](auto& stmt) -> Result<Ok, nsresult> {
4052 QM_TRY_INSPECT(const int32_t& repositoryId,
4053 MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 0));
4055 const auto maybePersistenceType =
4056 PersistenceTypeFromInt32(repositoryId, fallible);
4057 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
4059 FullOriginMetadata fullOriginMetadata;
4061 fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
4063 QM_TRY_UNWRAP(
4064 fullOriginMetadata.mSuffix,
4065 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 1));
4067 QM_TRY_UNWRAP(
4068 fullOriginMetadata.mGroup,
4069 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 2));
4071 QM_TRY_UNWRAP(
4072 fullOriginMetadata.mOrigin,
4073 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 3));
4075 QM_TRY_INSPECT(const bool& updated,
4076 MaybeUpdateGroupForOrigin(fullOriginMetadata));
4078 Unused << updated;
4080 // We don't need to update the .metadata-v2 file on disk here,
4081 // EnsureTemporaryOriginIsInitialized is responsible for doing that.
4082 // We just need to use correct group before initializing quota for the
4083 // given origin. (Note that calling LoadFullOriginMetadataWithRestore
4084 // below might update the group in the metadata file, but only as a
4085 // side-effect. The actual place we ensure consistency is in
4086 // EnsureTemporaryOriginIsInitialized.)
4088 QM_TRY_INSPECT(
4089 const auto& clientUsagesText,
4090 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, stmt, GetUTF8String, 4));
4092 ClientUsageArray clientUsages;
4093 QM_TRY(clientUsages.Deserialize(clientUsagesText));
4095 QM_TRY_INSPECT(const int64_t& usage,
4096 MOZ_TO_RESULT_INVOKE(stmt, GetInt64, 5));
4097 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
4098 MOZ_TO_RESULT_INVOKE(stmt, GetInt64, 6));
4099 QM_TRY_INSPECT(const int64_t& accessed,
4100 MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 7));
4101 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
4102 MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 8));
4104 if (accessed) {
4105 QM_TRY_INSPECT(
4106 const auto& directory,
4107 GetDirectoryForOrigin(fullOriginMetadata.mPersistenceType,
4108 fullOriginMetadata.mOrigin));
4110 QM_TRY_INSPECT(const bool& exists,
4111 MOZ_TO_RESULT_INVOKE(directory, Exists));
4113 QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));
4115 QM_TRY_INSPECT(const bool& isDirectory,
4116 MOZ_TO_RESULT_INVOKE(directory, IsDirectory));
4118 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
4120 // Calling LoadFullOriginMetadataWithRestore might update the group
4121 // in the metadata file, but only as a side-effect. The actual place
4122 // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
4124 QM_TRY_INSPECT(const auto& metadata,
4125 LoadFullOriginMetadataWithRestore(directory));
4127 QM_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
4128 metadata.mLastAccessTime),
4129 Err(NS_ERROR_FAILURE));
4131 QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
4132 Err(NS_ERROR_FAILURE));
4134 QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
4135 metadata.mPersistenceType),
4136 Err(NS_ERROR_FAILURE));
4138 QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
4139 Err(NS_ERROR_FAILURE));
4141 QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
4142 Err(NS_ERROR_FAILURE));
4144 QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
4145 Err(NS_ERROR_FAILURE));
4147 QM_TRY(InitializeOrigin(fullOriginMetadata.mPersistenceType,
4148 fullOriginMetadata,
4149 fullOriginMetadata.mLastAccessTime,
4150 fullOriginMetadata.mPersisted, directory));
4151 } else {
4152 InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
4155 return Ok{};
4156 }));
4158 autoRemoveQuota.release();
4160 return NS_OK;
4163 QM_TRY_INSPECT(
4164 const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
4165 if (mCacheUsable) {
4166 QM_TRY_INSPECT(
4167 const auto& stmt,
4168 CreateAndExecuteSingleStepStatement<
4169 SingleStepResult::ReturnNullIfNoResult>(
4170 *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
4172 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
4174 QM_TRY_INSPECT(const int32_t& valid,
4175 MOZ_TO_RESULT_INVOKE(stmt, GetInt32, 0));
4177 if (valid) {
4178 QM_TRY_INSPECT(const auto& buildId,
4179 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString, stmt,
4180 GetUTF8String, 1));
4182 return buildId == *gBuildId;
4186 return false;
4187 }()));
4189 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
4191 if (!loadQuotaFromCache ||
4192 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
4193 ![&LoadQuotaFromCache] {
4194 QM_TRY(LoadQuotaFromCache(), false);
4195 return true;
4196 }()) {
4197 // A keeper to defer the return only in Nightly, so that the telemetry data
4198 // for whole profile can be collected.
4199 #ifdef NIGHTLY_BUILD
4200 nsresult statusKeeper = NS_OK;
4201 #endif
4203 const auto statusKeeperFunc = [&](const nsresult rv) {
4204 RECORD_IN_NIGHTLY(statusKeeper, rv);
4207 for (const PersistenceType type : kBestEffortPersistenceTypes) {
4208 if (NS_WARN_IF(IsShuttingDown())) {
4209 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4212 QM_TRY(([&]() -> Result<Ok, nsresult> {
4213 QM_TRY(([this, type] {
4214 const nsresult rv = InitializeRepository(type);
4215 mInitializationInfo.RecordFirstInitializationAttempt(
4216 type == PERSISTENCE_TYPE_DEFAULT
4217 ? Initialization::DefaultRepository
4218 : Initialization::TemporaryRepository,
4219 rv);
4220 return rv;
4221 }()),
4222 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4224 return Ok{};
4225 }()));
4228 #ifdef NIGHTLY_BUILD
4229 if (NS_FAILED(statusKeeper)) {
4230 return statusKeeper;
4232 #endif
4235 recordQuotaInfoLoadTimeHelper->End();
4237 autoRemoveQuota.release();
4239 return NS_OK;
4242 void QuotaManager::UnloadQuota() {
4243 AssertIsOnIOThread();
4244 MOZ_ASSERT(mStorageConnection);
4245 MOZ_ASSERT(mTemporaryStorageInitialized);
4246 MOZ_ASSERT(mCacheUsable);
4248 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
4250 mozStorageTransaction transaction(
4251 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4253 QM_TRY(transaction.Start(), QM_VOID);
4255 QM_TRY(mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns),
4256 QM_VOID);
4258 nsCOMPtr<mozIStorageStatement> insertStmt;
4261 MutexAutoLock lock(mQuotaMutex);
4263 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
4264 MOZ_ASSERT(!iter.Key().IsEmpty());
4266 GroupInfoPair* const pair = iter.UserData();
4267 MOZ_ASSERT(pair);
4269 for (const PersistenceType type : kBestEffortPersistenceTypes) {
4270 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
4271 if (!groupInfo) {
4272 continue;
4275 for (const auto& originInfo : groupInfo->mOriginInfos) {
4276 MOZ_ASSERT(!originInfo->mQuotaObjects.Count());
4278 if (!originInfo->mDirectoryExists) {
4279 continue;
4282 if (insertStmt) {
4283 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
4284 } else {
4285 QM_TRY_UNWRAP(
4286 insertStmt,
4287 MOZ_TO_RESULT_INVOKE_TYPED(
4288 nsCOMPtr<mozIStorageStatement>, mStorageConnection,
4289 CreateStatement,
4290 "INSERT INTO origin (repository_id, suffix, group_, "
4291 "origin, client_usages, usage, last_access_time, "
4292 "accessed, persisted) "
4293 "VALUES (:repository_id, :suffix, :group_, :origin, "
4294 ":client_usages, :usage, :last_access_time, :accessed, "
4295 ":persisted)"_ns),
4296 QM_VOID);
4299 QM_TRY(originInfo->LockedBindToStatement(insertStmt), QM_VOID);
4301 QM_TRY(insertStmt->Execute(), QM_VOID);
4304 groupInfo->LockedRemoveOriginInfos();
4307 iter.Remove();
4311 QM_TRY_INSPECT(
4312 const auto& stmt,
4313 MOZ_TO_RESULT_INVOKE_TYPED(
4314 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
4315 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
4316 QM_VOID);
4318 QM_TRY(stmt->BindInt32ByName("valid"_ns, 1), QM_VOID);
4319 QM_TRY(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId), QM_VOID);
4320 QM_TRY(stmt->Execute(), QM_VOID);
4321 QM_TRY(transaction.Commit(), QM_VOID);
4324 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4325 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4326 Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
4327 int64_t* aFileSizeOut /* = nullptr */) {
4328 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4330 if (aFileSizeOut) {
4331 *aFileSizeOut = 0;
4334 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
4335 return nullptr;
4338 QM_TRY_INSPECT(const auto& path,
4339 MOZ_TO_RESULT_INVOKE_TYPED(nsString, aFile, GetPath), nullptr);
4341 #ifdef DEBUG
4343 QM_TRY_INSPECT(
4344 const auto& directory,
4345 GetDirectoryForOrigin(aPersistenceType, aOriginMetadata.mOrigin),
4346 nullptr);
4348 nsAutoString clientType;
4349 QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
4350 nullptr);
4352 QM_TRY(directory->Append(clientType), nullptr);
4354 QM_TRY_INSPECT(const auto& directoryPath,
4355 MOZ_TO_RESULT_INVOKE_TYPED(nsString, directory, GetPath),
4356 nullptr);
4358 MOZ_ASSERT(StringBeginsWith(path, directoryPath));
4360 #endif
4362 QM_TRY_INSPECT(const int64_t fileSize,
4363 ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
4364 if (aFileSize == -1) {
4365 QM_TRY_INSPECT(const bool& exists,
4366 MOZ_TO_RESULT_INVOKE(aFile, Exists));
4368 if (exists) {
4369 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aFile, GetFileSize));
4372 return 0;
4375 return aFileSize;
4376 }()),
4377 nullptr);
4379 RefPtr<QuotaObject> result;
4381 MutexAutoLock lock(mQuotaMutex);
4383 GroupInfoPair* pair;
4384 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
4385 return nullptr;
4388 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
4390 if (!groupInfo) {
4391 return nullptr;
4394 RefPtr<OriginInfo> originInfo =
4395 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
4397 if (!originInfo) {
4398 return nullptr;
4401 // We need this extra raw pointer because we can't assign to the smart
4402 // pointer directly since QuotaObject::AddRef would try to acquire the same
4403 // mutex.
4404 const NotNull<QuotaObject*> quotaObject =
4405 originInfo->mQuotaObjects.LookupOrInsertWith(path, [&] {
4406 // Create a new QuotaObject. The hashtable is not responsible to
4407 // delete the QuotaObject.
4408 return WrapNotNullUnchecked(
4409 new QuotaObject(originInfo, aClientType, path, fileSize));
4412 // Addref the QuotaObject and move the ownership to the result. This must
4413 // happen before we unlock!
4414 result = quotaObject->LockedAddRef();
4417 if (aFileSizeOut) {
4418 *aFileSizeOut = fileSize;
4421 // The caller becomes the owner of the QuotaObject, that is, the caller is
4422 // is responsible to delete it when the last reference is removed.
4423 return result.forget();
4426 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4427 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4428 Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
4429 int64_t* aFileSizeOut /* = nullptr */) {
4430 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4432 if (aFileSizeOut) {
4433 *aFileSizeOut = 0;
4436 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
4438 return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
4439 aFileSize, aFileSizeOut);
4442 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4443 const int64_t aDirectoryLockId, const nsAString& aPath) {
4444 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4446 Maybe<MutexAutoLock> lock;
4448 // See the comment for mDirectoryLockIdTable in QuotaManager.h
4449 if (!IsOnBackgroundThread()) {
4450 lock.emplace(mQuotaMutex);
4453 if (auto maybeDirectoryLock =
4454 mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
4455 const auto& directoryLock = *maybeDirectoryLock;
4456 MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
4458 const PersistenceType persistenceType = directoryLock->GetPersistenceType();
4459 const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
4460 const Client::Type clientType = directoryLock->ClientType();
4462 lock.reset();
4464 return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
4467 MOZ_CRASH("Getting quota object for an unregistered directory lock?");
4470 Nullable<bool> QuotaManager::OriginPersisted(
4471 const OriginMetadata& aOriginMetadata) {
4472 AssertIsOnIOThread();
4474 MutexAutoLock lock(mQuotaMutex);
4476 RefPtr<OriginInfo> originInfo =
4477 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
4478 if (originInfo) {
4479 return Nullable<bool>(originInfo->LockedPersisted());
4482 return Nullable<bool>();
4485 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
4486 AssertIsOnIOThread();
4488 MutexAutoLock lock(mQuotaMutex);
4490 RefPtr<OriginInfo> originInfo =
4491 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
4492 if (originInfo && !originInfo->LockedPersisted()) {
4493 originInfo->LockedPersist();
4497 void QuotaManager::AbortOperationsForLocks(
4498 const DirectoryLockIdTableArray& aLockIds) {
4499 for (Client::Type type : AllClientTypes()) {
4500 if (aLockIds[type].Filled()) {
4501 (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
4506 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
4507 AssertIsOnOwningThread();
4509 for (const RefPtr<Client>& client : *mClients) {
4510 client->AbortOperationsForProcess(aContentParentId);
4514 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetDirectoryForOrigin(
4515 PersistenceType aPersistenceType, const nsACString& aASCIIOrigin) const {
4516 QM_TRY_UNWRAP(auto directory,
4517 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
4519 QM_TRY(directory->Append(MakeSanitizedOriginString(aASCIIOrigin)));
4521 return directory;
4524 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
4525 AssertIsOnIOThread();
4526 MOZ_ASSERT(aDirectory);
4527 MOZ_ASSERT(mStorageConnection);
4529 RefPtr<RestoreDirectoryMetadata2Helper> helper =
4530 new RestoreDirectoryMetadata2Helper(aDirectory);
4532 QM_TRY(helper->Init());
4534 QM_TRY(helper->RestoreMetadata2File());
4536 return NS_OK;
4539 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
4540 nsIFile* aDirectory, PersistenceType aPersistenceType) {
4541 MOZ_ASSERT(!NS_IsMainThread());
4542 MOZ_ASSERT(aDirectory);
4543 MOZ_ASSERT(mStorageConnection);
4545 QM_TRY_INSPECT(const auto& binaryStream,
4546 GetBinaryInputStream(*aDirectory,
4547 nsLiteralString(METADATA_V2_FILE_NAME)));
4549 FullOriginMetadata fullOriginMetadata;
4551 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
4552 MOZ_TO_RESULT_INVOKE(binaryStream, Read64));
4554 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
4555 MOZ_TO_RESULT_INVOKE(binaryStream, ReadBoolean));
4557 QM_TRY_INSPECT(const bool& reservedData1,
4558 MOZ_TO_RESULT_INVOKE(binaryStream, Read32));
4559 Unused << reservedData1;
4561 // XXX Use for the persistence type.
4562 QM_TRY_INSPECT(const bool& reservedData2,
4563 MOZ_TO_RESULT_INVOKE(binaryStream, Read32));
4564 Unused << reservedData2;
4566 fullOriginMetadata.mPersistenceType = aPersistenceType;
4568 QM_TRY_UNWRAP(
4569 fullOriginMetadata.mSuffix,
4570 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, binaryStream, ReadCString));
4572 QM_TRY_UNWRAP(
4573 fullOriginMetadata.mGroup,
4574 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, binaryStream, ReadCString));
4576 QM_TRY_UNWRAP(
4577 fullOriginMetadata.mOrigin,
4578 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, binaryStream, ReadCString));
4580 // Currently unused (used to be isApp).
4581 QM_TRY_INSPECT(const bool& dummy,
4582 MOZ_TO_RESULT_INVOKE(binaryStream, ReadBoolean));
4583 Unused << dummy;
4585 QM_TRY(binaryStream->Close());
4587 QM_TRY_INSPECT(const bool& updated,
4588 MaybeUpdateGroupForOrigin(fullOriginMetadata));
4590 if (updated) {
4591 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
4592 // I/O.
4593 QM_TRY(CreateDirectoryMetadata2(
4594 *aDirectory, fullOriginMetadata.mLastAccessTime,
4595 fullOriginMetadata.mPersisted, fullOriginMetadata));
4598 return fullOriginMetadata;
4601 Result<FullOriginMetadata, nsresult>
4602 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
4603 // XXX Once the persistence type is stored in the metadata file, this block
4604 // for getting the persistence type from the parent directory name can be
4605 // removed.
4606 nsCOMPtr<nsIFile> parentDir;
4607 QM_TRY(aDirectory->GetParent(getter_AddRefs(parentDir)));
4609 const auto maybePersistenceType =
4610 PersistenceTypeFromFile(*parentDir, fallible);
4611 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
4613 const auto& persistenceType = maybePersistenceType.value();
4615 QM_TRY_RETURN(QM_OR_ELSE_WARN(
4616 LoadFullOriginMetadata(aDirectory, persistenceType),
4617 ([&aDirectory, &persistenceType,
4618 this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
4619 QM_TRY(RestoreDirectoryMetadata2(aDirectory));
4621 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
4622 })));
4625 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType) {
4626 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
4627 aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
4629 QM_TRY_INSPECT(const auto& directory,
4630 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
4632 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
4634 Unused << created;
4636 // A keeper to defer the return only in Nightly, so that the telemetry data
4637 // for whole profile can be collected
4638 #ifdef NIGHTLY_BUILD
4639 nsresult statusKeeper = NS_OK;
4640 #endif
4642 const auto statusKeeperFunc = [&](const nsresult rv) {
4643 RECORD_IN_NIGHTLY(statusKeeper, rv);
4646 struct RenameAndInitInfo {
4647 nsCOMPtr<nsIFile> mOriginDirectory;
4648 FullOriginMetadata mFullOriginMetadata;
4649 int64_t mTimestamp;
4650 bool mPersisted;
4652 nsTArray<RenameAndInitInfo> renameAndInitInfos;
4654 QM_TRY(([&]() -> Result<Ok, nsresult> {
4655 QM_TRY(
4656 CollectEachFile(
4657 *directory,
4658 [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
4659 if (NS_WARN_IF(IsShuttingDown())) {
4660 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4663 QM_TRY(
4664 ([this, &childDirectory, &renameAndInitInfos,
4665 aPersistenceType]() -> Result<Ok, nsresult> {
4666 QM_TRY_INSPECT(
4667 const auto& leafName,
4668 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, childDirectory,
4669 GetLeafName));
4671 QM_TRY_INSPECT(const auto& dirEntryKind,
4672 GetDirEntryKind(*childDirectory));
4674 switch (dirEntryKind) {
4675 case nsIFileKind::ExistsAsDirectory: {
4676 QM_TRY_UNWRAP(
4677 auto metadata,
4678 LoadFullOriginMetadataWithRestore(childDirectory));
4680 MOZ_ASSERT(metadata.mPersistenceType ==
4681 aPersistenceType);
4683 // FIXME(tt): The check for origin name consistency can
4684 // be removed once we have an upgrade to traverse origin
4685 // directories and check through the directory metadata
4686 // files.
4687 const auto originSanitized =
4688 MakeSanitizedOriginCString(metadata.mOrigin);
4690 NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
4691 if (!originSanitized.Equals(utf8LeafName)) {
4692 QM_WARNING(
4693 "The name of the origin directory (%s) doesn't "
4694 "match the sanitized origin string (%s) in the "
4695 "metadata file!",
4696 utf8LeafName.get(), originSanitized.get());
4698 // If it's the known case, we try to restore the
4699 // origin directory name if it's possible.
4700 if (originSanitized.Equals(utf8LeafName + "."_ns)) {
4701 const int64_t lastAccessTime =
4702 metadata.mLastAccessTime;
4703 const bool persisted = metadata.mPersisted;
4704 renameAndInitInfos.AppendElement(RenameAndInitInfo{
4705 std::move(childDirectory), std::move(metadata),
4706 lastAccessTime, persisted});
4707 break;
4710 // XXXtt: Try to restore the unknown cases base on the
4711 // content for their metadata files. Note that if the
4712 // restore fails, QM should maintain a list and ensure
4713 // they won't be accessed after initialization.
4716 QM_TRY(QM_OR_ELSE_WARN(
4717 ToResult(InitializeOrigin(
4718 aPersistenceType, metadata,
4719 metadata.mLastAccessTime, metadata.mPersisted,
4720 childDirectory)),
4721 ([&childDirectory](
4722 const nsresult rv) -> Result<Ok, nsresult> {
4723 if (IsDatabaseCorruptionError(rv)) {
4724 // If the origin can't be initialized due to
4725 // corruption, this is a permanent
4726 // condition, and we need to remove all data
4727 // for the origin on disk.
4729 QM_TRY(childDirectory->Remove(true));
4731 return Ok{};
4734 return Err(rv);
4735 })));
4737 break;
4740 case nsIFileKind::ExistsAsFile:
4741 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
4742 break;
4745 // Unknown files during initialization are now allowed.
4746 // Just warn if we find them.
4747 UNKNOWN_FILE_WARNING(leafName);
4748 break;
4750 case nsIFileKind::DoesNotExist:
4751 // Ignore files that got removed externally while
4752 // iterating.
4753 break;
4756 return Ok{};
4757 }()),
4758 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4760 return Ok{};
4762 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4764 return Ok{};
4765 }()));
4767 for (const auto& info : renameAndInitInfos) {
4768 QM_TRY(([&]() -> Result<Ok, nsresult> {
4769 QM_TRY(([&directory, &info, this,
4770 aPersistenceType]() -> Result<Ok, nsresult> {
4771 const auto originDirName =
4772 MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
4774 // Check if targetDirectory exist.
4775 QM_TRY_INSPECT(const auto& targetDirectory,
4776 CloneFileAndAppend(*directory, originDirName));
4778 QM_TRY_INSPECT(const bool& exists,
4779 MOZ_TO_RESULT_INVOKE(targetDirectory, Exists));
4781 if (exists) {
4782 QM_TRY(info.mOriginDirectory->Remove(true));
4784 return Ok{};
4787 QM_TRY(info.mOriginDirectory->RenameTo(nullptr, originDirName));
4789 QM_TRY(InitializeOrigin(
4790 aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
4791 info.mPersisted, targetDirectory));
4793 return Ok{};
4794 }()),
4795 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4797 return Ok{};
4798 }()));
4801 #ifdef NIGHTLY_BUILD
4802 if (NS_FAILED(statusKeeper)) {
4803 return statusKeeper;
4805 #endif
4807 return NS_OK;
4810 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
4811 const OriginMetadata& aOriginMetadata,
4812 int64_t aAccessTime, bool aPersisted,
4813 nsIFile* aDirectory) {
4814 AssertIsOnIOThread();
4816 const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
4818 // We need to initialize directories of all clients if they exists and also
4819 // get the total usage to initialize the quota.
4821 ClientUsageArray clientUsages;
4823 // A keeper to defer the return only in Nightly, so that the telemetry data
4824 // for whole profile can be collected
4825 #ifdef NIGHTLY_BUILD
4826 nsresult statusKeeper = NS_OK;
4827 #endif
4829 QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
4830 RECORD_IN_NIGHTLY(statusKeeper, rv);
4831 }]() -> Result<Ok, nsresult> {
4832 QM_TRY(
4833 CollectEachFile(
4834 *aDirectory,
4835 [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
4836 if (NS_WARN_IF(IsShuttingDown())) {
4837 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4840 QM_TRY(
4841 ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
4842 &clientUsages]() -> Result<Ok, nsresult> {
4843 QM_TRY_INSPECT(const auto& leafName,
4844 MOZ_TO_RESULT_INVOKE_TYPED(
4845 nsAutoString, file, GetLeafName));
4847 QM_TRY_INSPECT(const auto& dirEntryKind,
4848 GetDirEntryKind(*file));
4850 switch (dirEntryKind) {
4851 case nsIFileKind::ExistsAsDirectory: {
4852 Client::Type clientType;
4853 const bool ok = Client::TypeFromText(
4854 leafName, clientType, fallible);
4855 if (!ok) {
4856 // Unknown directories during initialization are now
4857 // allowed. Just warn if we find them.
4858 UNKNOWN_FILE_WARNING(leafName);
4859 break;
4862 if (trackQuota) {
4863 QM_TRY_INSPECT(
4864 const auto& usageInfo,
4865 (*mClients)[clientType]->InitOrigin(
4866 aPersistenceType, aOriginMetadata,
4867 /* aCanceled */ Atomic<bool>(false)));
4869 MOZ_ASSERT(!clientUsages[clientType]);
4871 if (usageInfo.TotalUsage()) {
4872 // XXX(Bug 1683863) Until we identify the root cause
4873 // of seemingly converted-from-negative usage
4874 // values, we will just treat them as unset here,
4875 // but log a warning to the browser console.
4876 if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
4877 0) {
4878 clientUsages[clientType] = usageInfo.TotalUsage();
4879 } else {
4880 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
4881 const nsCOMPtr<nsIConsoleService> console =
4882 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
4883 if (console) {
4884 console->LogStringMessage(
4885 nsString(
4886 u"QuotaManager warning: client "_ns +
4887 leafName +
4888 u" reported negative usage for group "_ns +
4889 NS_ConvertUTF8toUTF16(
4890 aOriginMetadata.mGroup) +
4891 u", origin "_ns +
4892 NS_ConvertUTF8toUTF16(
4893 aOriginMetadata.mOrigin))
4894 .get());
4896 #endif
4899 } else {
4900 QM_TRY((*mClients)[clientType]
4901 ->InitOriginWithoutTracking(
4902 aPersistenceType, aOriginMetadata,
4903 /* aCanceled */ Atomic<bool>(false)));
4906 break;
4909 case nsIFileKind::ExistsAsFile:
4910 if (IsOriginMetadata(leafName)) {
4911 break;
4914 if (IsTempMetadata(leafName)) {
4915 QM_TRY(file->Remove(/* recursive */ false));
4917 break;
4920 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
4921 break;
4924 // Unknown files during initialization are now allowed.
4925 // Just warn if we find them.
4926 UNKNOWN_FILE_WARNING(leafName);
4927 // Bug 1595448 will handle the case for unknown files
4928 // like idb, cache, or ls.
4929 break;
4931 case nsIFileKind::DoesNotExist:
4932 // Ignore files that got removed externally while
4933 // iterating.
4934 break;
4937 return Ok{};
4938 }()),
4939 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4941 return Ok{};
4943 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4945 return Ok{};
4946 }()));
4948 #ifdef NIGHTLY_BUILD
4949 if (NS_FAILED(statusKeeper)) {
4950 return statusKeeper;
4952 #endif
4954 if (trackQuota) {
4955 const auto usage = std::accumulate(
4956 clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
4957 [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
4958 return value + clientUsage.valueOr(0);
4961 // XXX Should we log more information, i.e. the whole clientUsages array, in
4962 // case usage is not valid?
4964 QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
4966 InitQuotaForOrigin(
4967 FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
4968 clientUsages, usage.value());
4971 return NS_OK;
4974 nsresult
4975 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4976 nsIFile* aIndexedDBDir) {
4977 AssertIsOnIOThread();
4978 MOZ_ASSERT(aIndexedDBDir);
4980 auto rv = [this, &aIndexedDBDir]() -> nsresult {
4981 bool isDirectory;
4982 QM_TRY(aIndexedDBDir->IsDirectory(&isDirectory));
4984 if (!isDirectory) {
4985 NS_WARNING("indexedDB entry is not a directory!");
4986 return NS_OK;
4989 auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
4990 if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
4991 return persistentStorageDirOrErr.unwrapErr();
4994 nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
4996 QM_TRY(persistentStorageDir->Append(
4997 nsLiteralString(PERSISTENT_DIRECTORY_NAME)));
4999 bool exists;
5000 QM_TRY(persistentStorageDir->Exists(&exists));
5002 if (exists) {
5003 QM_WARNING("Deleting old <profile>/indexedDB directory!");
5005 QM_TRY(aIndexedDBDir->Remove(/* aRecursive */ true));
5007 return NS_OK;
5010 nsCOMPtr<nsIFile> storageDir;
5011 QM_TRY(persistentStorageDir->GetParent(getter_AddRefs(storageDir)));
5013 // MoveTo() is atomic if the move happens on the same volume which should
5014 // be our case, so even if we crash in the middle of the operation nothing
5015 // breaks next time we try to initialize.
5016 // However there's a theoretical possibility that the indexedDB directory
5017 // is on different volume, but it should be rare enough that we don't have
5018 // to worry about it.
5019 QM_TRY(aIndexedDBDir->MoveTo(storageDir,
5020 nsLiteralString(PERSISTENT_DIRECTORY_NAME)));
5022 return NS_OK;
5023 }();
5025 mInitializationInfo.RecordFirstInitializationAttempt(
5026 Initialization::UpgradeFromIndexedDBDirectory, rv);
5028 return rv;
5031 nsresult
5032 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5033 nsIFile* aPersistentStorageDir) {
5034 AssertIsOnIOThread();
5035 MOZ_ASSERT(aPersistentStorageDir);
5037 auto rv = [this, &aPersistentStorageDir]() -> nsresult {
5038 QM_TRY_INSPECT(const bool& isDirectory,
5039 MOZ_TO_RESULT_INVOKE(aPersistentStorageDir, IsDirectory));
5041 if (!isDirectory) {
5042 NS_WARNING("persistent entry is not a directory!");
5043 return NS_OK;
5047 QM_TRY_INSPECT(const auto& defaultStorageDir,
5048 QM_NewLocalFile(*mDefaultStoragePath));
5050 QM_TRY_INSPECT(const bool& exists,
5051 MOZ_TO_RESULT_INVOKE(defaultStorageDir, Exists));
5053 if (exists) {
5054 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
5056 QM_TRY(aPersistentStorageDir->Remove(/* aRecursive */ true));
5058 return NS_OK;
5063 // Create real metadata files for origin directories in persistent
5064 // storage.
5065 auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
5066 aPersistentStorageDir);
5068 QM_TRY(helper->Init());
5070 QM_TRY(helper->ProcessRepository());
5072 // Upgrade metadata files for origin directories in temporary storage.
5073 QM_TRY_INSPECT(const auto& temporaryStorageDir,
5074 QM_NewLocalFile(*mTemporaryStoragePath));
5076 QM_TRY_INSPECT(const bool& exists,
5077 MOZ_TO_RESULT_INVOKE(temporaryStorageDir, Exists));
5079 if (exists) {
5080 QM_TRY_INSPECT(const bool& isDirectory,
5081 MOZ_TO_RESULT_INVOKE(temporaryStorageDir, IsDirectory));
5083 if (!isDirectory) {
5084 NS_WARNING("temporary entry is not a directory!");
5085 return NS_OK;
5088 helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
5089 temporaryStorageDir);
5091 QM_TRY(helper->Init());
5093 QM_TRY(helper->ProcessRepository());
5097 // And finally rename persistent to default.
5098 QM_TRY(aPersistentStorageDir->RenameTo(
5099 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
5101 return NS_OK;
5102 }();
5104 mInitializationInfo.RecordFirstInitializationAttempt(
5105 Initialization::UpgradeFromPersistentStorageDirectory, rv);
5107 return rv;
5110 template <typename Helper>
5111 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
5112 const int32_t aNewVersion,
5113 mozIStorageConnection* aConnection) {
5114 AssertIsOnIOThread();
5115 MOZ_ASSERT(aNewVersion > aOldVersion);
5116 MOZ_ASSERT(aNewVersion <= kStorageVersion);
5117 MOZ_ASSERT(aConnection);
5119 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
5120 QM_TRY_UNWRAP(auto directory,
5121 QM_NewLocalFile(GetStoragePath(persistenceType)));
5123 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(directory, Exists));
5125 if (!exists) {
5126 continue;
5129 RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
5131 QM_TRY(helper->Init());
5133 QM_TRY(helper->ProcessRepository());
5136 #ifdef DEBUG
5138 QM_TRY_INSPECT(const int32_t& storageVersion,
5139 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
5141 MOZ_ASSERT(storageVersion == aOldVersion);
5143 #endif
5145 QM_TRY(aConnection->SetSchemaVersion(aNewVersion));
5147 return NS_OK;
5150 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
5151 mozIStorageConnection* aConnection) {
5152 AssertIsOnIOThread();
5153 MOZ_ASSERT(aConnection);
5155 auto rv = [this, &aConnection]() -> nsresult {
5156 QM_TRY(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
5157 0, MakeStorageVersion(1, 0), aConnection));
5159 return NS_OK;
5160 }();
5162 mInitializationInfo.RecordFirstInitializationAttempt(
5163 Initialization::UpgradeStorageFrom0_0To1_0, rv);
5165 return rv;
5168 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
5169 mozIStorageConnection* aConnection) {
5170 AssertIsOnIOThread();
5171 MOZ_ASSERT(aConnection);
5173 // The upgrade consists of a number of logically distinct bugs that
5174 // intentionally got fixed at the same time to trigger just one major
5175 // version bump.
5178 // Morgue directory cleanup
5179 // [Feature/Bug]:
5180 // The original bug that added "on demand" morgue cleanup is 1165119.
5182 // [Mutations]:
5183 // Morgue directories are removed from all origin directories during the
5184 // upgrade process. Origin initialization and usage calculation doesn't try
5185 // to remove morgue directories anymore.
5187 // [Downgrade-incompatible changes]:
5188 // Morgue directories can reappear if user runs an already upgraded profile
5189 // in an older version of Firefox. Morgue directories then prevent current
5190 // Firefox from initializing and using the storage.
5193 // App data removal
5194 // [Feature/Bug]:
5195 // The bug that removes isApp flags is 1311057.
5197 // [Mutations]:
5198 // Origin directories with appIds are removed during the upgrade process.
5200 // [Downgrade-incompatible changes]:
5201 // Origin directories with appIds can reappear if user runs an already
5202 // upgraded profile in an older version of Firefox. Origin directories with
5203 // appIds don't prevent current Firefox from initializing and using the
5204 // storage, but they wouldn't ever be removed again, potentially causing
5205 // problems once appId is removed from origin attributes.
5208 // Strip obsolete origin attributes
5209 // [Feature/Bug]:
5210 // The bug that strips obsolete origin attributes is 1314361.
5212 // [Mutations]:
5213 // Origin directories with obsolete origin attributes are renamed and their
5214 // metadata files are updated during the upgrade process.
5216 // [Downgrade-incompatible changes]:
5217 // Origin directories with obsolete origin attributes can reappear if user
5218 // runs an already upgraded profile in an older version of Firefox. Origin
5219 // directories with obsolete origin attributes don't prevent current Firefox
5220 // from initializing and using the storage, but they wouldn't ever be upgraded
5221 // again, potentially causing problems in future.
5224 // File manager directory renaming (client specific)
5225 // [Feature/Bug]:
5226 // The original bug that added "on demand" file manager directory renaming is
5227 // 1056939.
5229 // [Mutations]:
5230 // All file manager directories are renamed to contain the ".files" suffix.
5232 // [Downgrade-incompatible changes]:
5233 // File manager directories with the ".files" suffix prevent older versions of
5234 // Firefox from initializing and using the storage.
5235 // File manager directories without the ".files" suffix can appear if user
5236 // runs an already upgraded profile in an older version of Firefox. File
5237 // manager directories without the ".files" suffix then prevent current
5238 // Firefox from initializing and using the storage.
5240 auto rv = [this, &aConnection]() -> nsresult {
5241 QM_TRY(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
5242 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection));
5244 return NS_OK;
5245 }();
5247 mInitializationInfo.RecordFirstInitializationAttempt(
5248 Initialization::UpgradeStorageFrom1_0To2_0, rv);
5250 return rv;
5253 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
5254 mozIStorageConnection* aConnection) {
5255 AssertIsOnIOThread();
5256 MOZ_ASSERT(aConnection);
5258 // The upgrade is mainly to create a directory padding file in DOM Cache
5259 // directory to record the overall padding size of an origin.
5261 auto rv = [this, &aConnection]() -> nsresult {
5262 QM_TRY(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
5263 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection));
5265 return NS_OK;
5266 }();
5268 mInitializationInfo.RecordFirstInitializationAttempt(
5269 Initialization::UpgradeStorageFrom2_0To2_1, rv);
5271 return rv;
5274 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
5275 mozIStorageConnection* aConnection) {
5276 AssertIsOnIOThread();
5277 MOZ_ASSERT(aConnection);
5279 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
5280 // asmjs client, and ".tmp" file in the idb folers.
5282 auto rv = [this, &aConnection]() -> nsresult {
5283 QM_TRY(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
5284 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection));
5286 return NS_OK;
5287 }();
5289 mInitializationInfo.RecordFirstInitializationAttempt(
5290 Initialization::UpgradeStorageFrom2_1To2_2, rv);
5292 return rv;
5295 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
5296 mozIStorageConnection* aConnection) {
5297 AssertIsOnIOThread();
5298 MOZ_ASSERT(aConnection);
5300 auto rv = [&aConnection]() -> nsresult {
5301 // Table `database`
5302 QM_TRY(aConnection->ExecuteSimpleSQL(
5303 nsLiteralCString("CREATE TABLE database"
5304 "( cache_version INTEGER NOT NULL DEFAULT 0"
5305 ");")));
5307 QM_TRY(aConnection->ExecuteSimpleSQL(
5308 nsLiteralCString("INSERT INTO database (cache_version) "
5309 "VALUES (0)")));
5311 #ifdef DEBUG
5313 QM_TRY_INSPECT(const int32_t& storageVersion,
5314 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
5316 MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
5318 #endif
5320 QM_TRY(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3)));
5322 return NS_OK;
5323 }();
5325 mInitializationInfo.RecordFirstInitializationAttempt(
5326 Initialization::UpgradeStorageFrom2_2To2_3, rv);
5328 return rv;
5331 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
5332 nsIFile& aLsArchiveFile) {
5333 AssertIsOnIOThread();
5334 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
5336 QM_TRY_INSPECT(const bool& exists,
5337 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5339 if (!exists) {
5340 // If the ls archive doesn't exist then ls directories can't exist either.
5341 return NS_OK;
5344 QM_TRY(MaybeRemoveLocalStorageDirectories());
5346 InvalidateQuotaCache();
5348 // Finally remove the ls archive, so we don't have to check all origin
5349 // directories next time this method is called.
5350 QM_TRY(aLsArchiveFile.Remove(false));
5352 return NS_OK;
5355 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
5356 AssertIsOnIOThread();
5358 QM_TRY_INSPECT(const auto& defaultStorageDir,
5359 QM_NewLocalFile(*mDefaultStoragePath));
5361 QM_TRY_INSPECT(const bool& exists,
5362 MOZ_TO_RESULT_INVOKE(defaultStorageDir, Exists));
5364 if (!exists) {
5365 return NS_OK;
5368 QM_TRY(CollectEachFile(
5369 *defaultStorageDir,
5370 [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
5371 #ifdef DEBUG
5373 QM_TRY_INSPECT(const bool& exists,
5374 MOZ_TO_RESULT_INVOKE(originDir, Exists));
5375 MOZ_ASSERT(exists);
5377 #endif
5379 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
5381 switch (dirEntryKind) {
5382 case nsIFileKind::ExistsAsDirectory: {
5383 QM_TRY_INSPECT(
5384 const auto& lsDir,
5385 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
5386 LS_DIRECTORY_NAME)));
5389 QM_TRY_INSPECT(const bool& exists,
5390 MOZ_TO_RESULT_INVOKE(lsDir, Exists));
5392 if (!exists) {
5393 return Ok{};
5398 QM_TRY_INSPECT(const bool& isDirectory,
5399 MOZ_TO_RESULT_INVOKE(lsDir, IsDirectory));
5401 if (!isDirectory) {
5402 QM_WARNING("ls entry is not a directory!");
5404 return Ok{};
5408 nsString path;
5409 QM_TRY(lsDir->GetPath(path));
5411 QM_WARNING("Deleting %s directory!",
5412 NS_ConvertUTF16toUTF8(path).get());
5414 QM_TRY(lsDir->Remove(/* aRecursive */ true));
5416 break;
5419 case nsIFileKind::ExistsAsFile: {
5420 QM_TRY_INSPECT(const auto& leafName,
5421 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, originDir,
5422 GetLeafName));
5424 // Unknown files during upgrade are allowed. Just warn if we find
5425 // them.
5426 if (!IsOSMetadata(leafName)) {
5427 UNKNOWN_FILE_WARNING(leafName);
5430 break;
5433 case nsIFileKind::DoesNotExist:
5434 // Ignore files that got removed externally while iterating.
5435 break;
5437 return Ok{};
5438 }));
5440 return NS_OK;
5443 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
5444 nsIFile& aLsArchiveFile) const {
5445 AssertIsOnIOThread();
5446 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5448 #ifdef DEBUG
5450 QM_TRY_INSPECT(const bool& exists,
5451 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5452 MOZ_ASSERT(!exists);
5454 #endif
5456 // Get the storage service first, we will need it at multiple places.
5457 QM_TRY_INSPECT(const auto& ss, ToResultGet<nsCOMPtr<mozIStorageService>>(
5458 MOZ_SELECT_OVERLOAD(do_GetService),
5459 MOZ_STORAGE_SERVICE_CONTRACTID));
5461 // Get the web apps store file.
5462 QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
5464 QM_TRY(webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME)));
5466 // Now check if the web apps store is useable.
5467 QM_TRY_INSPECT(const auto& connection,
5468 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
5470 if (connection) {
5471 // Find out the journal mode.
5472 QM_TRY_INSPECT(const auto& stmt,
5473 CreateAndExecuteSingleStepStatement(
5474 *connection, "PRAGMA journal_mode;"_ns));
5476 QM_TRY_INSPECT(
5477 const auto& journalMode,
5478 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString, *stmt, GetUTF8String, 0));
5480 QM_TRY(stmt->Finalize());
5482 if (journalMode.EqualsLiteral("wal")) {
5483 // We don't copy the WAL file, so make sure the old database is fully
5484 // checkpointed.
5485 QM_TRY(
5486 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns));
5489 // Explicitely close the connection before the old database is copied.
5490 QM_TRY(connection->Close());
5492 // Copy the old database. The database is copied from
5493 // <profile>/webappsstore.sqlite to
5494 // <profile>/storage/ls-archive-tmp.sqlite
5495 // We use a "-tmp" postfix since we are not done yet.
5496 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
5498 QM_TRY(webAppsStoreFile->CopyTo(storageDir,
5499 nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME)));
5501 QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
5502 GetLocalStorageArchiveTmpFile(*mStoragePath));
5504 if (journalMode.EqualsLiteral("wal")) {
5505 QM_TRY_INSPECT(
5506 const auto& lsArchiveTmpConnection,
5507 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageConnection>, ss,
5508 OpenUnsharedDatabase, lsArchiveTmpFile));
5510 // The archive will only be used for lazy data migration. There won't be
5511 // any concurrent readers and writers that could benefit from Write-Ahead
5512 // Logging. So switch to a standard rollback journal. The standard
5513 // rollback journal also provides atomicity across multiple attached
5514 // databases which is import for the lazy data migration to work safely.
5515 QM_TRY(lsArchiveTmpConnection->ExecuteSimpleSQL(
5516 "PRAGMA journal_mode = DELETE;"_ns));
5518 // Close the connection explicitly. We are going to rename the file below.
5519 QM_TRY(lsArchiveTmpConnection->Close());
5522 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
5523 QM_TRY(lsArchiveTmpFile->MoveTo(nullptr,
5524 nsLiteralString(LS_ARCHIVE_FILE_NAME)));
5526 return Ok{};
5529 // If webappsstore database is not useable, just create an empty archive.
5530 // XXX The code below should be removed and the caller should call us only
5531 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
5532 // reworked to propagate database corruption instead of returning null
5533 // connection.
5534 // So, if there's no webappsstore.sqlite
5535 // MaybeCreateOrUpgradeLocalStorageArchive will call
5536 // CreateEmptyLocalStorageArchive instead of
5537 // CopyLocalStorageArchiveFromWebAppsStore.
5538 // If there's any corruption detected during
5539 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
5540 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
5541 // EnsureStorageIsInitialized will fallback to
5542 // CreateEmptyLocalStorageArchive.
5544 // Ensure the storage directory actually exists.
5545 QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
5547 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
5549 Unused << created;
5551 QM_TRY_UNWRAP(
5552 auto lsArchiveConnection,
5553 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageConnection>, ss,
5554 OpenUnsharedDatabase, &aLsArchiveFile));
5556 QM_TRY(StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection));
5558 return Ok{};
5561 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
5562 QuotaManager::CreateLocalStorageArchiveConnection(
5563 nsIFile& aLsArchiveFile) const {
5564 AssertIsOnIOThread();
5565 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5567 #ifdef DEBUG
5569 QM_TRY_INSPECT(const bool& exists,
5570 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5571 MOZ_ASSERT(exists);
5573 #endif
5575 QM_TRY_INSPECT(const bool& isDirectory,
5576 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, IsDirectory));
5578 // A directory with the name of the archive file is treated as corruption
5579 // (similarly as wrong content of the file).
5580 QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
5582 QM_TRY_INSPECT(const auto& ss, ToResultGet<nsCOMPtr<mozIStorageService>>(
5583 MOZ_SELECT_OVERLOAD(do_GetService),
5584 MOZ_STORAGE_SERVICE_CONTRACTID));
5586 // This may return NS_ERROR_FILE_CORRUPTED too.
5587 QM_TRY_UNWRAP(auto connection, MOZ_TO_RESULT_INVOKE_TYPED(
5588 nsCOMPtr<mozIStorageConnection>, ss,
5589 OpenUnsharedDatabase, &aLsArchiveFile));
5591 // The legacy LS implementation removes the database and creates an empty one
5592 // when the schema can't be updated. The same effect can be achieved here by
5593 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
5594 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
5595 QM_TRY(
5596 ToResult(StorageDBUpdater::Update(connection))
5597 .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
5599 return connection;
5602 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
5603 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
5604 nsIFile& aLsArchiveFile) {
5605 AssertIsOnIOThread();
5606 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5608 QM_TRY(MaybeRemoveLocalStorageDirectories());
5610 #ifdef DEBUG
5612 QM_TRY_INSPECT(const bool& exists,
5613 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5615 MOZ_ASSERT(exists);
5617 #endif
5619 QM_TRY(aLsArchiveFile.Remove(false));
5621 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5623 QM_TRY_UNWRAP(auto connection,
5624 CreateLocalStorageArchiveConnection(aLsArchiveFile));
5626 QM_TRY(InitializeLocalStorageArchive(connection));
5628 return connection;
5631 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
5632 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
5633 AssertIsOnIOThread();
5634 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5636 QM_TRY_UNWRAP(auto connection,
5637 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5639 QM_TRY(
5640 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion));
5642 return connection;
5645 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
5646 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
5647 nsIFile& aLsArchiveFile) {
5648 AssertIsOnIOThread();
5649 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5651 QM_TRY_UNWRAP(auto connection,
5652 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5654 QM_TRY(SaveLocalStorageArchiveVersion(connection, 4));
5656 return connection;
5660 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
5661 nsCOMPtr<mozIStorageConnection>& aConnection) {
5662 AssertIsOnIOThread();
5663 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5665 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
5666 if (NS_WARN_IF(NS_FAILED(rv))) {
5667 return rv;
5670 return NS_OK;
5674 #ifdef DEBUG
5676 void QuotaManager::AssertStorageIsInitialized() const {
5677 AssertIsOnIOThread();
5678 MOZ_ASSERT(IsStorageInitialized());
5681 #endif // DEBUG
5683 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
5684 nsIFile& aStorageFile) {
5685 AssertIsOnIOThread();
5687 QM_TRY_INSPECT(const auto& storageFileExists,
5688 MOZ_TO_RESULT_INVOKE(aStorageFile, Exists));
5690 if (!storageFileExists) {
5691 QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
5693 QM_TRY_INSPECT(const auto& indexedDBDirExists,
5694 MOZ_TO_RESULT_INVOKE(indexedDBDir, Exists));
5696 if (indexedDBDirExists) {
5697 QM_TRY(UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
5698 indexedDBDir));
5701 QM_TRY_INSPECT(const auto& persistentStorageDir,
5702 QM_NewLocalFile(*mStoragePath));
5704 QM_TRY(persistentStorageDir->Append(
5705 nsLiteralString(PERSISTENT_DIRECTORY_NAME)));
5707 QM_TRY_INSPECT(const auto& persistentStorageDirExists,
5708 MOZ_TO_RESULT_INVOKE(persistentStorageDir, Exists));
5710 if (persistentStorageDirExists) {
5711 QM_TRY(UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5712 persistentStorageDir));
5716 return NS_OK;
5719 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
5720 mozIStorageConnection& aConnection) {
5721 AssertIsOnIOThread();
5723 QM_TRY_UNWRAP(auto storageVersion,
5724 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
5726 // Hacky downgrade logic!
5727 // If we see major.minor of 3.0, downgrade it to be 2.1.
5728 if (storageVersion == kHackyPreDowngradeStorageVersion) {
5729 storageVersion = kHackyPostDowngradeStorageVersion;
5730 QM_TRY(aConnection.SetSchemaVersion(storageVersion), QM_PROPAGATE,
5731 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
5734 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
5735 NS_ERROR_FAILURE, [](const auto&) {
5736 NS_WARNING("Unable to initialize storage, version is too high!");
5739 if (storageVersion < kStorageVersion) {
5740 const bool newDatabase = !storageVersion;
5742 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
5744 QM_TRY_INSPECT(const auto& storageDirExists,
5745 MOZ_TO_RESULT_INVOKE(storageDir, Exists));
5747 const bool newDirectory = !storageDirExists;
5749 if (newDatabase) {
5750 // Set the page size first.
5751 if (kSQLitePageSizeOverride) {
5752 QM_TRY(aConnection.ExecuteSimpleSQL(nsPrintfCString(
5753 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride)));
5757 mozStorageTransaction transaction(
5758 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
5760 QM_TRY(transaction.Start());
5762 // An upgrade method can upgrade the database, the storage or both.
5763 // The upgrade loop below can only be avoided when there's no database and
5764 // no storage yet (e.g. new profile).
5765 if (newDatabase && newDirectory) {
5766 QM_TRY(CreateTables(&aConnection));
5768 #ifdef DEBUG
5770 QM_TRY_INSPECT(const int32_t& storageVersion,
5771 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion),
5772 QM_ASSERT_UNREACHABLE);
5773 MOZ_ASSERT(storageVersion == kStorageVersion);
5775 #endif
5777 QM_TRY(aConnection.ExecuteSimpleSQL(
5778 nsLiteralCString("INSERT INTO database (cache_version) "
5779 "VALUES (0)")));
5780 } else {
5781 // This logic needs to change next time we change the storage!
5782 static_assert(kStorageVersion == int32_t((2 << 16) + 3),
5783 "Upgrade function needed due to storage version increase.");
5785 while (storageVersion != kStorageVersion) {
5786 if (storageVersion == 0) {
5787 QM_TRY(UpgradeStorageFrom0_0To1_0(&aConnection));
5788 } else if (storageVersion == MakeStorageVersion(1, 0)) {
5789 QM_TRY(UpgradeStorageFrom1_0To2_0(&aConnection));
5790 } else if (storageVersion == MakeStorageVersion(2, 0)) {
5791 QM_TRY(UpgradeStorageFrom2_0To2_1(&aConnection));
5792 } else if (storageVersion == MakeStorageVersion(2, 1)) {
5793 QM_TRY(UpgradeStorageFrom2_1To2_2(&aConnection));
5794 } else if (storageVersion == MakeStorageVersion(2, 2)) {
5795 QM_TRY(UpgradeStorageFrom2_2To2_3(&aConnection));
5796 } else {
5797 QM_FAIL(NS_ERROR_FAILURE, []() {
5798 NS_WARNING(
5799 "Unable to initialize storage, no upgrade path is "
5800 "available!");
5804 QM_TRY_UNWRAP(storageVersion,
5805 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
5808 MOZ_ASSERT(storageVersion == kStorageVersion);
5811 QM_TRY(transaction.Commit());
5814 return NS_OK;
5817 nsresult QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
5818 AssertIsOnIOThread();
5820 QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
5821 GetLocalStorageArchiveTmpFile(*mStoragePath));
5823 QM_TRY_INSPECT(const bool& exists,
5824 MOZ_TO_RESULT_INVOKE(lsArchiveTmpFile, Exists));
5826 if (exists) {
5827 QM_TRY(lsArchiveTmpFile->Remove(false));
5830 return NS_OK;
5833 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
5834 nsIFile& aLsArchiveFile) {
5835 AssertIsOnIOThread();
5837 QM_TRY_INSPECT(
5838 const bool& lsArchiveFileExisted,
5839 ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
5840 QM_TRY_INSPECT(const bool& exists,
5841 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5843 if (!exists) {
5844 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5847 return exists;
5848 }()));
5850 QM_TRY_UNWRAP(auto connection,
5851 CreateLocalStorageArchiveConnection(aLsArchiveFile));
5853 QM_TRY_INSPECT(const auto& initialized,
5854 IsLocalStorageArchiveInitialized(*connection));
5856 if (!initialized) {
5857 QM_TRY(InitializeLocalStorageArchive(connection));
5860 QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
5862 if (version > kLocalStorageArchiveVersion) {
5863 // Close local storage archive connection. We are going to remove underlying
5864 // file.
5865 QM_TRY(connection->Close());
5867 // This will wipe the archive and any migrated data and recopy the archive
5868 // from webappsstore.sqlite.
5869 QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
5871 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
5873 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
5874 } else if (version != kLocalStorageArchiveVersion) {
5875 // The version can be zero either when the archive didn't exist or it did
5876 // exist, but the archive was created without any version information.
5877 // We don't need to do any upgrades only if it didn't exist because existing
5878 // archives without version information must be recopied to really fix bug
5879 // 1542104. See also bug 1546305 which introduced archive versions.
5880 if (!lsArchiveFileExisted) {
5881 MOZ_ASSERT(version == 0);
5883 QM_TRY(SaveLocalStorageArchiveVersion(connection,
5884 kLocalStorageArchiveVersion));
5885 } else {
5886 static_assert(kLocalStorageArchiveVersion == 4,
5887 "Upgrade function needed due to LocalStorage archive "
5888 "version increase.");
5890 while (version != kLocalStorageArchiveVersion) {
5891 if (version < 4) {
5892 // Close local storage archive connection. We are going to remove
5893 // underlying file.
5894 QM_TRY(connection->Close());
5896 // This won't do an "upgrade" in a normal sense. It will wipe the
5897 // archive and any migrated data and recopy the archive from
5898 // webappsstore.sqlite
5899 QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
5900 aLsArchiveFile));
5901 } /* else if (version == 4) {
5902 QM_TRY(UpgradeLocalStorageArchiveFrom4To5(connection));
5903 } */
5904 else {
5905 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
5906 QM_WARNING(
5907 "Unable to initialize LocalStorage archive, no upgrade path "
5908 "is available!");
5912 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
5915 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
5919 // At this point, we have finished initializing the local storage archive, and
5920 // can continue storage initialization. We don't know though if the actual
5921 // data in the archive file is readable. We can't do a PRAGMA integrity_check
5922 // here though, because that would be too heavyweight.
5924 return Ok{};
5927 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
5928 nsIFile& aLsArchiveFile) const {
5929 AssertIsOnIOThread();
5931 QM_TRY_INSPECT(const bool& exists,
5932 MOZ_TO_RESULT_INVOKE(aLsArchiveFile, Exists));
5934 // If it exists, remove it. It might be a directory, so remove it recursively.
5935 if (exists) {
5936 QM_TRY(aLsArchiveFile.Remove(true));
5938 // XXX If we crash right here, the next session will copy the archive from
5939 // webappsstore.sqlite again!
5940 // XXX Create a marker file before removing the archive which can be
5941 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
5942 // archive instead of recopying it from webapppstore.sqlite (in other
5943 // words, finishing what was started here).
5946 QM_TRY_INSPECT(const auto& ss, ToResultGet<nsCOMPtr<mozIStorageService>>(
5947 MOZ_SELECT_OVERLOAD(do_GetService),
5948 MOZ_STORAGE_SERVICE_CONTRACTID));
5950 QM_TRY_UNWRAP(
5951 const auto connection,
5952 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageConnection>, ss,
5953 OpenUnsharedDatabase, &aLsArchiveFile));
5955 QM_TRY(StorageDBUpdater::CreateCurrentSchema(connection));
5957 QM_TRY(InitializeLocalStorageArchive(connection));
5959 QM_TRY(
5960 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion));
5962 return Ok{};
5965 nsresult QuotaManager::EnsureStorageIsInitialized() {
5966 AssertIsOnIOThread();
5968 if (mStorageConnection) {
5969 mInitializationInfo.AssertInitializationAttempted(Initialization::Storage);
5970 return NS_OK;
5973 const auto autoRecord = mInitializationInfo.RecordFirstInitializationAttempt(
5974 Initialization::Storage,
5975 [&self = *this] { return static_cast<bool>(self.mStorageConnection); });
5977 const auto contextLogExtraInfo =
5978 autoRecord.IsFirstInitializationAttempt()
5979 ? Some(ScopedLogExtraInfo{ScopedLogExtraInfo::kTagContext,
5980 "Initialization::Storage"_ns})
5981 : Nothing{};
5983 QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
5984 QM_TRY(storageFile->Append(mStorageName + kSQLiteSuffix));
5986 QM_TRY(MaybeUpgradeToDefaultStorageDirectory(*storageFile));
5988 QM_TRY_INSPECT(const auto& ss, ToResultGet<nsCOMPtr<mozIStorageService>>(
5989 MOZ_SELECT_OVERLOAD(do_GetService),
5990 MOZ_STORAGE_SERVICE_CONTRACTID));
5992 QM_TRY_UNWRAP(
5993 auto connection,
5994 QM_OR_ELSE_WARN(
5995 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageConnection>, ss,
5996 OpenUnsharedDatabase, storageFile),
5997 (FilterDatabaseCorruptionError<nullptr,
5998 nsCOMPtr<mozIStorageConnection>>)));
6000 if (!connection) {
6001 // Nuke the database file.
6002 QM_TRY(storageFile->Remove(false));
6004 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_TYPED(
6005 nsCOMPtr<mozIStorageConnection>, ss,
6006 OpenUnsharedDatabase, storageFile));
6009 // We want extra durability for this important file.
6010 QM_TRY(connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns));
6012 // Check to make sure that the storage version is correct.
6013 QM_TRY(MaybeCreateOrUpgradeStorage(*connection));
6015 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
6017 QM_TRY_INSPECT(const auto& lsArchiveFile,
6018 GetLocalStorageArchiveFile(*mStoragePath));
6020 if (CachedNextGenLocalStorageEnabled()) {
6021 QM_TRY(QM_OR_ELSE_WARN(
6022 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
6023 ([&](const nsresult rv) -> Result<Ok, nsresult> {
6024 if (IsDatabaseCorruptionError(rv)) {
6025 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
6027 return Err(rv);
6028 })));
6029 } else {
6030 QM_TRY(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile));
6033 QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
6035 if (mCacheUsable && gInvalidateQuotaCache) {
6036 QM_TRY(InvalidateCache(*connection));
6038 gInvalidateQuotaCache = false;
6041 mStorageConnection = std::move(connection);
6043 return NS_OK;
6046 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
6047 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
6048 Client::Type aClientType, bool aExclusive) {
6049 AssertIsOnOwningThread();
6051 return DirectoryLockImpl::Create(WrapNotNullUnchecked(this), aPersistenceType,
6052 aOriginMetadata, aClientType, aExclusive);
6055 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
6056 const Nullable<PersistenceType>& aPersistenceType,
6057 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
6058 bool aExclusive) {
6059 AssertIsOnOwningThread();
6061 return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
6062 aPersistenceType, aOriginScope,
6063 aClientType, aExclusive);
6066 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6067 QuotaManager::EnsurePersistentOriginIsInitialized(
6068 const OriginMetadata& aOriginMetadata) {
6069 AssertIsOnIOThread();
6070 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
6071 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6073 auto res = [&aOriginMetadata, this]()
6074 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6075 QM_TRY_UNWRAP(auto directory,
6076 GetDirectoryForOrigin(PERSISTENCE_TYPE_PERSISTENT,
6077 aOriginMetadata.mOrigin));
6079 if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) {
6080 return std::pair(std::move(directory), false);
6083 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6085 QM_TRY_INSPECT(const int64_t& timestamp,
6086 ([this, created, &directory,
6087 &aOriginMetadata]() -> Result<int64_t, nsresult> {
6088 if (created) {
6089 const int64_t timestamp = PR_Now();
6091 // Only creating .metadata-v2 to reduce IO.
6092 QM_TRY(CreateDirectoryMetadata2(*directory, timestamp,
6093 /* aPersisted */ true,
6094 aOriginMetadata));
6096 return timestamp;
6099 // Get the metadata. We only use the timestamp.
6100 QM_TRY_INSPECT(
6101 const auto& metadata,
6102 LoadFullOriginMetadataWithRestore(directory));
6104 MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
6106 return metadata.mLastAccessTime;
6107 }()));
6109 QM_TRY(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT, aOriginMetadata,
6110 timestamp,
6111 /* aPersisted */ true, directory));
6113 mInitializedOrigins.AppendElement(aOriginMetadata.mOrigin);
6115 return std::pair(std::move(directory), created);
6116 }();
6118 if (auto& info =
6119 mOriginInitializationInfos.LookupOrInsert(aOriginMetadata.mOrigin);
6120 !info.mPersistentOriginAttempted) {
6121 Telemetry::Accumulate(Telemetry::QM_FIRST_INITIALIZATION_ATTEMPT,
6122 kPersistentOriginTelemetryKey,
6123 static_cast<uint32_t>(res.isOk()));
6125 info.mPersistentOriginAttempted = true;
6128 return res;
6131 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
6132 QuotaManager::EnsureTemporaryOriginIsInitialized(
6133 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6134 AssertIsOnIOThread();
6135 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6136 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6137 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitialized);
6139 auto res = [&aPersistenceType, &aOriginMetadata, this]()
6140 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6141 // Get directory for this origin and persistence type.
6142 QM_TRY_UNWRAP(
6143 auto directory,
6144 GetDirectoryForOrigin(aPersistenceType, aOriginMetadata.mOrigin));
6146 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6148 if (created) {
6149 const int64_t timestamp =
6150 NoteOriginDirectoryCreated(aOriginMetadata, /* aPersisted */ false);
6152 // Only creating .metadata-v2 to reduce IO.
6153 QM_TRY(CreateDirectoryMetadata2(*directory, timestamp,
6154 /* aPersisted */ false, aOriginMetadata));
6157 // TODO: If the metadata file exists and we didn't call
6158 // LoadFullOriginMetadataWithRestore for it (because the quota info
6159 // was loaded from the cache), then the group in the metadata file
6160 // may be wrong, so it should be checked and eventually updated.
6161 // It's not a big deal that we are not doing it here, because the
6162 // origin will be marked as "accessed", so
6163 // LoadFullOriginMetadataWithRestore will be called for the metadata
6164 // file in next session in LoadQuotaFromCache.
6166 return std::pair(std::move(directory), created);
6167 }();
6169 auto& info =
6170 mOriginInitializationInfos.LookupOrInsert(aOriginMetadata.mOrigin);
6171 if (!info.mTemporaryOriginAttempted) {
6172 Telemetry::Accumulate(Telemetry::QM_FIRST_INITIALIZATION_ATTEMPT,
6173 kTemporaryOriginTelemetryKey,
6174 static_cast<uint32_t>(res.isOk()));
6176 info.mTemporaryOriginAttempted = true;
6179 return res;
6182 nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() {
6183 AssertIsOnIOThread();
6184 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6186 if (mTemporaryStorageInitialized) {
6187 mInitializationInfo.AssertInitializationAttempted(
6188 Initialization::TemporaryStorage);
6189 return NS_OK;
6192 const auto autoRecord = mInitializationInfo.RecordFirstInitializationAttempt(
6193 Initialization::TemporaryStorage,
6194 [&self = *this] { return self.mTemporaryStorageInitialized; });
6196 const auto contextLogExtraInfo =
6197 autoRecord.IsFirstInitializationAttempt()
6198 ? Some(ScopedLogExtraInfo{ScopedLogExtraInfo::kTagContext,
6199 "Initialization::TemporaryStorage"_ns})
6200 : Nothing{};
6202 QM_TRY_INSPECT(
6203 const auto& storageDir,
6204 ToResultGet<nsCOMPtr<nsIFile>>(MOZ_SELECT_OVERLOAD(do_CreateInstance),
6205 NS_LOCAL_FILE_CONTRACTID));
6207 QM_TRY(storageDir->InitWithPath(GetStoragePath()));
6209 // The storage directory must exist before calling GetDiskSpaceAvailable.
6210 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
6212 Unused << created;
6214 // Check for available disk space users have on their device where storage
6215 // directory lives.
6216 QM_TRY_INSPECT(const int64_t& diskSpaceAvailable,
6217 MOZ_TO_RESULT_INVOKE(storageDir, GetDiskSpaceAvailable));
6219 MOZ_ASSERT(diskSpaceAvailable >= 0);
6221 QM_TRY(LoadQuota());
6223 mTemporaryStorageInitialized = true;
6225 // Available disk space shouldn't be used directly for temporary storage
6226 // limit calculation since available disk space is affected by existing data
6227 // stored in temporary storage. So we need to increase it by the temporary
6228 // storage size (that has been calculated in LoadQuota) before passing to
6229 // GetTemporaryStorageLimit..
6230 mTemporaryStorageLimit = GetTemporaryStorageLimit(
6231 /* aAvailableSpaceBytes */ diskSpaceAvailable + mTemporaryStorageUsage);
6233 CleanupTemporaryStorage();
6235 if (mCacheUsable) {
6236 QM_TRY(InvalidateCache(*mStorageConnection));
6239 return NS_OK;
6242 void QuotaManager::ShutdownStorage() {
6243 AssertIsOnIOThread();
6245 if (mStorageConnection) {
6246 mOriginInitializationInfos.Clear();
6247 mInitializedOrigins.Clear();
6249 if (mTemporaryStorageInitialized) {
6250 if (mCacheUsable) {
6251 UnloadQuota();
6252 } else {
6253 RemoveQuota();
6256 mTemporaryStorageInitialized = false;
6259 ReleaseIOThreadObjects();
6261 mStorageConnection = nullptr;
6262 mCacheUsable = false;
6265 mInitializationInfo.ResetInitializationAttempts();
6268 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
6269 nsIFile& aDirectory) {
6270 AssertIsOnIOThread();
6272 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(aDirectory, Exists));
6274 if (!exists) {
6275 QM_TRY_INSPECT(const auto& leafName,
6276 MOZ_TO_RESULT_INVOKE_TYPED(nsString, aDirectory, GetLeafName)
6277 .map([](const auto& leafName) {
6278 return NS_ConvertUTF16toUTF8(leafName);
6279 }));
6281 QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
6282 [](const auto&) {
6283 QM_WARNING(
6284 "Preventing creation of a new origin directory which is not "
6285 "supported by our origin parser or is obsolete!");
6289 QM_TRY_RETURN(EnsureDirectory(aDirectory));
6292 nsresult QuotaManager::AboutToClearOrigins(
6293 const Nullable<PersistenceType>& aPersistenceType,
6294 const OriginScope& aOriginScope,
6295 const Nullable<Client::Type>& aClientType) {
6296 AssertIsOnIOThread();
6298 if (aClientType.IsNull()) {
6299 for (Client::Type type : AllClientTypes()) {
6300 QM_TRY((*mClients)[type]->AboutToClearOrigins(aPersistenceType,
6301 aOriginScope));
6303 } else {
6304 QM_TRY((*mClients)[aClientType.Value()]->AboutToClearOrigins(
6305 aPersistenceType, aOriginScope));
6308 return NS_OK;
6311 void QuotaManager::OriginClearCompleted(
6312 PersistenceType aPersistenceType, const nsACString& aOrigin,
6313 const Nullable<Client::Type>& aClientType) {
6314 AssertIsOnIOThread();
6316 if (aClientType.IsNull()) {
6317 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
6318 mInitializedOrigins.RemoveElement(aOrigin);
6321 for (Client::Type type : AllClientTypes()) {
6322 (*mClients)[type]->OnOriginClearCompleted(aPersistenceType, aOrigin);
6324 } else {
6325 (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
6326 aOrigin);
6330 Client* QuotaManager::GetClient(Client::Type aClientType) {
6331 MOZ_ASSERT(aClientType >= Client::IDB);
6332 MOZ_ASSERT(aClientType < Client::TypeMax());
6334 return (*mClients)[aClientType];
6337 const AutoTArray<Client::Type, Client::TYPE_MAX>&
6338 QuotaManager::AllClientTypes() {
6339 if (CachedNextGenLocalStorageEnabled()) {
6340 return *mAllClientTypes;
6342 return *mAllClientTypesExceptLS;
6345 uint64_t QuotaManager::GetGroupLimit() const {
6346 // To avoid one group evicting all the rest, limit the amount any one group
6347 // can use to 20% resp. a fifth. To prevent individual sites from using
6348 // exorbitant amounts of storage where there is a lot of free space, cap the
6349 // group limit to 2GB.
6350 const uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 2 GB);
6352 // In low-storage situations, make an exception (while not exceeding the total
6353 // storage limit).
6354 return std::min<uint64_t>(mTemporaryStorageLimit,
6355 std::max<uint64_t>(x, 10 MB));
6358 uint64_t QuotaManager::GetGroupUsage(const nsACString& aGroup) {
6359 AssertIsOnIOThread();
6361 uint64_t usage = 0;
6364 MutexAutoLock lock(mQuotaMutex);
6366 GroupInfoPair* pair;
6367 if (mGroupInfoPairs.Get(aGroup, &pair)) {
6368 for (const PersistenceType type : kBestEffortPersistenceTypes) {
6369 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
6370 if (groupInfo) {
6371 AssertNoOverflow(usage, groupInfo->mUsage);
6372 usage += groupInfo->mUsage;
6378 return usage;
6381 uint64_t QuotaManager::GetOriginUsage(
6382 const PrincipalMetadata& aPrincipalMetadata) {
6383 AssertIsOnIOThread();
6385 uint64_t usage = 0;
6388 MutexAutoLock lock(mQuotaMutex);
6390 GroupInfoPair* pair;
6391 if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
6392 for (const PersistenceType type : kBestEffortPersistenceTypes) {
6393 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
6394 if (groupInfo) {
6395 RefPtr<OriginInfo> originInfo =
6396 groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
6397 if (originInfo) {
6398 AssertNoOverflow(usage, originInfo->LockedUsage());
6399 usage += originInfo->LockedUsage();
6406 return usage;
6409 void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
6410 mQuotaMutex.AssertNotCurrentThreadOwns();
6412 RefPtr<StoragePressureRunnable> storagePressureRunnable =
6413 new StoragePressureRunnable(aUsage);
6415 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
6418 // static
6419 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
6420 const nsACString& aOrigin,
6421 Client::Type aClientType,
6422 nsACString& aDatabaseId) {
6423 nsAutoCString str;
6424 str.AppendInt(aPersistenceType);
6425 str.Append('*');
6426 str.Append(aOrigin);
6427 str.Append('*');
6428 str.AppendInt(aClientType);
6430 aDatabaseId = str;
6433 // static
6434 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
6435 switch (aPrincipalInfo.type()) {
6436 // A system principal is acceptable.
6437 case PrincipalInfo::TSystemPrincipalInfo: {
6438 return true;
6441 // Validate content principals to ensure that the spec, originNoSuffix and
6442 // baseDomain are sane.
6443 case PrincipalInfo::TContentPrincipalInfo: {
6444 const ContentPrincipalInfo& info =
6445 aPrincipalInfo.get_ContentPrincipalInfo();
6447 // Verify the principal spec parses.
6448 RefPtr<MozURL> specURL;
6449 nsresult rv = MozURL::Init(getter_AddRefs(specURL), info.spec());
6450 if (NS_WARN_IF(NS_FAILED(rv))) {
6451 QM_WARNING("A URL %s is not recognized by MozURL", info.spec().get());
6452 return false;
6455 // Verify the principal originNoSuffix matches spec.
6456 nsCString originNoSuffix;
6457 specURL->Origin(originNoSuffix);
6459 if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
6460 QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
6461 originNoSuffix.get(), info.originNoSuffix().get());
6462 return false;
6465 if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
6466 return false;
6469 if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
6470 QM_WARNING("originNoSuffix (%s) contains the '^' character!",
6471 info.originNoSuffix().get());
6472 return false;
6475 // Verify the principal baseDomain exists.
6476 if (NS_WARN_IF(info.baseDomain().IsVoid())) {
6477 return false;
6480 // Verify the principal baseDomain matches spec.
6481 nsCString baseDomain;
6482 rv = specURL->BaseDomain(baseDomain);
6483 if (NS_WARN_IF(NS_FAILED(rv))) {
6484 return false;
6487 if (NS_WARN_IF(baseDomain != info.baseDomain())) {
6488 QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
6489 baseDomain.get(), info.baseDomain().get());
6490 return false;
6493 return true;
6496 default: {
6497 break;
6501 // Null and expanded principals are not acceptable.
6502 return false;
6505 // static
6506 PrincipalMetadata QuotaManager::GetInfoFromValidatedPrincipalInfo(
6507 const PrincipalInfo& aPrincipalInfo) {
6508 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
6510 switch (aPrincipalInfo.type()) {
6511 case PrincipalInfo::TSystemPrincipalInfo: {
6512 return GetInfoForChrome();
6515 case PrincipalInfo::TContentPrincipalInfo: {
6516 const ContentPrincipalInfo& info =
6517 aPrincipalInfo.get_ContentPrincipalInfo();
6519 PrincipalMetadata principalMetadata;
6521 info.attrs().CreateSuffix(principalMetadata.mSuffix);
6523 principalMetadata.mGroup = info.baseDomain() + principalMetadata.mSuffix;
6525 principalMetadata.mOrigin =
6526 info.originNoSuffix() + principalMetadata.mSuffix;
6528 return principalMetadata;
6531 default: {
6532 MOZ_CRASH("Should never get here!");
6537 // static
6538 nsAutoCString QuotaManager::GetOriginFromValidatedPrincipalInfo(
6539 const PrincipalInfo& aPrincipalInfo) {
6540 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
6542 switch (aPrincipalInfo.type()) {
6543 case PrincipalInfo::TSystemPrincipalInfo: {
6544 return nsAutoCString{GetOriginForChrome()};
6547 case PrincipalInfo::TContentPrincipalInfo: {
6548 const ContentPrincipalInfo& info =
6549 aPrincipalInfo.get_ContentPrincipalInfo();
6551 nsAutoCString suffix;
6553 info.attrs().CreateSuffix(suffix);
6555 return info.originNoSuffix() + suffix;
6558 default: {
6559 MOZ_CRASH("Should never get here!");
6564 // static
6565 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromPrincipal(
6566 nsIPrincipal* aPrincipal) {
6567 MOZ_ASSERT(NS_IsMainThread());
6568 MOZ_ASSERT(aPrincipal);
6570 if (aPrincipal->IsSystemPrincipal()) {
6571 return GetInfoForChrome();
6574 if (aPrincipal->GetIsNullPrincipal()) {
6575 NS_WARNING("IndexedDB not supported from this principal!");
6576 return Err(NS_ERROR_FAILURE);
6579 PrincipalMetadata principalMetadata;
6581 QM_TRY(aPrincipal->GetOrigin(principalMetadata.mOrigin));
6583 if (principalMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
6584 NS_WARNING("Non-chrome principal can't use chrome origin!");
6585 return Err(NS_ERROR_FAILURE);
6588 aPrincipal->OriginAttributesRef().CreateSuffix(principalMetadata.mSuffix);
6590 nsAutoCString baseDomain;
6591 QM_TRY(aPrincipal->GetBaseDomain(baseDomain));
6593 MOZ_ASSERT(!baseDomain.IsEmpty());
6595 principalMetadata.mGroup = baseDomain + principalMetadata.mSuffix;
6597 return principalMetadata;
6600 // static
6601 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromPrincipal(
6602 nsIPrincipal* aPrincipal) {
6603 MOZ_ASSERT(NS_IsMainThread());
6604 MOZ_ASSERT(aPrincipal);
6606 if (aPrincipal->IsSystemPrincipal()) {
6607 return nsAutoCString{GetOriginForChrome()};
6610 if (aPrincipal->GetIsNullPrincipal()) {
6611 NS_WARNING("IndexedDB not supported from this principal!");
6612 return Err(NS_ERROR_FAILURE);
6615 QM_TRY_UNWRAP(const auto origin, MOZ_TO_RESULT_INVOKE_TYPED(
6616 nsAutoCString, aPrincipal, GetOrigin));
6618 if (origin.EqualsLiteral(kChromeOrigin)) {
6619 NS_WARNING("Non-chrome principal can't use chrome origin!");
6620 return Err(NS_ERROR_FAILURE);
6623 return origin;
6626 // static
6627 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromWindow(
6628 nsPIDOMWindowOuter* aWindow) {
6629 MOZ_ASSERT(NS_IsMainThread());
6630 MOZ_ASSERT(aWindow);
6632 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6633 QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6635 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6636 QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6638 QM_TRY_RETURN(GetOriginFromPrincipal(principal));
6641 // static
6642 PrincipalMetadata QuotaManager::GetInfoForChrome() {
6643 return {{}, GetOriginForChrome(), GetOriginForChrome()};
6646 // static
6647 nsLiteralCString QuotaManager::GetOriginForChrome() {
6648 return nsLiteralCString{kChromeOrigin};
6651 // static
6652 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
6653 MOZ_ASSERT(!aOrigin.IsEmpty());
6655 // The first prompt is not required for these origins.
6656 if (aOrigin.EqualsLiteral(kChromeOrigin) ||
6657 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
6658 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
6659 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
6660 return true;
6663 return false;
6666 // static
6667 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
6668 const nsACString& aOrigin2) {
6669 return MakeSanitizedOriginCString(aOrigin1) ==
6670 MakeSanitizedOriginCString(aOrigin2);
6673 // static
6674 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
6675 const nsACString& aOrigin) {
6676 // An origin string either corresponds to a SystemPrincipalInfo or a
6677 // ContentPrincipalInfo, see
6678 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6680 if (aOrigin.Equals(kChromeOrigin)) {
6681 return PrincipalInfo{SystemPrincipalInfo{}};
6684 ContentPrincipalInfo contentPrincipalInfo;
6686 nsCString originalSuffix;
6687 const OriginParser::ResultType result = OriginParser::ParseOrigin(
6688 MakeSanitizedOriginCString(aOrigin), contentPrincipalInfo.spec(),
6689 &contentPrincipalInfo.attrs(), originalSuffix);
6690 QM_TRY(OkIf(result == OriginParser::ValidOrigin), Err(NS_ERROR_FAILURE));
6692 return PrincipalInfo{std::move(contentPrincipalInfo)};
6695 // static
6696 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
6698 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6699 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6700 mQuotaMutex.AssertCurrentThreadOwns();
6702 RefPtr<CollectOriginsHelper> helper =
6703 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
6705 // Unlock while calling out to XPCOM (code behind the dispatch method needs
6706 // to acquire its own lock which can potentially lead to a deadlock and it
6707 // also calls an observer that can do various stuff like IO, so it's better
6708 // to not hold our mutex while that happens).
6710 MutexAutoUnlock autoUnlock(mQuotaMutex);
6712 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
6715 return helper->BlockAndReturnOriginsForEviction(aLocks);
6718 void QuotaManager::LockedRemoveQuotaForOrigin(
6719 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6720 mQuotaMutex.AssertCurrentThreadOwns();
6721 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6723 GroupInfoPair* pair;
6724 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6725 return;
6728 MOZ_ASSERT(pair);
6730 if (RefPtr<GroupInfo> groupInfo =
6731 pair->LockedGetGroupInfo(aPersistenceType)) {
6732 groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
6734 if (!groupInfo->LockedHasOriginInfos()) {
6735 pair->LockedClearGroupInfo(aPersistenceType);
6737 if (!pair->LockedHasGroupInfos()) {
6738 mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
6744 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
6745 PersistenceType aPersistenceType, const nsACString& aSuffix,
6746 const nsACString& aGroup) {
6747 mQuotaMutex.AssertCurrentThreadOwns();
6748 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6750 GroupInfoPair* const pair =
6751 mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
6753 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6754 if (!groupInfo) {
6755 groupInfo = new GroupInfo(pair, aPersistenceType);
6756 pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
6759 return groupInfo.forget();
6762 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
6763 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6764 mQuotaMutex.AssertCurrentThreadOwns();
6765 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6767 GroupInfoPair* pair;
6768 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6769 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6770 if (groupInfo) {
6771 return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6775 return nullptr;
6778 template <typename Iterator>
6779 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6780 Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
6781 const RefPtr<GroupInfo>& aDefaultGroupInfo) {
6782 const auto copy = [&aDest](const GroupInfo& groupInfo) {
6783 std::copy_if(
6784 groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
6785 [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
6788 if (aTemporaryGroupInfo) {
6789 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
6790 aTemporaryGroupInfo->GetPersistenceType());
6792 copy(*aTemporaryGroupInfo);
6794 if (aDefaultGroupInfo) {
6795 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
6796 aDefaultGroupInfo->GetPersistenceType());
6798 copy(*aDefaultGroupInfo);
6802 template <typename Collect, typename Pred>
6803 QuotaManager::OriginInfosFlatTraversable
6804 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
6805 OriginInfosFlatTraversable originInfos;
6807 std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
6809 originInfos.Sort(OriginInfoAccessTimeComparator());
6811 const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
6812 std::forward<Pred>(aPred));
6814 originInfos.TruncateLength(foundIt - originInfos.cbegin());
6816 return originInfos;
6819 QuotaManager::OriginInfosNestedTraversable
6820 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
6821 MutexAutoLock lock(mQuotaMutex);
6823 OriginInfosNestedTraversable originInfos;
6825 for (const auto& entry : mGroupInfoPairs) {
6826 const auto& pair = entry.GetData();
6828 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6829 MOZ_ASSERT(pair);
6831 uint64_t groupUsage = 0;
6833 const RefPtr<GroupInfo> temporaryGroupInfo =
6834 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
6835 if (temporaryGroupInfo) {
6836 groupUsage += temporaryGroupInfo->mUsage;
6839 const RefPtr<GroupInfo> defaultGroupInfo =
6840 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
6841 if (defaultGroupInfo) {
6842 groupUsage += defaultGroupInfo->mUsage;
6845 if (groupUsage > 0) {
6846 QuotaManager* quotaManager = QuotaManager::Get();
6847 MOZ_ASSERT(quotaManager, "Shouldn't be null!");
6849 if (groupUsage > quotaManager->GetGroupLimit()) {
6850 originInfos.AppendElement(CollectLRUOriginInfosUntil(
6851 [&temporaryGroupInfo, &defaultGroupInfo](auto inserter) {
6852 MaybeInsertNonPersistedOriginInfos(
6853 std::move(inserter), temporaryGroupInfo, defaultGroupInfo);
6855 [&groupUsage, quotaManager](const auto& originInfo) {
6856 groupUsage -= originInfo->LockedUsage();
6858 return groupUsage <= quotaManager->GetGroupLimit();
6859 }));
6864 return originInfos;
6867 QuotaManager::OriginInfosNestedTraversable
6868 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
6869 MutexAutoLock lock(mQuotaMutex);
6871 QuotaManager::OriginInfosNestedTraversable res;
6872 res.AppendElement(CollectLRUOriginInfosUntil(
6873 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
6874 // can't.
6875 [&](auto inserter) {
6876 for (const auto& entry : mGroupInfoPairs) {
6877 const auto& pair = entry.GetData();
6879 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6880 MOZ_ASSERT(pair);
6882 MaybeInsertNonPersistedOriginInfos(
6883 inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
6884 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT));
6887 [temporaryStorageUsage = mTemporaryStorageUsage,
6888 temporaryStorageLimit = mTemporaryStorageLimit,
6889 doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
6890 if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
6891 return true;
6894 doomedUsage += originInfo->LockedUsage();
6895 return false;
6896 }));
6898 return res;
6901 void QuotaManager::ClearOrigins(
6902 const OriginInfosNestedTraversable& aDoomedOriginInfos) {
6903 AssertIsOnIOThread();
6905 // XXX Does this need to be done a) in order and/or b) sequentially?
6906 for (const auto& doomedOriginInfo :
6907 Flatten<OriginInfosFlatTraversable::elem_type>(aDoomedOriginInfos)) {
6908 #ifdef DEBUG
6910 MutexAutoLock lock(mQuotaMutex);
6911 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
6913 #endif
6915 DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
6916 doomedOriginInfo->mOrigin);
6919 struct OriginParams {
6920 nsCString mOrigin;
6921 PersistenceType mPersistenceType;
6924 nsTArray<OriginParams> clearedOrigins;
6927 MutexAutoLock lock(mQuotaMutex);
6929 for (const auto& doomedOriginInfo :
6930 Flatten<OriginInfosFlatTraversable::elem_type>(aDoomedOriginInfos)) {
6931 // LockedRemoveQuotaForOrigin might remove the group info;
6932 // OriginInfo::mGroupInfo is only a raw pointer, so we need to store the
6933 // information for calling OriginClearCompleted below in a separate array.
6934 clearedOrigins.AppendElement(
6935 OriginParams{doomedOriginInfo->mOrigin,
6936 doomedOriginInfo->mGroupInfo->mPersistenceType});
6938 LockedRemoveQuotaForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
6939 doomedOriginInfo->FlattenToOriginMetadata());
6943 for (const auto& clearedOrigin : clearedOrigins) {
6944 OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin,
6945 Nullable<Client::Type>());
6949 void QuotaManager::CleanupTemporaryStorage() {
6950 AssertIsOnIOThread();
6952 // Evicting origins that exceed their group limit also affects the global
6953 // temporary storage usage, so these steps have to be taken sequentially.
6954 // Combining them doesn't seem worth the added complexity.
6955 ClearOrigins(GetOriginInfosExceedingGroupLimit());
6956 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
6958 if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
6959 // If disk space is still low after origin clear, notify storage pressure.
6960 NotifyStoragePressure(mTemporaryStorageUsage);
6964 void QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
6965 const nsACString& aOrigin) {
6966 QM_TRY_INSPECT(const auto& directory,
6967 GetDirectoryForOrigin(aPersistenceType, aOrigin), QM_VOID);
6969 nsresult rv = directory->Remove(true);
6970 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
6971 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
6972 // This should never fail if we've closed all storage connections
6973 // correctly...
6974 NS_ERROR("Failed to remove directory!");
6978 void QuotaManager::FinalizeOriginEviction(
6979 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
6980 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
6982 RefPtr<FinalizeOriginEvictionOp> op =
6983 new FinalizeOriginEvictionOp(mOwningThread, std::move(aLocks));
6985 if (IsOnIOThread()) {
6986 op->RunOnIOThreadImmediately();
6987 } else {
6988 op->Dispatch();
6992 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
6993 -> DirectoryLockTable& {
6994 switch (aPersistenceType) {
6995 case PERSISTENCE_TYPE_TEMPORARY:
6996 return mTemporaryDirectoryLockTable;
6997 case PERSISTENCE_TYPE_DEFAULT:
6998 return mDefaultDirectoryLockTable;
7000 case PERSISTENCE_TYPE_PERSISTENT:
7001 case PERSISTENCE_TYPE_INVALID:
7002 default:
7003 MOZ_CRASH("Bad persistence type value!");
7007 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
7008 AssertIsOnIOThread();
7010 // Do not parse this sanitized origin string, if we already parsed it.
7011 return mValidOrigins.LookupOrInsertWith(
7012 aSanitizedOrigin, [&aSanitizedOrigin] {
7013 nsCString spec;
7014 OriginAttributes attrs;
7015 nsCString originalSuffix;
7016 const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
7017 &attrs, originalSuffix);
7019 return result == OriginParser::ValidOrigin;
7023 int64_t QuotaManager::GenerateDirectoryLockId() {
7024 const int64_t directorylockId = mNextDirectoryLockId;
7026 if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
7027 result.isValid()) {
7028 mNextDirectoryLockId = result.value();
7029 } else {
7030 NS_WARNING("Quota manager has run out of ids for directory locks!");
7032 // There's very little chance for this to happen given the max size of
7033 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
7034 // to zero since such old directory locks shouldn't exist anymore.
7035 mNextDirectoryLockId = 0;
7038 // TODO: Maybe add an assertion here to check that there is no existing
7039 // directory lock with given id.
7041 return directorylockId;
7044 /*******************************************************************************
7045 * Local class implementations
7046 ******************************************************************************/
7048 void ClientUsageArray::Serialize(nsACString& aText) const {
7049 QuotaManager* quotaManager = QuotaManager::Get();
7050 MOZ_ASSERT(quotaManager);
7052 bool first = true;
7054 for (Client::Type type : quotaManager->AllClientTypes()) {
7055 const Maybe<uint64_t>& clientUsage = ElementAt(type);
7056 if (clientUsage.isSome()) {
7057 if (first) {
7058 first = false;
7059 } else {
7060 aText.Append(" ");
7063 aText.Append(Client::TypeToPrefix(type));
7064 aText.AppendInt(clientUsage.value());
7069 nsresult ClientUsageArray::Deserialize(const nsACString& aText) {
7070 for (const auto& token :
7071 nsCCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
7072 .ToRange()) {
7073 QM_TRY(OkIf(token.Length() >= 2), NS_ERROR_FAILURE);
7075 Client::Type clientType;
7076 QM_TRY(OkIf(Client::TypeFromPrefix(token.First(), clientType, fallible)),
7077 NS_ERROR_FAILURE);
7079 nsresult rv;
7080 const uint64_t usage = Substring(token, 1).ToInteger(&rv);
7081 QM_TRY(ToResult(rv));
7083 ElementAt(clientType) = Some(usage);
7086 return NS_OK;
7089 OriginInfo::OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
7090 const ClientUsageArray& aClientUsages, uint64_t aUsage,
7091 int64_t aAccessTime, bool aPersisted,
7092 bool aDirectoryExists)
7093 : mClientUsages(aClientUsages.Clone()),
7094 mGroupInfo(aGroupInfo),
7095 mOrigin(aOrigin),
7096 mUsage(aUsage),
7097 mAccessTime(aAccessTime),
7098 mAccessed(false),
7099 mPersisted(aPersisted),
7100 mDirectoryExists(aDirectoryExists) {
7101 MOZ_ASSERT(aGroupInfo);
7102 MOZ_ASSERT(aClientUsages.Length() == Client::TypeMax());
7103 MOZ_ASSERT_IF(aPersisted,
7104 aGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
7106 #ifdef DEBUG
7107 QuotaManager* quotaManager = QuotaManager::Get();
7108 MOZ_ASSERT(quotaManager);
7110 uint64_t usage = 0;
7111 for (Client::Type type : quotaManager->AllClientTypes()) {
7112 AssertNoOverflow(usage, aClientUsages[type].valueOr(0));
7113 usage += aClientUsages[type].valueOr(0);
7115 MOZ_ASSERT(aUsage == usage);
7116 #endif
7118 MOZ_COUNT_CTOR(OriginInfo);
7121 OriginMetadata OriginInfo::FlattenToOriginMetadata() const {
7122 return {mGroupInfo->mGroupInfoPair->Suffix(),
7123 mGroupInfo->mGroupInfoPair->Group(), mOrigin,
7124 mGroupInfo->mPersistenceType};
7127 nsresult OriginInfo::LockedBindToStatement(
7128 mozIStorageStatement* aStatement) const {
7129 AssertCurrentThreadOwnsQuotaMutex();
7130 MOZ_ASSERT(mGroupInfo);
7132 QM_TRY(aStatement->BindInt32ByName("repository_id"_ns,
7133 mGroupInfo->mPersistenceType));
7135 QM_TRY(aStatement->BindUTF8StringByName(
7136 "suffix"_ns, mGroupInfo->mGroupInfoPair->Suffix()));
7137 QM_TRY(aStatement->BindUTF8StringByName("group_"_ns,
7138 mGroupInfo->mGroupInfoPair->Group()));
7139 QM_TRY(aStatement->BindUTF8StringByName("origin"_ns, mOrigin));
7141 nsCString clientUsagesText;
7142 mClientUsages.Serialize(clientUsagesText);
7144 QM_TRY(
7145 aStatement->BindUTF8StringByName("client_usages"_ns, clientUsagesText));
7146 QM_TRY(aStatement->BindInt64ByName("usage"_ns, mUsage));
7147 QM_TRY(aStatement->BindInt64ByName("last_access_time"_ns, mAccessTime));
7148 QM_TRY(aStatement->BindInt32ByName("accessed"_ns, mAccessed));
7149 QM_TRY(aStatement->BindInt32ByName("persisted"_ns, mPersisted));
7151 return NS_OK;
7154 void OriginInfo::LockedDecreaseUsage(Client::Type aClientType, int64_t aSize) {
7155 AssertCurrentThreadOwnsQuotaMutex();
7157 MOZ_ASSERT(mClientUsages[aClientType].isSome());
7158 AssertNoUnderflow(mClientUsages[aClientType].value(), aSize);
7159 mClientUsages[aClientType] = Some(mClientUsages[aClientType].value() - aSize);
7161 AssertNoUnderflow(mUsage, aSize);
7162 mUsage -= aSize;
7164 if (!LockedPersisted()) {
7165 AssertNoUnderflow(mGroupInfo->mUsage, aSize);
7166 mGroupInfo->mUsage -= aSize;
7169 QuotaManager* quotaManager = QuotaManager::Get();
7170 MOZ_ASSERT(quotaManager);
7172 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
7173 quotaManager->mTemporaryStorageUsage -= aSize;
7176 void OriginInfo::LockedResetUsageForClient(Client::Type aClientType) {
7177 AssertCurrentThreadOwnsQuotaMutex();
7179 uint64_t size = mClientUsages[aClientType].valueOr(0);
7181 mClientUsages[aClientType].reset();
7183 AssertNoUnderflow(mUsage, size);
7184 mUsage -= size;
7186 if (!LockedPersisted()) {
7187 AssertNoUnderflow(mGroupInfo->mUsage, size);
7188 mGroupInfo->mUsage -= size;
7191 QuotaManager* quotaManager = QuotaManager::Get();
7192 MOZ_ASSERT(quotaManager);
7194 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, size);
7195 quotaManager->mTemporaryStorageUsage -= size;
7198 UsageInfo OriginInfo::LockedGetUsageForClient(Client::Type aClientType) {
7199 AssertCurrentThreadOwnsQuotaMutex();
7201 // The current implementation of this method only supports DOMCACHE and LS,
7202 // which only use DatabaseUsage. If this assertion is lifted, the logic below
7203 // must be adapted.
7204 MOZ_ASSERT(aClientType == Client::Type::DOMCACHE ||
7205 aClientType == Client::Type::LS);
7207 return UsageInfo{DatabaseUsageType{mClientUsages[aClientType]}};
7210 void OriginInfo::LockedPersist() {
7211 AssertCurrentThreadOwnsQuotaMutex();
7212 MOZ_ASSERT(mGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
7213 MOZ_ASSERT(!mPersisted);
7215 mPersisted = true;
7217 // Remove Usage from GroupInfo
7218 AssertNoUnderflow(mGroupInfo->mUsage, mUsage);
7219 mGroupInfo->mUsage -= mUsage;
7222 already_AddRefed<OriginInfo> GroupInfo::LockedGetOriginInfo(
7223 const nsACString& aOrigin) {
7224 AssertCurrentThreadOwnsQuotaMutex();
7226 for (const auto& originInfo : mOriginInfos) {
7227 if (originInfo->mOrigin == aOrigin) {
7228 RefPtr<OriginInfo> result = originInfo;
7229 return result.forget();
7233 return nullptr;
7236 void GroupInfo::LockedAddOriginInfo(NotNull<RefPtr<OriginInfo>>&& aOriginInfo) {
7237 AssertCurrentThreadOwnsQuotaMutex();
7239 NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
7240 "Replacing an existing entry!");
7241 mOriginInfos.AppendElement(std::move(aOriginInfo));
7243 uint64_t usage = aOriginInfo->LockedUsage();
7245 if (!aOriginInfo->LockedPersisted()) {
7246 AssertNoOverflow(mUsage, usage);
7247 mUsage += usage;
7250 QuotaManager* quotaManager = QuotaManager::Get();
7251 MOZ_ASSERT(quotaManager);
7253 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, usage);
7254 quotaManager->mTemporaryStorageUsage += usage;
7257 void GroupInfo::LockedAdjustUsageForRemovedOriginInfo(
7258 const OriginInfo& aOriginInfo) {
7259 const uint64_t usage = aOriginInfo.LockedUsage();
7261 if (!aOriginInfo.LockedPersisted()) {
7262 AssertNoUnderflow(mUsage, usage);
7263 mUsage -= usage;
7266 QuotaManager* const quotaManager = QuotaManager::Get();
7267 MOZ_ASSERT(quotaManager);
7269 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, usage);
7270 quotaManager->mTemporaryStorageUsage -= usage;
7273 void GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) {
7274 AssertCurrentThreadOwnsQuotaMutex();
7276 const auto foundIt = std::find_if(mOriginInfos.cbegin(), mOriginInfos.cend(),
7277 [&aOrigin](const auto& originInfo) {
7278 return originInfo->mOrigin == aOrigin;
7281 // XXX Or can we MOZ_ASSERT(foundIt != mOriginInfos.cend()) ?
7282 if (foundIt != mOriginInfos.cend()) {
7283 LockedAdjustUsageForRemovedOriginInfo(**foundIt);
7285 mOriginInfos.RemoveElementAt(foundIt);
7289 void GroupInfo::LockedRemoveOriginInfos() {
7290 AssertCurrentThreadOwnsQuotaMutex();
7292 for (const auto& originInfo : std::exchange(mOriginInfos, {})) {
7293 LockedAdjustUsageForRemovedOriginInfo(*originInfo);
7297 RefPtr<GroupInfo>& GroupInfoPair::GetGroupInfoForPersistenceType(
7298 PersistenceType aPersistenceType) {
7299 switch (aPersistenceType) {
7300 case PERSISTENCE_TYPE_TEMPORARY:
7301 return mTemporaryStorageGroupInfo;
7302 case PERSISTENCE_TYPE_DEFAULT:
7303 return mDefaultStorageGroupInfo;
7305 case PERSISTENCE_TYPE_PERSISTENT:
7306 case PERSISTENCE_TYPE_INVALID:
7307 default:
7308 MOZ_CRASH("Bad persistence type value!");
7312 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
7313 uint64_t aMinSizeToBeFreed)
7314 : Runnable("dom::quota::CollectOriginsHelper"),
7315 mMinSizeToBeFreed(aMinSizeToBeFreed),
7316 mMutex(aMutex),
7317 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
7318 mSizeToBeFreed(0),
7319 mWaiting(true) {
7320 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7321 mMutex.AssertCurrentThreadOwns();
7324 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
7325 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
7326 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7327 mMutex.AssertCurrentThreadOwns();
7329 while (mWaiting) {
7330 mCondVar.Wait();
7333 mLocks.SwapElements(aLocks);
7334 return mSizeToBeFreed;
7337 NS_IMETHODIMP
7338 CollectOriginsHelper::Run() {
7339 AssertIsOnBackgroundThread();
7341 QuotaManager* quotaManager = QuotaManager::Get();
7342 NS_ASSERTION(quotaManager, "Shouldn't be null!");
7344 // We use extra stack vars here to avoid race detector warnings (the same
7345 // memory accessed with and without the lock held).
7346 nsTArray<RefPtr<OriginDirectoryLock>> locks;
7347 uint64_t sizeToBeFreed =
7348 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
7350 MutexAutoLock lock(mMutex);
7352 NS_ASSERTION(mWaiting, "Huh?!");
7354 mLocks.SwapElements(locks);
7355 mSizeToBeFreed = sizeToBeFreed;
7356 mWaiting = false;
7357 mCondVar.Notify();
7359 return NS_OK;
7362 /*******************************************************************************
7363 * OriginOperationBase
7364 ******************************************************************************/
7366 NS_IMETHODIMP
7367 OriginOperationBase::Run() {
7368 nsresult rv;
7370 switch (mState) {
7371 case State_Initial: {
7372 rv = Init();
7373 break;
7376 case State_CreatingQuotaManager: {
7377 rv = QuotaManagerOpen();
7378 break;
7381 case State_DirectoryOpenPending: {
7382 rv = DirectoryOpen();
7383 break;
7386 case State_DirectoryWorkOpen: {
7387 rv = DirectoryWork();
7388 break;
7391 case State_UnblockingOpen: {
7392 UnblockOpen();
7393 return NS_OK;
7396 default:
7397 MOZ_CRASH("Bad state!");
7400 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
7401 Finish(rv);
7404 return NS_OK;
7407 nsresult OriginOperationBase::DirectoryOpen() {
7408 AssertIsOnOwningThread();
7409 MOZ_ASSERT(mState == State_DirectoryOpenPending);
7411 QuotaManager* const quotaManager = QuotaManager::Get();
7412 QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
7414 // Must set this before dispatching otherwise we will race with the IO thread.
7415 AdvanceState();
7417 QM_TRY(quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL),
7418 NS_ERROR_FAILURE);
7420 return NS_OK;
7423 void OriginOperationBase::Finish(nsresult aResult) {
7424 if (NS_SUCCEEDED(mResultCode)) {
7425 mResultCode = aResult;
7428 // Must set mState before dispatching otherwise we will race with the main
7429 // thread.
7430 mState = State_UnblockingOpen;
7432 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7435 nsresult OriginOperationBase::Init() {
7436 AssertIsOnOwningThread();
7437 MOZ_ASSERT(mState == State_Initial);
7439 if (QuotaManager::IsShuttingDown()) {
7440 return NS_ERROR_FAILURE;
7443 AdvanceState();
7445 if (mNeedsQuotaManagerInit && !QuotaManager::Get()) {
7446 QuotaManager::GetOrCreate(this);
7447 } else {
7448 Open();
7451 return NS_OK;
7454 nsresult OriginOperationBase::QuotaManagerOpen() {
7455 AssertIsOnOwningThread();
7456 MOZ_ASSERT(mState == State_CreatingQuotaManager);
7458 if (NS_WARN_IF(!QuotaManager::Get())) {
7459 return NS_ERROR_FAILURE;
7462 Open();
7464 return NS_OK;
7467 nsresult OriginOperationBase::DirectoryWork() {
7468 AssertIsOnIOThread();
7469 MOZ_ASSERT(mState == State_DirectoryWorkOpen);
7471 QuotaManager* const quotaManager = QuotaManager::Get();
7472 QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
7474 if (mNeedsStorageInit) {
7475 QM_TRY(quotaManager->EnsureStorageIsInitialized());
7478 QM_TRY(DoDirectoryWork(*quotaManager));
7480 // Must set mState before dispatching otherwise we will race with the owning
7481 // thread.
7482 AdvanceState();
7484 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7486 return NS_OK;
7489 void FinalizeOriginEvictionOp::Dispatch() {
7490 MOZ_ASSERT(!NS_IsMainThread());
7491 MOZ_ASSERT(GetState() == State_Initial);
7493 SetState(State_DirectoryOpenPending);
7495 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7498 void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() {
7499 AssertIsOnIOThread();
7500 MOZ_ASSERT(GetState() == State_Initial);
7502 SetState(State_DirectoryWorkOpen);
7504 MOZ_ALWAYS_SUCCEEDS(this->Run());
7507 void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); }
7509 nsresult FinalizeOriginEvictionOp::DoDirectoryWork(
7510 QuotaManager& aQuotaManager) {
7511 AssertIsOnIOThread();
7513 AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER);
7515 for (const auto& lock : mLocks) {
7516 aQuotaManager.OriginClearCompleted(
7517 lock->GetPersistenceType(), lock->Origin(), Nullable<Client::Type>());
7520 return NS_OK;
7523 void FinalizeOriginEvictionOp::UnblockOpen() {
7524 AssertIsOnOwningThread();
7525 MOZ_ASSERT(GetState() == State_UnblockingOpen);
7527 #ifdef DEBUG
7528 NoteActorDestroyed();
7529 #endif
7531 mLocks.Clear();
7533 AdvanceState();
7536 NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable)
7538 void NormalOriginOperationBase::Open() {
7539 AssertIsOnOwningThread();
7540 MOZ_ASSERT(GetState() == State_CreatingQuotaManager);
7541 MOZ_ASSERT(QuotaManager::Get());
7543 AdvanceState();
7545 if (mNeedsDirectoryLocking) {
7546 RefPtr<DirectoryLock> directoryLock =
7547 QuotaManager::Get()->CreateDirectoryLockInternal(
7548 mPersistenceType, mOriginScope, mClientType, mExclusive);
7550 directoryLock->Acquire(this);
7551 } else {
7552 QM_TRY(DirectoryOpen(), QM_VOID, [this](const nsresult rv) { Finish(rv); });
7556 void NormalOriginOperationBase::UnblockOpen() {
7557 AssertIsOnOwningThread();
7558 MOZ_ASSERT(GetState() == State_UnblockingOpen);
7560 SendResults();
7562 if (mNeedsDirectoryLocking) {
7563 mDirectoryLock = nullptr;
7566 UnregisterNormalOriginOp(*this);
7568 AdvanceState();
7571 void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) {
7572 AssertIsOnOwningThread();
7573 MOZ_ASSERT(aLock);
7574 MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
7575 MOZ_ASSERT(!mDirectoryLock);
7577 mDirectoryLock = aLock;
7579 QM_TRY(DirectoryOpen(), QM_VOID, [this](const nsresult rv) { Finish(rv); });
7582 void NormalOriginOperationBase::DirectoryLockFailed() {
7583 AssertIsOnOwningThread();
7584 MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
7585 MOZ_ASSERT(!mDirectoryLock);
7587 Finish(NS_ERROR_FAILURE);
7590 nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
7591 AssertIsOnIOThread();
7592 MOZ_ASSERT(!mPersistenceType.IsNull());
7593 MOZ_ASSERT(mOriginScope.IsOrigin());
7595 AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER);
7597 QM_TRY_INSPECT(const auto& file,
7598 aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
7599 mOriginScope.GetOrigin()));
7601 // The origin directory might not exist
7602 // anymore, because it was deleted by a clear operation.
7603 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(file, Exists));
7605 if (exists) {
7606 QM_TRY(file->Append(nsLiteralString(METADATA_V2_FILE_NAME)));
7608 QM_TRY_INSPECT(const auto& stream,
7609 GetBinaryOutputStream(*file, FileFlag::Update));
7610 MOZ_ASSERT(stream);
7612 QM_TRY(stream->Write64(mTimestamp));
7615 return NS_OK;
7618 void SaveOriginAccessTimeOp::SendResults() {
7619 #ifdef DEBUG
7620 NoteActorDestroyed();
7621 #endif
7624 NS_IMETHODIMP
7625 StoragePressureRunnable::Run() {
7626 MOZ_ASSERT(NS_IsMainThread());
7628 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
7629 if (NS_WARN_IF(!obsSvc)) {
7630 return NS_ERROR_FAILURE;
7633 nsCOMPtr<nsISupportsPRUint64> wrapper =
7634 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
7635 if (NS_WARN_IF(!wrapper)) {
7636 return NS_ERROR_FAILURE;
7639 wrapper->SetData(mUsage);
7641 obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
7643 return NS_OK;
7646 void RecordQuotaInfoLoadTimeHelper::Start() {
7647 AssertIsOnIOThread();
7649 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
7650 // gLastOSWake is set, then this time duration would still be recorded with
7651 // key "Normal". We are assumming this is rather rare to happen.
7652 mStartTime.init(TimeStamp::Now());
7653 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7656 void RecordQuotaInfoLoadTimeHelper::End() {
7657 AssertIsOnIOThread();
7659 mEndTime.init(TimeStamp::Now());
7660 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7663 NS_IMETHODIMP
7664 RecordQuotaInfoLoadTimeHelper::Run() {
7665 MOZ_ASSERT(NS_IsMainThread());
7667 if (mInitializedTime.isSome()) {
7668 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0:
7669 // Normal: Normal conditions.
7670 // WasSuspended: There was a OS sleep so that it was suspended.
7671 // TimeStampErr1: The recorded start time is unexpectedly greater than the
7672 // end time.
7673 // TimeStampErr2: The initialized time for the recording class is unexpectly
7674 // greater than the last OS wake time.
7675 const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
7676 if (wasSuspended) {
7677 return "WasSuspended"_ns;
7680 // XXX File a bug if we have data for this key.
7681 // We found negative values in our query in STMO for
7682 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
7683 // because the documentation for TimeStamp::Now() says it returns a
7684 // monotonically increasing number.
7685 if (*mStartTime > *mEndTime) {
7686 return "TimeStampErr1"_ns;
7689 if (*mInitializedTime > gLastOSWake) {
7690 return "TimeStampErr2"_ns;
7693 return "Normal"_ns;
7694 }();
7696 Telemetry::AccumulateTimeDelta(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0, key,
7697 *mStartTime, *mEndTime);
7699 return NS_OK;
7702 gLastOSWake = TimeStamp::Now();
7703 mInitializedTime.init(gLastOSWake);
7705 return NS_OK;
7708 /*******************************************************************************
7709 * Quota
7710 ******************************************************************************/
7712 Quota::Quota()
7713 #ifdef DEBUG
7714 : mActorDestroyed(false)
7715 #endif
7719 Quota::~Quota() { MOZ_ASSERT(mActorDestroyed); }
7721 void Quota::StartIdleMaintenance() {
7722 AssertIsOnBackgroundThread();
7723 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
7725 QuotaManager* const quotaManager = QuotaManager::Get();
7726 QM_TRY(OkIf(quotaManager), QM_VOID);
7728 quotaManager->StartIdleMaintenance();
7731 bool Quota::VerifyRequestParams(const UsageRequestParams& aParams) const {
7732 AssertIsOnBackgroundThread();
7733 MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
7735 switch (aParams.type()) {
7736 case UsageRequestParams::TAllUsageParams:
7737 break;
7739 case UsageRequestParams::TOriginUsageParams: {
7740 const OriginUsageParams& params = aParams.get_OriginUsageParams();
7742 if (NS_WARN_IF(
7743 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7744 ASSERT_UNLESS_FUZZING();
7745 return false;
7748 break;
7751 default:
7752 MOZ_CRASH("Should never get here!");
7755 return true;
7758 bool Quota::VerifyRequestParams(const RequestParams& aParams) const {
7759 AssertIsOnBackgroundThread();
7760 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
7762 switch (aParams.type()) {
7763 case RequestParams::TStorageNameParams:
7764 case RequestParams::TStorageInitializedParams:
7765 case RequestParams::TTemporaryStorageInitializedParams:
7766 case RequestParams::TInitParams:
7767 case RequestParams::TInitTemporaryStorageParams:
7768 break;
7770 case RequestParams::TInitializePersistentOriginParams: {
7771 const InitializePersistentOriginParams& params =
7772 aParams.get_InitializePersistentOriginParams();
7774 if (NS_WARN_IF(
7775 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7776 ASSERT_UNLESS_FUZZING();
7777 return false;
7780 break;
7783 case RequestParams::TInitializeTemporaryOriginParams: {
7784 const InitializeTemporaryOriginParams& params =
7785 aParams.get_InitializeTemporaryOriginParams();
7787 if (NS_WARN_IF(!IsBestEffortPersistenceType(params.persistenceType()))) {
7788 ASSERT_UNLESS_FUZZING();
7789 return false;
7792 if (NS_WARN_IF(
7793 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7794 ASSERT_UNLESS_FUZZING();
7795 return false;
7798 break;
7801 case RequestParams::TClearOriginParams: {
7802 const ClearResetOriginParams& params =
7803 aParams.get_ClearOriginParams().commonParams();
7805 if (NS_WARN_IF(
7806 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7807 ASSERT_UNLESS_FUZZING();
7808 return false;
7811 if (params.persistenceTypeIsExplicit()) {
7812 if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
7813 ASSERT_UNLESS_FUZZING();
7814 return false;
7818 if (params.clientTypeIsExplicit()) {
7819 if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
7820 ASSERT_UNLESS_FUZZING();
7821 return false;
7825 break;
7828 case RequestParams::TResetOriginParams: {
7829 const ClearResetOriginParams& params =
7830 aParams.get_ResetOriginParams().commonParams();
7832 if (NS_WARN_IF(
7833 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7834 ASSERT_UNLESS_FUZZING();
7835 return false;
7838 if (params.persistenceTypeIsExplicit()) {
7839 if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
7840 ASSERT_UNLESS_FUZZING();
7841 return false;
7845 if (params.clientTypeIsExplicit()) {
7846 if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
7847 ASSERT_UNLESS_FUZZING();
7848 return false;
7852 break;
7855 case RequestParams::TClearDataParams: {
7856 if (BackgroundParent::IsOtherProcessActor(Manager())) {
7857 ASSERT_UNLESS_FUZZING();
7858 return false;
7861 break;
7864 case RequestParams::TClearAllParams:
7865 case RequestParams::TResetAllParams:
7866 case RequestParams::TListOriginsParams:
7867 break;
7869 case RequestParams::TPersistedParams: {
7870 const PersistedParams& params = aParams.get_PersistedParams();
7872 if (NS_WARN_IF(
7873 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7874 ASSERT_UNLESS_FUZZING();
7875 return false;
7878 break;
7881 case RequestParams::TPersistParams: {
7882 const PersistParams& params = aParams.get_PersistParams();
7884 if (NS_WARN_IF(
7885 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7886 ASSERT_UNLESS_FUZZING();
7887 return false;
7890 break;
7893 case RequestParams::TEstimateParams: {
7894 const EstimateParams& params = aParams.get_EstimateParams();
7896 if (NS_WARN_IF(
7897 !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
7898 ASSERT_UNLESS_FUZZING();
7899 return false;
7902 break;
7905 default:
7906 MOZ_CRASH("Should never get here!");
7909 return true;
7912 void Quota::ActorDestroy(ActorDestroyReason aWhy) {
7913 AssertIsOnBackgroundThread();
7914 #ifdef DEBUG
7915 MOZ_ASSERT(!mActorDestroyed);
7916 mActorDestroyed = true;
7917 #endif
7920 PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(
7921 const UsageRequestParams& aParams) {
7922 AssertIsOnBackgroundThread();
7923 MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
7925 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
7926 return nullptr;
7929 #ifdef DEBUG
7930 // Always verify parameters in DEBUG builds!
7931 bool trustParams = false;
7932 #else
7933 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
7934 #endif
7936 if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
7937 ASSERT_UNLESS_FUZZING();
7938 return nullptr;
7941 auto actor = [&]() -> RefPtr<QuotaUsageRequestBase> {
7942 switch (aParams.type()) {
7943 case UsageRequestParams::TAllUsageParams:
7944 return MakeRefPtr<GetUsageOp>(aParams);
7946 case UsageRequestParams::TOriginUsageParams:
7947 return MakeRefPtr<GetOriginUsageOp>(aParams);
7949 default:
7950 MOZ_CRASH("Should never get here!");
7952 }();
7954 MOZ_ASSERT(actor);
7956 RegisterNormalOriginOp(*actor);
7958 // Transfer ownership to IPDL.
7959 return actor.forget().take();
7962 mozilla::ipc::IPCResult Quota::RecvPQuotaUsageRequestConstructor(
7963 PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) {
7964 AssertIsOnBackgroundThread();
7965 MOZ_ASSERT(aActor);
7966 MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
7967 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
7969 auto* op = static_cast<QuotaUsageRequestBase*>(aActor);
7971 op->Init(*this);
7973 op->RunImmediately();
7974 return IPC_OK();
7977 bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) {
7978 AssertIsOnBackgroundThread();
7979 MOZ_ASSERT(aActor);
7981 // Transfer ownership back from IPDL.
7982 RefPtr<QuotaUsageRequestBase> actor =
7983 dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor));
7984 return true;
7987 PQuotaRequestParent* Quota::AllocPQuotaRequestParent(
7988 const RequestParams& aParams) {
7989 AssertIsOnBackgroundThread();
7990 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
7992 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
7993 return nullptr;
7996 #ifdef DEBUG
7997 // Always verify parameters in DEBUG builds!
7998 bool trustParams = false;
7999 #else
8000 bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
8001 #endif
8003 if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
8004 ASSERT_UNLESS_FUZZING();
8005 return nullptr;
8008 auto actor = [&]() -> RefPtr<QuotaRequestBase> {
8009 switch (aParams.type()) {
8010 case RequestParams::TStorageNameParams:
8011 return MakeRefPtr<StorageNameOp>();
8013 case RequestParams::TStorageInitializedParams:
8014 return MakeRefPtr<StorageInitializedOp>();
8016 case RequestParams::TTemporaryStorageInitializedParams:
8017 return MakeRefPtr<TemporaryStorageInitializedOp>();
8019 case RequestParams::TInitParams:
8020 return MakeRefPtr<InitOp>();
8022 case RequestParams::TInitTemporaryStorageParams:
8023 return MakeRefPtr<InitTemporaryStorageOp>();
8025 case RequestParams::TInitializePersistentOriginParams:
8026 return MakeRefPtr<InitializePersistentOriginOp>(aParams);
8028 case RequestParams::TInitializeTemporaryOriginParams:
8029 return MakeRefPtr<InitializeTemporaryOriginOp>(aParams);
8031 case RequestParams::TClearOriginParams:
8032 return MakeRefPtr<ClearOriginOp>(aParams);
8034 case RequestParams::TResetOriginParams:
8035 return MakeRefPtr<ResetOriginOp>(aParams);
8037 case RequestParams::TClearDataParams:
8038 return MakeRefPtr<ClearDataOp>(aParams);
8040 case RequestParams::TClearAllParams:
8041 return MakeRefPtr<ResetOrClearOp>(/* aClear */ true);
8043 case RequestParams::TResetAllParams:
8044 return MakeRefPtr<ResetOrClearOp>(/* aClear */ false);
8046 case RequestParams::TPersistedParams:
8047 return MakeRefPtr<PersistedOp>(aParams);
8049 case RequestParams::TPersistParams:
8050 return MakeRefPtr<PersistOp>(aParams);
8052 case RequestParams::TEstimateParams:
8053 return MakeRefPtr<EstimateOp>(aParams);
8055 case RequestParams::TListOriginsParams:
8056 return MakeRefPtr<ListOriginsOp>();
8058 default:
8059 MOZ_CRASH("Should never get here!");
8061 }();
8063 MOZ_ASSERT(actor);
8065 RegisterNormalOriginOp(*actor);
8067 // Transfer ownership to IPDL.
8068 return actor.forget().take();
8071 mozilla::ipc::IPCResult Quota::RecvPQuotaRequestConstructor(
8072 PQuotaRequestParent* aActor, const RequestParams& aParams) {
8073 AssertIsOnBackgroundThread();
8074 MOZ_ASSERT(aActor);
8075 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
8076 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
8078 auto* op = static_cast<QuotaRequestBase*>(aActor);
8080 op->Init(*this);
8082 op->RunImmediately();
8083 return IPC_OK();
8086 bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) {
8087 AssertIsOnBackgroundThread();
8088 MOZ_ASSERT(aActor);
8090 // Transfer ownership back from IPDL.
8091 RefPtr<QuotaRequestBase> actor =
8092 dont_AddRef(static_cast<QuotaRequestBase*>(aActor));
8093 return true;
8096 mozilla::ipc::IPCResult Quota::RecvStartIdleMaintenance() {
8097 AssertIsOnBackgroundThread();
8099 PBackgroundParent* actor = Manager();
8100 MOZ_ASSERT(actor);
8102 if (BackgroundParent::IsOtherProcessActor(actor)) {
8103 ASSERT_UNLESS_FUZZING();
8104 return IPC_FAIL_NO_REASON(this);
8107 if (QuotaManager::IsShuttingDown()) {
8108 return IPC_OK();
8111 QuotaManager* quotaManager = QuotaManager::Get();
8112 if (!quotaManager) {
8113 nsCOMPtr<nsIRunnable> callback =
8114 NewRunnableMethod("dom::quota::Quota::StartIdleMaintenance", this,
8115 &Quota::StartIdleMaintenance);
8117 QuotaManager::GetOrCreate(callback);
8118 return IPC_OK();
8121 quotaManager->StartIdleMaintenance();
8123 return IPC_OK();
8126 mozilla::ipc::IPCResult Quota::RecvStopIdleMaintenance() {
8127 AssertIsOnBackgroundThread();
8129 PBackgroundParent* actor = Manager();
8130 MOZ_ASSERT(actor);
8132 if (BackgroundParent::IsOtherProcessActor(actor)) {
8133 ASSERT_UNLESS_FUZZING();
8134 return IPC_FAIL_NO_REASON(this);
8137 if (QuotaManager::IsShuttingDown()) {
8138 return IPC_OK();
8141 QuotaManager* quotaManager = QuotaManager::Get();
8142 if (!quotaManager) {
8143 return IPC_OK();
8146 quotaManager->StopIdleMaintenance();
8148 return IPC_OK();
8151 mozilla::ipc::IPCResult Quota::RecvAbortOperationsForProcess(
8152 const ContentParentId& aContentParentId) {
8153 AssertIsOnBackgroundThread();
8155 PBackgroundParent* actor = Manager();
8156 MOZ_ASSERT(actor);
8158 if (BackgroundParent::IsOtherProcessActor(actor)) {
8159 ASSERT_UNLESS_FUZZING();
8160 return IPC_FAIL_NO_REASON(this);
8163 if (QuotaManager::IsShuttingDown()) {
8164 return IPC_OK();
8167 QuotaManager* quotaManager = QuotaManager::Get();
8168 if (!quotaManager) {
8169 return IPC_OK();
8172 quotaManager->AbortOperationsForProcess(aContentParentId);
8174 return IPC_OK();
8177 void QuotaUsageRequestBase::Init(Quota& aQuota) {
8178 AssertIsOnOwningThread();
8180 mNeedsQuotaManagerInit = true;
8181 mNeedsStorageInit = true;
8184 Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOrigin(
8185 QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
8186 const OriginMetadata& aOriginMetadata) {
8187 AssertIsOnIOThread();
8189 QM_TRY_INSPECT(const auto& directory,
8190 aQuotaManager.GetDirectoryForOrigin(aPersistenceType,
8191 aOriginMetadata.mOrigin));
8193 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(directory, Exists));
8195 if (!exists || mCanceled) {
8196 return UsageInfo();
8199 // If the directory exists then enumerate all the files inside, adding up
8200 // the sizes to get the final usage statistic.
8201 bool initialized;
8203 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
8204 initialized = aQuotaManager.IsOriginInitialized(aOriginMetadata.mOrigin);
8205 } else {
8206 initialized = aQuotaManager.IsTemporaryStorageInitialized();
8209 return GetUsageForOriginEntries(aQuotaManager, aPersistenceType,
8210 aOriginMetadata, *directory, initialized);
8213 Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOriginEntries(
8214 QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
8215 const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
8216 const bool aInitialized) {
8217 AssertIsOnIOThread();
8219 QM_TRY_RETURN((ReduceEachFileAtomicCancelable(
8220 aDirectory, mCanceled, UsageInfo{},
8221 [&](UsageInfo oldUsageInfo, const nsCOMPtr<nsIFile>& file)
8222 -> mozilla::Result<UsageInfo, nsresult> {
8223 QM_TRY_INSPECT(
8224 const auto& leafName,
8225 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
8227 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
8229 switch (dirEntryKind) {
8230 case nsIFileKind::ExistsAsDirectory: {
8231 Client::Type clientType;
8232 const bool ok =
8233 Client::TypeFromText(leafName, clientType, fallible);
8234 if (!ok) {
8235 // Unknown directories during getting usage for an origin (even
8236 // for an uninitialized origin) are now allowed. Just warn if we
8237 // find them.
8238 UNKNOWN_FILE_WARNING(leafName);
8239 break;
8242 Client* const client = aQuotaManager.GetClient(clientType);
8243 MOZ_ASSERT(client);
8245 QM_TRY_INSPECT(
8246 const auto& usageInfo,
8247 aInitialized ? client->GetUsageForOrigin(
8248 aPersistenceType, aOriginMetadata, mCanceled)
8249 : client->InitOrigin(aPersistenceType,
8250 aOriginMetadata, mCanceled));
8251 return oldUsageInfo + usageInfo;
8254 case nsIFileKind::ExistsAsFile:
8255 // We are maintaining existing behavior for unknown files here (just
8256 // continuing).
8257 // This can possibly be used by developers to add temporary backups
8258 // into origin directories without losing get usage functionality.
8259 if (IsTempMetadata(leafName)) {
8260 if (!aInitialized) {
8261 QM_TRY(file->Remove(/* recursive */ false));
8264 break;
8267 if (IsOriginMetadata(leafName) || IsOSMetadata(leafName) ||
8268 IsDotFile(leafName)) {
8269 break;
8272 // Unknown files during getting usage for an origin (even for an
8273 // uninitialized origin) are now allowed. Just warn if we find them.
8274 UNKNOWN_FILE_WARNING(leafName);
8275 break;
8277 case nsIFileKind::DoesNotExist:
8278 // Ignore files that got removed externally while iterating.
8279 break;
8282 return oldUsageInfo;
8283 })));
8286 void QuotaUsageRequestBase::SendResults() {
8287 AssertIsOnOwningThread();
8289 if (IsActorDestroyed()) {
8290 if (NS_SUCCEEDED(mResultCode)) {
8291 mResultCode = NS_ERROR_FAILURE;
8293 } else {
8294 if (mCanceled) {
8295 mResultCode = NS_ERROR_FAILURE;
8298 UsageRequestResponse response;
8300 if (NS_SUCCEEDED(mResultCode)) {
8301 GetResponse(response);
8302 } else {
8303 response = mResultCode;
8306 Unused << PQuotaUsageRequestParent::Send__delete__(this, response);
8310 void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
8311 AssertIsOnOwningThread();
8313 NoteActorDestroyed();
8316 mozilla::ipc::IPCResult QuotaUsageRequestBase::RecvCancel() {
8317 AssertIsOnOwningThread();
8319 if (mCanceled.exchange(true)) {
8320 NS_WARNING("Canceled more than once?!");
8321 return IPC_FAIL_NO_REASON(this);
8324 return IPC_OK();
8327 nsresult TraverseRepositoryHelper::TraverseRepository(
8328 QuotaManager& aQuotaManager, PersistenceType aPersistenceType) {
8329 AssertIsOnIOThread();
8331 QM_TRY_INSPECT(
8332 const auto& directory,
8333 QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)));
8335 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(directory, Exists));
8337 if (!exists) {
8338 return NS_OK;
8341 QM_TRY(CollectEachFileAtomicCancelable(
8342 *directory, GetIsCanceledFlag(),
8343 [this, aPersistenceType, &aQuotaManager,
8344 persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT](
8345 const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
8346 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
8348 switch (dirEntryKind) {
8349 case nsIFileKind::ExistsAsDirectory:
8350 QM_TRY(ProcessOrigin(aQuotaManager, *originDir, persistent,
8351 aPersistenceType));
8352 break;
8354 case nsIFileKind::ExistsAsFile: {
8355 QM_TRY_INSPECT(const auto& leafName,
8356 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, originDir,
8357 GetLeafName));
8359 // Unknown files during getting usages are allowed. Just warn if we
8360 // find them.
8361 if (!IsOSMetadata(leafName)) {
8362 UNKNOWN_FILE_WARNING(leafName);
8365 break;
8368 case nsIFileKind::DoesNotExist:
8369 // Ignore files that got removed externally while iterating.
8370 break;
8373 return Ok{};
8374 }));
8376 return NS_OK;
8379 GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
8380 : mGetAll(aParams.get_AllUsageParams().getAll()) {
8381 AssertIsOnOwningThread();
8382 MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
8385 void GetUsageOp::ProcessOriginInternal(QuotaManager* aQuotaManager,
8386 const PersistenceType aPersistenceType,
8387 const nsACString& aOrigin,
8388 const int64_t aTimestamp,
8389 const bool aPersisted,
8390 const uint64_t aUsage) {
8391 if (!mGetAll && aQuotaManager->IsOriginInternal(aOrigin)) {
8392 return;
8395 // We can't store pointers to OriginUsage objects in the hashtable
8396 // since AppendElement() reallocates its internal array buffer as number
8397 // of elements grows.
8398 const auto& originUsage =
8399 mOriginUsagesIndex.WithEntryHandle(aOrigin, [&](auto&& entry) {
8400 if (entry) {
8401 return WrapNotNullUnchecked(&mOriginUsages[entry.Data()]);
8404 entry.Insert(mOriginUsages.Length());
8406 return mOriginUsages.EmplaceBack(nsCString{aOrigin}, false, 0, 0);
8409 if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
8410 originUsage->persisted() = aPersisted;
8413 originUsage->usage() = originUsage->usage() + aUsage;
8415 originUsage->lastAccessed() =
8416 std::max<int64_t>(originUsage->lastAccessed(), aTimestamp);
8419 const Atomic<bool>& GetUsageOp::GetIsCanceledFlag() {
8420 AssertIsOnIOThread();
8422 return mCanceled;
8425 // XXX Remove aPersistent
8426 // XXX Remove aPersistenceType once GetUsageForOrigin uses the persistence
8427 // type from OriginMetadata
8428 nsresult GetUsageOp::ProcessOrigin(QuotaManager& aQuotaManager,
8429 nsIFile& aOriginDir, const bool aPersistent,
8430 const PersistenceType aPersistenceType) {
8431 AssertIsOnIOThread();
8433 QM_TRY_INSPECT(const auto& metadata,
8434 aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir));
8436 QM_TRY_INSPECT(const auto& usageInfo,
8437 GetUsageForOrigin(aQuotaManager, aPersistenceType, metadata));
8439 ProcessOriginInternal(&aQuotaManager, aPersistenceType, metadata.mOrigin,
8440 metadata.mLastAccessTime, metadata.mPersisted,
8441 usageInfo.TotalUsage().valueOr(0));
8443 return NS_OK;
8446 nsresult GetUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8447 AssertIsOnIOThread();
8448 aQuotaManager.AssertStorageIsInitialized();
8450 AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
8452 nsresult rv;
8454 for (const PersistenceType type : kAllPersistenceTypes) {
8455 rv = TraverseRepository(aQuotaManager, type);
8456 if (NS_WARN_IF(NS_FAILED(rv))) {
8457 return rv;
8461 // TraverseRepository above only consulted the filesystem. We also need to
8462 // consider origins which may have pending quota usage, such as buffered
8463 // LocalStorage writes for an origin which didn't previously have any
8464 // LocalStorage data.
8466 aQuotaManager.CollectPendingOriginsForListing(
8467 [this, &aQuotaManager](const auto& originInfo) {
8468 ProcessOriginInternal(
8469 &aQuotaManager, originInfo->GetGroupInfo()->GetPersistenceType(),
8470 originInfo->Origin(), originInfo->LockedAccessTime(),
8471 originInfo->LockedPersisted(), originInfo->LockedUsage());
8474 return NS_OK;
8477 void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
8478 AssertIsOnOwningThread();
8480 aResponse = AllUsageResponse();
8482 aResponse.get_AllUsageResponse().originUsages() = std::move(mOriginUsages);
8485 GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
8486 : mUsage(0), mFileUsage(0) {
8487 AssertIsOnOwningThread();
8488 MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
8490 const OriginUsageParams& params = aParams.get_OriginUsageParams();
8492 PrincipalMetadata principalMetadata =
8493 QuotaManager::GetInfoFromValidatedPrincipalInfo(params.principalInfo());
8495 mSuffix = std::move(principalMetadata.mSuffix);
8496 mGroup = std::move(principalMetadata.mGroup);
8497 mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
8499 mFromMemory = params.fromMemory();
8501 // Overwrite NormalOriginOperationBase default values.
8502 if (mFromMemory) {
8503 mNeedsDirectoryLocking = false;
8506 // Overwrite OriginOperationBase default values.
8507 mNeedsQuotaManagerInit = true;
8508 mNeedsStorageInit = true;
8511 nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8512 AssertIsOnIOThread();
8513 aQuotaManager.AssertStorageIsInitialized();
8514 MOZ_ASSERT(mUsage == 0);
8515 MOZ_ASSERT(mFileUsage == 0);
8517 AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
8519 if (mFromMemory) {
8520 const PrincipalMetadata principalMetadata = {
8521 mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}};
8523 // Ensure temporary storage is initialized. If temporary storage hasn't been
8524 // initialized yet, the method will initialize it by traversing the
8525 // repositories for temporary and default storage (including our origin).
8526 QM_TRY(aQuotaManager.EnsureTemporaryStorageIsInitialized());
8528 // Get cached usage (the method doesn't have to stat any files). File usage
8529 // is not tracked in memory separately, so just add to the total usage.
8530 mUsage = aQuotaManager.GetOriginUsage(principalMetadata);
8532 return NS_OK;
8535 UsageInfo usageInfo;
8537 // Add all the persistent/temporary/default storage files we care about.
8538 for (const PersistenceType type : kAllPersistenceTypes) {
8539 const OriginMetadata originMetadata = {
8540 mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}, type};
8542 auto usageInfoOrErr =
8543 GetUsageForOrigin(aQuotaManager, type, originMetadata);
8544 if (NS_WARN_IF(usageInfoOrErr.isErr())) {
8545 return usageInfoOrErr.unwrapErr();
8548 usageInfo += usageInfoOrErr.unwrap();
8551 mUsage = usageInfo.TotalUsage().valueOr(0);
8552 mFileUsage = usageInfo.FileUsage().valueOr(0);
8554 return NS_OK;
8557 void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) {
8558 AssertIsOnOwningThread();
8560 OriginUsageResponse usageResponse;
8562 usageResponse.usage() = mUsage;
8563 usageResponse.fileUsage() = mFileUsage;
8565 aResponse = usageResponse;
8568 void QuotaRequestBase::Init(Quota& aQuota) {
8569 AssertIsOnOwningThread();
8571 mNeedsQuotaManagerInit = true;
8572 mNeedsStorageInit = true;
8575 void QuotaRequestBase::SendResults() {
8576 AssertIsOnOwningThread();
8578 if (IsActorDestroyed()) {
8579 if (NS_SUCCEEDED(mResultCode)) {
8580 mResultCode = NS_ERROR_FAILURE;
8582 } else {
8583 RequestResponse response;
8585 if (NS_SUCCEEDED(mResultCode)) {
8586 GetResponse(response);
8587 } else {
8588 response = mResultCode;
8591 Unused << PQuotaRequestParent::Send__delete__(this, response);
8595 void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
8596 AssertIsOnOwningThread();
8598 NoteActorDestroyed();
8601 StorageNameOp::StorageNameOp() : QuotaRequestBase(/* aExclusive */ false) {
8602 AssertIsOnOwningThread();
8604 // Overwrite NormalOriginOperationBase default values.
8605 mNeedsDirectoryLocking = false;
8607 // Overwrite OriginOperationBase default values.
8608 mNeedsQuotaManagerInit = true;
8609 mNeedsStorageInit = false;
8612 void StorageNameOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8614 nsresult StorageNameOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8615 AssertIsOnIOThread();
8617 AUTO_PROFILER_LABEL("StorageNameOp::DoDirectoryWork", OTHER);
8619 mName = aQuotaManager.GetStorageName();
8621 return NS_OK;
8624 void StorageNameOp::GetResponse(RequestResponse& aResponse) {
8625 AssertIsOnOwningThread();
8627 StorageNameResponse storageNameResponse;
8629 storageNameResponse.name() = mName;
8631 aResponse = storageNameResponse;
8634 InitializedRequestBase::InitializedRequestBase()
8635 : QuotaRequestBase(/* aExclusive */ false), mInitialized(false) {
8636 AssertIsOnOwningThread();
8638 // Overwrite NormalOriginOperationBase default values.
8639 mNeedsDirectoryLocking = false;
8641 // Overwrite OriginOperationBase default values.
8642 mNeedsQuotaManagerInit = true;
8643 mNeedsStorageInit = false;
8646 void InitializedRequestBase::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8648 nsresult StorageInitializedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8649 AssertIsOnIOThread();
8651 AUTO_PROFILER_LABEL("StorageInitializedOp::DoDirectoryWork", OTHER);
8653 mInitialized = aQuotaManager.IsStorageInitialized();
8655 return NS_OK;
8658 void StorageInitializedOp::GetResponse(RequestResponse& aResponse) {
8659 AssertIsOnOwningThread();
8661 StorageInitializedResponse storageInitializedResponse;
8663 storageInitializedResponse.initialized() = mInitialized;
8665 aResponse = storageInitializedResponse;
8668 nsresult TemporaryStorageInitializedOp::DoDirectoryWork(
8669 QuotaManager& aQuotaManager) {
8670 AssertIsOnIOThread();
8672 AUTO_PROFILER_LABEL("TemporaryStorageInitializedOp::DoDirectoryWork", OTHER);
8674 mInitialized = aQuotaManager.IsTemporaryStorageInitialized();
8676 return NS_OK;
8679 void TemporaryStorageInitializedOp::GetResponse(RequestResponse& aResponse) {
8680 AssertIsOnOwningThread();
8682 TemporaryStorageInitializedResponse temporaryStorageInitializedResponse;
8684 temporaryStorageInitializedResponse.initialized() = mInitialized;
8686 aResponse = temporaryStorageInitializedResponse;
8689 InitOp::InitOp() : QuotaRequestBase(/* aExclusive */ false) {
8690 AssertIsOnOwningThread();
8692 // Overwrite OriginOperationBase default values.
8693 mNeedsQuotaManagerInit = true;
8694 mNeedsStorageInit = false;
8697 void InitOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8699 nsresult InitOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8700 AssertIsOnIOThread();
8702 AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER);
8704 QM_TRY(aQuotaManager.EnsureStorageIsInitialized());
8706 return NS_OK;
8709 void InitOp::GetResponse(RequestResponse& aResponse) {
8710 AssertIsOnOwningThread();
8712 aResponse = InitResponse();
8715 InitTemporaryStorageOp::InitTemporaryStorageOp()
8716 : QuotaRequestBase(/* aExclusive */ false) {
8717 AssertIsOnOwningThread();
8719 // Overwrite OriginOperationBase default values.
8720 mNeedsQuotaManagerInit = true;
8721 mNeedsStorageInit = false;
8724 void InitTemporaryStorageOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8726 nsresult InitTemporaryStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8727 AssertIsOnIOThread();
8729 AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER);
8731 QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE;);
8733 QM_TRY(aQuotaManager.EnsureTemporaryStorageIsInitialized());
8735 return NS_OK;
8738 void InitTemporaryStorageOp::GetResponse(RequestResponse& aResponse) {
8739 AssertIsOnOwningThread();
8741 aResponse = InitTemporaryStorageResponse();
8744 InitializeOriginRequestBase::InitializeOriginRequestBase(
8745 const PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo)
8746 : QuotaRequestBase(/* aExclusive */ false), mCreated(false) {
8747 AssertIsOnOwningThread();
8749 auto principalMetadata =
8750 QuotaManager::GetInfoFromValidatedPrincipalInfo(aPrincipalInfo);
8752 // Overwrite OriginOperationBase default values.
8753 mNeedsQuotaManagerInit = true;
8754 mNeedsStorageInit = false;
8756 // Overwrite NormalOriginOperationBase default values.
8757 mPersistenceType.SetValue(aPersistenceType);
8758 mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
8760 // Overwrite InitializeOriginRequestBase default values.
8761 mSuffix = std::move(principalMetadata.mSuffix);
8762 mGroup = std::move(principalMetadata.mGroup);
8765 void InitializeOriginRequestBase::Init(Quota& aQuota) {
8766 AssertIsOnOwningThread();
8769 InitializePersistentOriginOp::InitializePersistentOriginOp(
8770 const RequestParams& aParams)
8771 : InitializeOriginRequestBase(
8772 PERSISTENCE_TYPE_PERSISTENT,
8773 aParams.get_InitializePersistentOriginParams().principalInfo()) {
8774 AssertIsOnOwningThread();
8775 MOZ_ASSERT(aParams.type() ==
8776 RequestParams::TInitializePersistentOriginParams);
8779 nsresult InitializePersistentOriginOp::DoDirectoryWork(
8780 QuotaManager& aQuotaManager) {
8781 AssertIsOnIOThread();
8782 MOZ_ASSERT(!mPersistenceType.IsNull());
8784 AUTO_PROFILER_LABEL("InitializePersistentOriginOp::DoDirectoryWork", OTHER);
8786 QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
8788 QM_TRY_UNWRAP(mCreated,
8789 (aQuotaManager
8790 .EnsurePersistentOriginIsInitialized(OriginMetadata{
8791 mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
8792 PERSISTENCE_TYPE_PERSISTENT})
8793 .map([](const auto& res) { return res.second; })));
8795 return NS_OK;
8798 void InitializePersistentOriginOp::GetResponse(RequestResponse& aResponse) {
8799 AssertIsOnOwningThread();
8801 aResponse = InitializePersistentOriginResponse(mCreated);
8804 InitializeTemporaryOriginOp::InitializeTemporaryOriginOp(
8805 const RequestParams& aParams)
8806 : InitializeOriginRequestBase(
8807 aParams.get_InitializeTemporaryOriginParams().persistenceType(),
8808 aParams.get_InitializeTemporaryOriginParams().principalInfo()) {
8809 AssertIsOnOwningThread();
8810 MOZ_ASSERT(aParams.type() == RequestParams::TInitializeTemporaryOriginParams);
8813 nsresult InitializeTemporaryOriginOp::DoDirectoryWork(
8814 QuotaManager& aQuotaManager) {
8815 AssertIsOnIOThread();
8816 MOZ_ASSERT(!mPersistenceType.IsNull());
8818 AUTO_PROFILER_LABEL("InitializeTemporaryOriginOp::DoDirectoryWork", OTHER);
8820 QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
8822 QM_TRY(OkIf(aQuotaManager.IsTemporaryStorageInitialized()), NS_ERROR_FAILURE);
8824 QM_TRY_UNWRAP(mCreated,
8825 (aQuotaManager
8826 .EnsureTemporaryOriginIsInitialized(
8827 mPersistenceType.Value(),
8828 OriginMetadata{mSuffix, mGroup,
8829 nsCString{mOriginScope.GetOrigin()},
8830 mPersistenceType.Value()})
8831 .map([](const auto& res) { return res.second; })));
8833 return NS_OK;
8836 void InitializeTemporaryOriginOp::GetResponse(RequestResponse& aResponse) {
8837 AssertIsOnOwningThread();
8839 aResponse = InitializeTemporaryOriginResponse(mCreated);
8842 ResetOrClearOp::ResetOrClearOp(bool aClear)
8843 : QuotaRequestBase(/* aExclusive */ true), mClear(aClear) {
8844 AssertIsOnOwningThread();
8846 // Overwrite OriginOperationBase default values.
8847 mNeedsQuotaManagerInit = true;
8848 mNeedsStorageInit = false;
8851 void ResetOrClearOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8853 void ResetOrClearOp::DeleteFiles(QuotaManager& aQuotaManager) {
8854 AssertIsOnIOThread();
8856 nsresult rv = aQuotaManager.AboutToClearOrigins(Nullable<PersistenceType>(),
8857 OriginScope::FromNull(),
8858 Nullable<Client::Type>());
8859 if (NS_WARN_IF(NS_FAILED(rv))) {
8860 return;
8863 auto directoryOrErr = QM_NewLocalFile(aQuotaManager.GetStoragePath());
8864 if (NS_WARN_IF(directoryOrErr.isErr())) {
8865 return;
8868 nsCOMPtr<nsIFile> directory = directoryOrErr.unwrap();
8870 rv = directory->Remove(true);
8871 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
8872 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
8873 // This should never fail if we've closed all storage connections
8874 // correctly...
8875 MOZ_ASSERT(false, "Failed to remove storage directory!");
8879 void ResetOrClearOp::DeleteStorageFile(QuotaManager& aQuotaManager) {
8880 AssertIsOnIOThread();
8882 QM_TRY_INSPECT(const auto& storageFile,
8883 QM_NewLocalFile(aQuotaManager.GetBasePath()), QM_VOID);
8885 QM_TRY(storageFile->Append(aQuotaManager.GetStorageName() + kSQLiteSuffix),
8886 QM_VOID);
8888 const nsresult rv = storageFile->Remove(true);
8889 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
8890 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
8891 // This should never fail if we've closed the storage connection
8892 // correctly...
8893 MOZ_ASSERT(false, "Failed to remove storage file!");
8897 nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8898 AssertIsOnIOThread();
8900 AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER);
8902 if (mClear) {
8903 DeleteFiles(aQuotaManager);
8905 aQuotaManager.RemoveQuota();
8908 aQuotaManager.ShutdownStorage();
8910 if (mClear) {
8911 DeleteStorageFile(aQuotaManager);
8914 return NS_OK;
8917 void ResetOrClearOp::GetResponse(RequestResponse& aResponse) {
8918 AssertIsOnOwningThread();
8919 if (mClear) {
8920 aResponse = ClearAllResponse();
8921 } else {
8922 aResponse = ResetAllResponse();
8926 void ClearRequestBase::DeleteFiles(QuotaManager& aQuotaManager,
8927 PersistenceType aPersistenceType) {
8928 AssertIsOnIOThread();
8930 QM_TRY(aQuotaManager.AboutToClearOrigins(
8931 Nullable<PersistenceType>(aPersistenceType), mOriginScope,
8932 mClientType),
8933 QM_VOID);
8935 QM_TRY_INSPECT(
8936 const auto& directory,
8937 QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)), QM_VOID);
8939 nsTArray<nsCOMPtr<nsIFile>> directoriesForRemovalRetry;
8941 aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
8942 "ClearRequestBase: Starting deleting files"_ns);
8944 QM_TRY(CollectEachFile(
8945 *directory,
8946 [originScope =
8947 [this] {
8948 OriginScope originScope = mOriginScope.Clone();
8949 if (originScope.IsOrigin()) {
8950 originScope.SetOrigin(
8951 MakeSanitizedOriginCString(originScope.GetOrigin()));
8952 } else if (originScope.IsPrefix()) {
8953 originScope.SetOriginNoSuffix(MakeSanitizedOriginCString(
8954 originScope.GetOriginNoSuffix()));
8956 return originScope;
8957 }(),
8958 aPersistenceType, &aQuotaManager, &directoriesForRemovalRetry,
8959 this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
8960 QM_TRY_INSPECT(
8961 const auto& leafName,
8962 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
8964 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
8966 switch (dirEntryKind) {
8967 case nsIFileKind::ExistsAsDirectory: {
8968 // Skip the origin directory if it doesn't match the pattern.
8969 if (!originScope.Matches(OriginScope::FromOrigin(
8970 NS_ConvertUTF16toUTF8(leafName)))) {
8971 break;
8974 QM_TRY_INSPECT(
8975 const auto& metadata,
8976 aQuotaManager.LoadFullOriginMetadataWithRestore(file));
8978 MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
8980 if (!mClientType.IsNull()) {
8981 nsAutoString clientDirectoryName;
8982 QM_TRY(OkIf(Client::TypeToText(mClientType.Value(),
8983 clientDirectoryName,
8984 fallible)),
8985 Err(NS_ERROR_FAILURE));
8987 QM_TRY(file->Append(clientDirectoryName));
8989 QM_TRY_INSPECT(const bool& exists,
8990 MOZ_TO_RESULT_INVOKE(file, Exists));
8992 if (!exists) {
8993 break;
8997 // We can't guarantee that this will always succeed on
8998 // Windows...
8999 QM_WARNONLY_TRY(file->Remove(true), [&](const auto&) {
9000 directoriesForRemovalRetry.AppendElement(std::move(file));
9003 const bool initialized =
9004 aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
9005 ? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
9006 : aQuotaManager.IsTemporaryStorageInitialized();
9008 // If it hasn't been initialized, we don't need to update the
9009 // quota and notify the removing client.
9010 if (!initialized) {
9011 break;
9014 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
9015 if (mClientType.IsNull()) {
9016 aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
9017 metadata);
9018 } else {
9019 aQuotaManager.ResetUsageForClient(
9020 ClientMetadata{metadata, mClientType.Value()});
9024 aQuotaManager.OriginClearCompleted(
9025 aPersistenceType, metadata.mOrigin, mClientType);
9027 break;
9030 case nsIFileKind::ExistsAsFile:
9031 // Unknown files during clearing are allowed. Just warn if we
9032 // find them.
9033 if (!IsOSMetadata(leafName)) {
9034 UNKNOWN_FILE_WARNING(leafName);
9037 break;
9039 case nsIFileKind::DoesNotExist:
9040 // Ignore files that got removed externally while iterating.
9041 break;
9044 return Ok{};
9046 QM_VOID);
9048 // Retry removing any directories that failed to be removed earlier now.
9050 // XXX This will still block this operation. We might instead dispatch a
9051 // runnable to our own thread for each retry round with a timer. We must
9052 // ensure that the directory lock is upheld until we complete or give up
9053 // though.
9054 for (uint32_t index = 0; index < 10; index++) {
9055 aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
9056 "ClearRequestBase: Retrying directory removal"_ns);
9058 for (auto&& file : std::exchange(directoriesForRemovalRetry,
9059 nsTArray<nsCOMPtr<nsIFile>>{})) {
9060 if (NS_FAILED((file->Remove(true)))) {
9061 directoriesForRemovalRetry.AppendElement(std::move(file));
9065 if (directoriesForRemovalRetry.IsEmpty()) {
9066 break;
9069 PR_Sleep(PR_MillisecondsToInterval(200));
9072 if (!directoriesForRemovalRetry.IsEmpty()) {
9073 NS_WARNING("Failed to remove one or more directories, giving up!");
9076 aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
9077 "ClearRequestBase: Completed deleting files"_ns);
9080 nsresult ClearRequestBase::DoDirectoryWork(QuotaManager& aQuotaManager) {
9081 AssertIsOnIOThread();
9082 aQuotaManager.AssertStorageIsInitialized();
9084 AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
9086 if (mPersistenceType.IsNull()) {
9087 for (const PersistenceType type : kAllPersistenceTypes) {
9088 DeleteFiles(aQuotaManager, type);
9090 } else {
9091 DeleteFiles(aQuotaManager, mPersistenceType.Value());
9094 return NS_OK;
9097 ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
9098 : ClearRequestBase(/* aExclusive */ true),
9099 mParams(aParams.get_ClearOriginParams().commonParams()),
9100 mMatchAll(aParams.get_ClearOriginParams().matchAll()) {
9101 MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams);
9104 void ClearOriginOp::Init(Quota& aQuota) {
9105 AssertIsOnOwningThread();
9107 QuotaRequestBase::Init(aQuota);
9109 if (mParams.persistenceTypeIsExplicit()) {
9110 mPersistenceType.SetValue(mParams.persistenceType());
9113 // Figure out which origin we're dealing with.
9114 const auto origin = QuotaManager::GetOriginFromValidatedPrincipalInfo(
9115 mParams.principalInfo());
9117 if (mMatchAll) {
9118 mOriginScope.SetFromPrefix(origin);
9119 } else {
9120 mOriginScope.SetFromOrigin(origin);
9123 if (mParams.clientTypeIsExplicit()) {
9124 mClientType.SetValue(mParams.clientType());
9128 void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
9129 AssertIsOnOwningThread();
9131 aResponse = ClearOriginResponse();
9134 ClearDataOp::ClearDataOp(const RequestParams& aParams)
9135 : ClearRequestBase(/* aExclusive */ true), mParams(aParams) {
9136 MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
9139 void ClearDataOp::Init(Quota& aQuota) {
9140 AssertIsOnOwningThread();
9142 QuotaRequestBase::Init(aQuota);
9144 mOriginScope.SetFromPattern(mParams.pattern());
9147 void ClearDataOp::GetResponse(RequestResponse& aResponse) {
9148 AssertIsOnOwningThread();
9150 aResponse = ClearDataResponse();
9153 ResetOriginOp::ResetOriginOp(const RequestParams& aParams)
9154 : QuotaRequestBase(/* aExclusive */ true) {
9155 AssertIsOnOwningThread();
9156 MOZ_ASSERT(aParams.type() == RequestParams::TResetOriginParams);
9158 const ClearResetOriginParams& params =
9159 aParams.get_ResetOriginParams().commonParams();
9161 const auto origin =
9162 QuotaManager::GetOriginFromValidatedPrincipalInfo(params.principalInfo());
9164 // Overwrite OriginOperationBase default values.
9165 mNeedsQuotaManagerInit = true;
9166 mNeedsStorageInit = false;
9168 // Overwrite NormalOriginOperationBase default values.
9169 if (params.persistenceTypeIsExplicit()) {
9170 mPersistenceType.SetValue(params.persistenceType());
9173 mOriginScope.SetFromOrigin(origin);
9175 if (params.clientTypeIsExplicit()) {
9176 mClientType.SetValue(params.clientType());
9180 void ResetOriginOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
9182 nsresult ResetOriginOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9183 AssertIsOnIOThread();
9185 AUTO_PROFILER_LABEL("ResetOriginOp::DoDirectoryWork", OTHER);
9187 // All the work is handled by NormalOriginOperationBase parent class. In this
9188 // particular case, we just needed to acquire an exclusive directory lock and
9189 // that's it.
9191 return NS_OK;
9194 void ResetOriginOp::GetResponse(RequestResponse& aResponse) {
9195 AssertIsOnOwningThread();
9197 aResponse = ResetOriginResponse();
9200 PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
9201 : QuotaRequestBase(/* aExclusive */ false), mPrincipalInfo(aPrincipalInfo) {
9202 AssertIsOnOwningThread();
9205 void PersistRequestBase::Init(Quota& aQuota) {
9206 AssertIsOnOwningThread();
9208 QuotaRequestBase::Init(aQuota);
9210 mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
9212 // Figure out which origin we're dealing with.
9213 PrincipalMetadata principalMetadata =
9214 QuotaManager::GetInfoFromValidatedPrincipalInfo(mPrincipalInfo);
9216 mSuffix = std::move(principalMetadata.mSuffix);
9217 mGroup = std::move(principalMetadata.mGroup);
9218 mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
9221 PersistedOp::PersistedOp(const RequestParams& aParams)
9222 : PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
9223 mPersisted(false) {
9224 MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
9227 nsresult PersistedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9228 AssertIsOnIOThread();
9229 aQuotaManager.AssertStorageIsInitialized();
9230 MOZ_ASSERT(!mPersistenceType.IsNull());
9231 MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
9232 MOZ_ASSERT(mOriginScope.IsOrigin());
9234 AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER);
9236 Nullable<bool> persisted = aQuotaManager.OriginPersisted(
9237 OriginMetadata{mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
9238 mPersistenceType.Value()});
9240 if (!persisted.IsNull()) {
9241 mPersisted = persisted.Value();
9242 return NS_OK;
9245 // If we get here, it means the origin hasn't been initialized yet.
9246 // Try to get the persisted flag from directory metadata on disk.
9248 QM_TRY_INSPECT(const auto& directory,
9249 aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
9250 mOriginScope.GetOrigin()));
9252 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(directory, Exists));
9254 if (exists) {
9255 // Get the metadata. We only use the persisted flag.
9256 QM_TRY_INSPECT(const auto& metadata,
9257 aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
9259 mPersisted = metadata.mPersisted;
9260 } else {
9261 // The directory has not been created yet.
9262 mPersisted = false;
9265 return NS_OK;
9268 void PersistedOp::GetResponse(RequestResponse& aResponse) {
9269 AssertIsOnOwningThread();
9271 PersistedResponse persistedResponse;
9272 persistedResponse.persisted() = mPersisted;
9274 aResponse = persistedResponse;
9277 PersistOp::PersistOp(const RequestParams& aParams)
9278 : PersistRequestBase(aParams.get_PersistParams().principalInfo()) {
9279 MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
9282 nsresult PersistOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9283 AssertIsOnIOThread();
9284 aQuotaManager.AssertStorageIsInitialized();
9285 MOZ_ASSERT(!mPersistenceType.IsNull());
9286 MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
9287 MOZ_ASSERT(mOriginScope.IsOrigin());
9289 const OriginMetadata originMetadata = {mSuffix, mGroup,
9290 nsCString{mOriginScope.GetOrigin()},
9291 mPersistenceType.Value()};
9293 AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER);
9295 // Update directory metadata on disk first. Then, create/update the originInfo
9296 // if needed.
9297 QM_TRY_INSPECT(const auto& directory,
9298 aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
9299 originMetadata.mOrigin));
9301 QM_TRY_INSPECT(const bool& created,
9302 aQuotaManager.EnsureOriginDirectory(*directory));
9304 if (created) {
9305 int64_t timestamp;
9307 // Origin directory has been successfully created.
9308 // Create OriginInfo too if temporary storage was already initialized.
9309 if (aQuotaManager.IsTemporaryStorageInitialized()) {
9310 timestamp = aQuotaManager.NoteOriginDirectoryCreated(
9311 originMetadata, /* aPersisted */ true);
9312 } else {
9313 timestamp = PR_Now();
9316 QM_TRY(CreateDirectoryMetadata2(*directory, timestamp,
9317 /* aPersisted */ true, originMetadata));
9318 } else {
9319 // Get the metadata (restore the metadata file if necessary). We only use
9320 // the persisted flag.
9321 QM_TRY_INSPECT(const auto& metadata,
9322 aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
9324 if (!metadata.mPersisted) {
9325 QM_TRY_INSPECT(const auto& file,
9326 CloneFileAndAppend(
9327 *directory, nsLiteralString(METADATA_V2_FILE_NAME)));
9329 QM_TRY_INSPECT(const auto& stream,
9330 GetBinaryOutputStream(*file, FileFlag::Update));
9332 MOZ_ASSERT(stream);
9334 // Update origin access time while we are here.
9335 QM_TRY(stream->Write64(PR_Now()));
9337 // Set the persisted flag to true.
9338 QM_TRY(stream->WriteBoolean(true));
9341 // Directory metadata has been successfully updated.
9342 // Update OriginInfo too if temporary storage was already initialized.
9343 if (aQuotaManager.IsTemporaryStorageInitialized()) {
9344 aQuotaManager.PersistOrigin(originMetadata);
9348 return NS_OK;
9351 void PersistOp::GetResponse(RequestResponse& aResponse) {
9352 AssertIsOnOwningThread();
9354 aResponse = PersistResponse();
9357 EstimateOp::EstimateOp(const RequestParams& aParams)
9358 : QuotaRequestBase(/* aExclusive */ false), mUsage(0), mLimit(0) {
9359 AssertIsOnOwningThread();
9360 MOZ_ASSERT(aParams.type() == RequestParams::TEstimateParams);
9362 // XXX We don't use the quota info components other than the group here.
9363 mGroup = std::move(QuotaManager::GetInfoFromValidatedPrincipalInfo(
9364 aParams.get_EstimateParams().principalInfo())
9365 .mGroup);
9367 // Overwrite NormalOriginOperationBase default values.
9368 mNeedsDirectoryLocking = false;
9370 // Overwrite OriginOperationBase default values.
9371 mNeedsQuotaManagerInit = true;
9372 mNeedsStorageInit = true;
9375 nsresult EstimateOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9376 AssertIsOnIOThread();
9377 aQuotaManager.AssertStorageIsInitialized();
9379 AUTO_PROFILER_LABEL("EstimateOp::DoDirectoryWork", OTHER);
9381 // Ensure temporary storage is initialized. If temporary storage hasn't been
9382 // initialized yet, the method will initialize it by traversing the
9383 // repositories for temporary and default storage (including origins belonging
9384 // to our group).
9385 QM_TRY(aQuotaManager.EnsureTemporaryStorageIsInitialized());
9387 // Get cached usage (the method doesn't have to stat any files).
9388 mUsage = aQuotaManager.GetGroupUsage(mGroup);
9390 mLimit = aQuotaManager.GetGroupLimit();
9392 return NS_OK;
9395 void EstimateOp::GetResponse(RequestResponse& aResponse) {
9396 AssertIsOnOwningThread();
9398 EstimateResponse estimateResponse;
9400 estimateResponse.usage() = mUsage;
9401 estimateResponse.limit() = mLimit;
9403 aResponse = estimateResponse;
9406 ListOriginsOp::ListOriginsOp()
9407 : QuotaRequestBase(/* aExclusive */ false), TraverseRepositoryHelper() {
9408 AssertIsOnOwningThread();
9411 void ListOriginsOp::Init(Quota& aQuota) {
9412 AssertIsOnOwningThread();
9414 mNeedsQuotaManagerInit = true;
9415 mNeedsStorageInit = true;
9418 nsresult ListOriginsOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9419 AssertIsOnIOThread();
9420 aQuotaManager.AssertStorageIsInitialized();
9422 AUTO_PROFILER_LABEL("ListOriginsOp::DoDirectoryWork", OTHER);
9424 for (const PersistenceType type : kAllPersistenceTypes) {
9425 QM_TRY(TraverseRepository(aQuotaManager, type));
9428 // TraverseRepository above only consulted the file-system to get a list of
9429 // known origins, but we also need to include origins that have pending quota
9430 // usage.
9432 aQuotaManager.CollectPendingOriginsForListing([this](const auto& originInfo) {
9433 mOrigins.AppendElement(originInfo->Origin());
9436 return NS_OK;
9439 const Atomic<bool>& ListOriginsOp::GetIsCanceledFlag() {
9440 AssertIsOnIOThread();
9442 return mCanceled;
9445 nsresult ListOriginsOp::ProcessOrigin(QuotaManager& aQuotaManager,
9446 nsIFile& aOriginDir,
9447 const bool aPersistent,
9448 const PersistenceType aPersistenceType) {
9449 AssertIsOnIOThread();
9451 // XXX We only use metadata.mOriginMetadata.mOrigin...
9452 QM_TRY_UNWRAP(auto metadata,
9453 aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir));
9455 if (aQuotaManager.IsOriginInternal(metadata.mOrigin)) {
9456 return NS_OK;
9459 mOrigins.AppendElement(std::move(metadata.mOrigin));
9461 return NS_OK;
9464 void ListOriginsOp::GetResponse(RequestResponse& aResponse) {
9465 AssertIsOnOwningThread();
9467 aResponse = ListOriginsResponse();
9468 if (mOrigins.IsEmpty()) {
9469 return;
9472 nsTArray<nsCString>& origins = aResponse.get_ListOriginsResponse().origins();
9473 mOrigins.SwapElements(origins);
9476 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9478 // static
9479 already_AddRefed<PrincipalVerifier> PrincipalVerifier::CreateAndDispatch(
9480 nsTArray<PrincipalInfo>&& aPrincipalInfos) {
9481 AssertIsOnIOThread();
9483 RefPtr<PrincipalVerifier> verifier =
9484 new PrincipalVerifier(std::move(aPrincipalInfos));
9486 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(verifier));
9488 return verifier.forget();
9491 Result<Ok, nsCString> PrincipalVerifier::CheckPrincipalInfoValidity(
9492 const PrincipalInfo& aPrincipalInfo) {
9493 MOZ_ASSERT(NS_IsMainThread());
9495 switch (aPrincipalInfo.type()) {
9496 // A system principal is acceptable.
9497 case PrincipalInfo::TSystemPrincipalInfo: {
9498 return Ok{};
9501 case PrincipalInfo::TContentPrincipalInfo: {
9502 const ContentPrincipalInfo& info =
9503 aPrincipalInfo.get_ContentPrincipalInfo();
9505 nsCOMPtr<nsIURI> uri;
9506 nsresult rv = NS_NewURI(getter_AddRefs(uri), info.spec());
9507 if (NS_WARN_IF(NS_FAILED(rv))) {
9508 return Err("NS_NewURI failed"_ns);
9511 nsCOMPtr<nsIPrincipal> principal =
9512 BasePrincipal::CreateContentPrincipal(uri, info.attrs());
9513 if (NS_WARN_IF(!principal)) {
9514 return Err("CreateContentPrincipal failed"_ns);
9517 nsCString originNoSuffix;
9518 rv = principal->GetOriginNoSuffix(originNoSuffix);
9519 if (NS_WARN_IF(NS_FAILED(rv))) {
9520 return Err("GetOriginNoSuffix failed"_ns);
9523 if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
9524 static const char messageTemplate[] =
9525 "originNoSuffix (%s) doesn't match passed one (%s)!";
9527 QM_WARNING(messageTemplate, originNoSuffix.get(),
9528 info.originNoSuffix().get());
9530 return Err(nsPrintfCString(
9531 messageTemplate, AnonymizedOriginString(originNoSuffix).get(),
9532 AnonymizedOriginString(info.originNoSuffix()).get()));
9535 nsCString baseDomain;
9536 rv = principal->GetBaseDomain(baseDomain);
9537 if (NS_WARN_IF(NS_FAILED(rv))) {
9538 return Err("GetBaseDomain failed"_ns);
9541 if (NS_WARN_IF(baseDomain != info.baseDomain())) {
9542 static const char messageTemplate[] =
9543 "baseDomain (%s) doesn't match passed one (%s)!";
9545 QM_WARNING(messageTemplate, baseDomain.get(), info.baseDomain().get());
9547 return Err(nsPrintfCString(messageTemplate,
9548 AnonymizedCString(baseDomain).get(),
9549 AnonymizedCString(info.baseDomain()).get()));
9552 return Ok{};
9555 default: {
9556 break;
9560 return Err("Null and expanded principals are not acceptable"_ns);
9563 NS_IMETHODIMP
9564 PrincipalVerifier::Run() {
9565 MOZ_ASSERT(NS_IsMainThread());
9567 nsAutoCString allDetails;
9568 for (auto& principalInfo : mPrincipalInfos) {
9569 const auto res = CheckPrincipalInfoValidity(principalInfo);
9570 if (res.isErr()) {
9571 if (!allDetails.IsEmpty()) {
9572 allDetails.AppendLiteral(", ");
9575 allDetails.Append(res.inspectErr());
9579 if (!allDetails.IsEmpty()) {
9580 allDetails.Insert("Invalid principal infos found: ", 0);
9582 // In case of invalid principal infos, this will produce a crash reason such
9583 // as:
9584 // Invalid principal infos found: originNoSuffix (https://aaa.aaaaaaa.aaa)
9585 // doesn't match passed one (about:aaaa)!
9587 // In case of errors while validating a principal, it will contain a
9588 // different message describing that error, which does not contain any
9589 // details of the actual principal info at the moment.
9591 // This string will be leaked.
9592 MOZ_CRASH_UNSAFE(strdup(allDetails.BeginReading()));
9595 return NS_OK;
9598 #endif
9600 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
9601 int64_t& aTimestamp,
9602 nsACString& aGroup,
9603 nsACString& aOrigin,
9604 Nullable<bool>& aIsApp) {
9605 AssertIsOnIOThread();
9606 MOZ_ASSERT(aDirectory);
9608 QM_TRY_INSPECT(
9609 const auto& binaryStream,
9610 GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
9612 QM_TRY_INSPECT(const uint64_t& timestamp,
9613 MOZ_TO_RESULT_INVOKE(binaryStream, Read64));
9615 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_TYPED(
9616 nsCString, binaryStream, ReadCString));
9618 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_TYPED(
9619 nsCString, binaryStream, ReadCString));
9621 Nullable<bool> isApp;
9622 bool value;
9623 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
9624 isApp.SetValue(value);
9627 aTimestamp = timestamp;
9628 aGroup = group;
9629 aOrigin = origin;
9630 aIsApp = std::move(isApp);
9631 return NS_OK;
9634 nsresult StorageOperationBase::GetDirectoryMetadata2(
9635 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
9636 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
9637 AssertIsOnIOThread();
9638 MOZ_ASSERT(aDirectory);
9640 QM_TRY_INSPECT(const auto& binaryStream,
9641 GetBinaryInputStream(*aDirectory,
9642 nsLiteralString(METADATA_V2_FILE_NAME)));
9644 QM_TRY_INSPECT(const uint64_t& timestamp,
9645 MOZ_TO_RESULT_INVOKE(binaryStream, Read64));
9647 QM_TRY_INSPECT(const bool& persisted,
9648 MOZ_TO_RESULT_INVOKE(binaryStream, ReadBoolean));
9649 Unused << persisted;
9651 QM_TRY_INSPECT(const bool& reservedData1,
9652 MOZ_TO_RESULT_INVOKE(binaryStream, Read32));
9653 Unused << reservedData1;
9655 QM_TRY_INSPECT(const bool& reservedData2,
9656 MOZ_TO_RESULT_INVOKE(binaryStream, Read32));
9657 Unused << reservedData2;
9659 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_TYPED(
9660 nsCString, binaryStream, ReadCString));
9662 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_TYPED(
9663 nsCString, binaryStream, ReadCString));
9665 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_TYPED(
9666 nsCString, binaryStream, ReadCString));
9668 QM_TRY_INSPECT(const bool& isApp,
9669 MOZ_TO_RESULT_INVOKE(binaryStream, ReadBoolean));
9671 aTimestamp = timestamp;
9672 aSuffix = suffix;
9673 aGroup = group;
9674 aOrigin = origin;
9675 aIsApp = isApp;
9676 return NS_OK;
9679 int64_t StorageOperationBase::GetOriginLastModifiedTime(
9680 const OriginProps& aOriginProps) {
9681 return GetLastModifiedTime(*aOriginProps.mPersistenceType,
9682 *aOriginProps.mDirectory);
9685 nsresult StorageOperationBase::RemoveObsoleteOrigin(
9686 const OriginProps& aOriginProps) {
9687 AssertIsOnIOThread();
9689 QM_WARNING(
9690 "Deleting obsolete %s directory that is no longer a legal "
9691 "origin!",
9692 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
9694 QM_TRY(aOriginProps.mDirectory->Remove(/* recursive */ true));
9696 return NS_OK;
9699 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
9700 const OriginProps& aOriginProps) {
9701 AssertIsOnIOThread();
9703 const nsAString& oldLeafName = aOriginProps.mLeafName;
9705 const auto newLeafName =
9706 MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
9708 if (oldLeafName == newLeafName) {
9709 return false;
9712 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
9713 aOriginProps.mTimestamp,
9714 aOriginProps.mOriginMetadata));
9716 QM_TRY(CreateDirectoryMetadata2(
9717 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
9718 /* aPersisted */ false, aOriginProps.mOriginMetadata));
9720 QM_TRY_INSPECT(const auto& newFile,
9721 MOZ_TO_RESULT_INVOKE_TYPED(
9722 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
9724 QM_TRY(newFile->Append(newLeafName));
9726 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(newFile, Exists));
9728 if (exists) {
9729 QM_WARNING(
9730 "Can't rename %s directory to %s, the target already exists, removing "
9731 "instead of renaming!",
9732 NS_ConvertUTF16toUTF8(oldLeafName).get(),
9733 NS_ConvertUTF16toUTF8(newLeafName).get());
9736 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
9737 [&exists, &aOriginProps, &newLeafName] {
9738 if (exists) {
9739 QM_TRY_RETURN(aOriginProps.mDirectory->Remove(/* recursive */ true));
9741 QM_TRY_RETURN(aOriginProps.mDirectory->RenameTo(nullptr, newLeafName));
9743 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
9744 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
9746 return true;
9749 nsresult StorageOperationBase::ProcessOriginDirectories() {
9750 AssertIsOnIOThread();
9751 MOZ_ASSERT(!mOriginProps.IsEmpty());
9753 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9754 nsTArray<PrincipalInfo> principalInfos;
9755 #endif
9757 for (auto& originProps : mOriginProps) {
9758 switch (originProps.mType) {
9759 case OriginProps::eChrome: {
9760 originProps.mOriginMetadata = {QuotaManager::GetInfoForChrome(),
9761 *originProps.mPersistenceType};
9762 break;
9765 case OriginProps::eContent: {
9766 RefPtr<MozURL> specURL;
9767 nsresult rv = MozURL::Init(getter_AddRefs(specURL), originProps.mSpec);
9768 if (NS_WARN_IF(NS_FAILED(rv))) {
9769 // If a URL cannot be understood by MozURL during restoring or
9770 // upgrading, either marking the directory as broken or removing that
9771 // corresponding directory should be considered. While the cost of
9772 // marking the directory as broken during a upgrade is too high,
9773 // removing the directory is a better choice rather than blocking the
9774 // initialization or the upgrade.
9775 QM_WARNING(
9776 "A URL (%s) for the origin directory is not recognized by "
9777 "MozURL. The directory will be deleted for now to pass the "
9778 "initialization or the upgrade.",
9779 originProps.mSpec.get());
9781 originProps.mType = OriginProps::eObsolete;
9782 break;
9785 nsCString originNoSuffix;
9786 specURL->Origin(originNoSuffix);
9788 QM_TRY_INSPECT(
9789 const auto& baseDomain,
9790 MOZ_TO_RESULT_INVOKE_TYPED(nsCString, specURL, BaseDomain));
9792 ContentPrincipalInfo contentPrincipalInfo;
9793 contentPrincipalInfo.attrs() = originProps.mAttrs;
9794 contentPrincipalInfo.originNoSuffix() = originNoSuffix;
9795 contentPrincipalInfo.spec() = originProps.mSpec;
9796 contentPrincipalInfo.baseDomain() = baseDomain;
9798 PrincipalInfo principalInfo(contentPrincipalInfo);
9800 originProps.mOriginMetadata = {
9801 QuotaManager::GetInfoFromValidatedPrincipalInfo(principalInfo),
9802 *originProps.mPersistenceType};
9804 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9805 principalInfos.AppendElement(principalInfo);
9806 #endif
9808 break;
9811 case OriginProps::eObsolete: {
9812 // There's no way to get info for obsolete origins.
9813 break;
9816 default:
9817 MOZ_CRASH("Bad type!");
9821 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9822 if (!principalInfos.IsEmpty()) {
9823 RefPtr<PrincipalVerifier> principalVerifier =
9824 PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
9826 #endif
9828 // Don't try to upgrade obsolete origins, remove them right after we detect
9829 // them.
9830 for (const auto& originProps : mOriginProps) {
9831 if (originProps.mType == OriginProps::eObsolete) {
9832 MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
9833 MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
9834 MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
9836 QM_TRY(RemoveObsoleteOrigin(originProps));
9837 } else {
9838 MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
9839 MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
9841 QM_TRY(ProcessOriginDirectory(originProps));
9845 return NS_OK;
9848 // XXX Do the fallible initialization in a separate non-static member function
9849 // of StorageOperationBase and eventually get rid of this method and use a
9850 // normal constructor instead.
9851 template <typename PersistenceTypeFunc>
9852 nsresult StorageOperationBase::OriginProps::Init(
9853 PersistenceTypeFunc&& aPersistenceTypeFunc) {
9854 AssertIsOnIOThread();
9856 QM_TRY_INSPECT(
9857 const auto& leafName,
9858 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, *mDirectory, GetLeafName));
9860 nsCString spec;
9861 OriginAttributes attrs;
9862 nsCString originalSuffix;
9863 OriginParser::ResultType result = OriginParser::ParseOrigin(
9864 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
9865 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
9866 mType = OriginProps::eInvalid;
9867 return NS_OK;
9870 const auto persistenceType = [&]() -> PersistenceType {
9871 // XXX We shouldn't continue with initialization if OriginParser returned
9872 // anything else but ValidOrigin. Otherwise, we have to deal with empty
9873 // spec when the origin is obsolete, like here. The caller should handle
9874 // the errors. Until it's fixed, we have to treat obsolete origins as
9875 // origins with unknown/invalid persistence type.
9876 if (result != OriginParser::ValidOrigin) {
9877 return PERSISTENCE_TYPE_INVALID;
9879 return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
9880 }();
9882 mLeafName = leafName;
9883 mSpec = spec;
9884 mAttrs = attrs;
9885 mOriginalSuffix = originalSuffix;
9886 mPersistenceType.init(persistenceType);
9887 if (result == OriginParser::ObsoleteOrigin) {
9888 mType = eObsolete;
9889 } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
9890 mType = eChrome;
9891 } else {
9892 mType = eContent;
9895 return NS_OK;
9898 // static
9899 auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
9900 OriginAttributes* aAttrs,
9901 nsCString& aOriginalSuffix) -> ResultType {
9902 MOZ_ASSERT(!aOrigin.IsEmpty());
9903 MOZ_ASSERT(aAttrs);
9905 nsCString origin(aOrigin);
9906 int32_t pos = origin.RFindChar('^');
9908 if (pos == kNotFound) {
9909 aOriginalSuffix.Truncate();
9910 } else {
9911 aOriginalSuffix = Substring(origin, pos);
9914 OriginAttributes originAttributes;
9916 nsCString originNoSuffix;
9917 bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
9918 if (!ok) {
9919 return InvalidOrigin;
9922 OriginParser parser(originNoSuffix);
9924 *aAttrs = originAttributes;
9925 return parser.Parse(aSpec);
9928 auto OriginParser::Parse(nsACString& aSpec) -> ResultType {
9929 while (mTokenizer.hasMoreTokens()) {
9930 const nsDependentCSubstring& token = mTokenizer.nextToken();
9932 HandleToken(token);
9934 if (mError) {
9935 break;
9938 if (!mHandledTokens.IsEmpty()) {
9939 mHandledTokens.AppendLiteral(", ");
9941 mHandledTokens.Append('\'');
9942 mHandledTokens.Append(token);
9943 mHandledTokens.Append('\'');
9946 if (!mError && mTokenizer.separatorAfterCurrentToken()) {
9947 HandleTrailingSeparator();
9950 if (mError) {
9951 QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
9952 mHandledTokens.get());
9954 return (mSchemeType == eChrome || mSchemeType == eAbout) ? ObsoleteOrigin
9955 : InvalidOrigin;
9958 MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
9960 // For IPv6 URL, it should at least have three groups.
9961 MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
9963 nsAutoCString spec(mScheme);
9965 if (mSchemeType == eFile) {
9966 spec.AppendLiteral("://");
9968 if (mUniversalFileOrigin) {
9969 MOZ_ASSERT(mPathnameComponents.Length() == 1);
9971 spec.Append(mPathnameComponents[0]);
9972 } else {
9973 for (uint32_t count = mPathnameComponents.Length(), index = 0;
9974 index < count; index++) {
9975 spec.Append('/');
9976 spec.Append(mPathnameComponents[index]);
9980 aSpec = spec;
9982 return ValidOrigin;
9985 if (mSchemeType == eAbout) {
9986 if (mMaybeObsolete) {
9987 // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
9988 // and can be safely removed.
9989 return mHost.EqualsLiteral("home") ? ObsoleteOrigin : InvalidOrigin;
9991 spec.Append(':');
9992 } else if (mSchemeType != eChrome) {
9993 spec.AppendLiteral("://");
9996 spec.Append(mHost);
9998 if (!mPort.IsNull()) {
9999 spec.Append(':');
10000 spec.AppendInt(mPort.Value());
10003 aSpec = spec;
10005 return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin;
10008 void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) {
10009 MOZ_ASSERT(!aToken.IsEmpty());
10010 MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme);
10012 bool isAbout = false;
10013 bool isMozSafeAbout = false;
10014 bool isFile = false;
10015 bool isChrome = false;
10016 if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") ||
10017 (isAbout = aToken.EqualsLiteral("about") ||
10018 (isMozSafeAbout = aToken.EqualsLiteral("moz-safe-about"))) ||
10019 aToken.EqualsLiteral("indexeddb") ||
10020 (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") ||
10021 aToken.EqualsLiteral("resource") ||
10022 aToken.EqualsLiteral("moz-extension") ||
10023 (isChrome = aToken.EqualsLiteral(kChromeOrigin))) {
10024 mScheme = aToken;
10026 if (isAbout) {
10027 mSchemeType = eAbout;
10028 mState = isMozSafeAbout ? eExpectingEmptyToken1OrHost : eExpectingHost;
10029 } else if (isChrome) {
10030 mSchemeType = eChrome;
10031 if (mTokenizer.hasMoreTokens()) {
10032 mError = true;
10034 mState = eComplete;
10035 } else {
10036 if (isFile) {
10037 mSchemeType = eFile;
10039 mState = eExpectingEmptyToken1;
10042 return;
10045 QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get());
10047 mError = true;
10050 void OriginParser::HandlePathnameComponent(
10051 const nsDependentCSubstring& aToken) {
10052 MOZ_ASSERT(!aToken.IsEmpty());
10053 MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
10054 mState == eExpectingEmptyTokenOrPathnameComponent);
10055 MOZ_ASSERT(mSchemeType == eFile);
10057 mPathnameComponents.AppendElement(aToken);
10059 mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
10060 : eComplete;
10063 void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
10064 switch (mState) {
10065 case eExpectingAppIdOrScheme: {
10066 if (aToken.IsEmpty()) {
10067 QM_WARNING("Expected an app id or scheme (not an empty string)!");
10069 mError = true;
10070 return;
10073 if (IsAsciiDigit(aToken.First())) {
10074 // nsDependentCSubstring doesn't provice ToInteger()
10075 nsCString token(aToken);
10077 nsresult rv;
10078 Unused << token.ToInteger(&rv);
10079 if (NS_SUCCEEDED(rv)) {
10080 mState = eExpectingInMozBrowser;
10081 return;
10085 HandleScheme(aToken);
10087 return;
10090 case eExpectingInMozBrowser: {
10091 if (aToken.Length() != 1) {
10092 QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!",
10093 aToken.Length());
10095 mError = true;
10096 return;
10099 if (aToken.First() == 't') {
10100 mInIsolatedMozBrowser = true;
10101 } else if (aToken.First() == 'f') {
10102 mInIsolatedMozBrowser = false;
10103 } else {
10104 QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
10105 nsCString(aToken).get());
10107 mError = true;
10108 return;
10111 mState = eExpectingScheme;
10113 return;
10116 case eExpectingScheme: {
10117 if (aToken.IsEmpty()) {
10118 QM_WARNING("Expected a scheme (not an empty string)!");
10120 mError = true;
10121 return;
10124 HandleScheme(aToken);
10126 return;
10129 case eExpectingEmptyToken1: {
10130 if (!aToken.IsEmpty()) {
10131 QM_WARNING("Expected the first empty token!");
10133 mError = true;
10134 return;
10137 mState = eExpectingEmptyToken2;
10139 return;
10142 case eExpectingEmptyToken2: {
10143 if (!aToken.IsEmpty()) {
10144 QM_WARNING("Expected the second empty token!");
10146 mError = true;
10147 return;
10150 if (mSchemeType == eFile) {
10151 mState = eExpectingEmptyTokenOrUniversalFileOrigin;
10152 } else {
10153 if (mSchemeType == eAbout) {
10154 mMaybeObsolete = true;
10156 mState = eExpectingHost;
10159 return;
10162 case eExpectingEmptyTokenOrUniversalFileOrigin: {
10163 MOZ_ASSERT(mSchemeType == eFile);
10165 if (aToken.IsEmpty()) {
10166 mState = mTokenizer.hasMoreTokens()
10167 ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
10168 : eComplete;
10170 return;
10173 if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
10174 mUniversalFileOrigin = true;
10176 mPathnameComponents.AppendElement(aToken);
10178 mState = eComplete;
10180 return;
10183 QM_WARNING(
10184 "Expected the third empty token or "
10185 "UNIVERSAL_FILE_URI_ORIGIN!");
10187 mError = true;
10188 return;
10191 case eExpectingHost: {
10192 if (aToken.IsEmpty()) {
10193 QM_WARNING("Expected a host (not an empty string)!");
10195 mError = true;
10196 return;
10199 mHost = aToken;
10201 if (aToken.First() == '[') {
10202 MOZ_ASSERT(mIPGroup == 0);
10204 ++mIPGroup;
10205 mState = eExpectingIPV6Token;
10207 MOZ_ASSERT(mTokenizer.hasMoreTokens());
10208 return;
10211 if (mTokenizer.hasMoreTokens()) {
10212 if (mSchemeType == eAbout) {
10213 QM_WARNING("Expected an empty string after host!");
10215 mError = true;
10216 return;
10219 mState = eExpectingPort;
10221 return;
10224 mState = eComplete;
10226 return;
10229 case eExpectingPort: {
10230 MOZ_ASSERT(mSchemeType == eNone);
10232 if (aToken.IsEmpty()) {
10233 QM_WARNING("Expected a port (not an empty string)!");
10235 mError = true;
10236 return;
10239 // nsDependentCSubstring doesn't provice ToInteger()
10240 nsCString token(aToken);
10242 nsresult rv;
10243 uint32_t port = token.ToInteger(&rv);
10244 if (NS_SUCCEEDED(rv)) {
10245 mPort.SetValue() = port;
10246 } else {
10247 QM_WARNING("'%s' is not a valid port number!", token.get());
10249 mError = true;
10250 return;
10253 mState = eComplete;
10255 return;
10258 case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
10259 MOZ_ASSERT(mSchemeType == eFile);
10261 if (aToken.IsEmpty()) {
10262 mPathnameComponents.AppendElement(""_ns);
10264 mState = mTokenizer.hasMoreTokens()
10265 ? eExpectingEmptyTokenOrPathnameComponent
10266 : eComplete;
10268 return;
10271 if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) {
10272 mMaybeDriveLetter = true;
10274 mPathnameComponents.AppendElement(aToken);
10276 mState = mTokenizer.hasMoreTokens()
10277 ? eExpectingEmptyTokenOrPathnameComponent
10278 : eComplete;
10280 return;
10283 HandlePathnameComponent(aToken);
10285 return;
10288 case eExpectingEmptyTokenOrPathnameComponent: {
10289 MOZ_ASSERT(mSchemeType == eFile);
10291 if (aToken.IsEmpty()) {
10292 if (mMaybeDriveLetter) {
10293 MOZ_ASSERT(mPathnameComponents.Length() == 1);
10295 nsCString& pathnameComponent = mPathnameComponents[0];
10296 pathnameComponent.Append(':');
10298 mMaybeDriveLetter = false;
10299 } else {
10300 mPathnameComponents.AppendElement(""_ns);
10303 mState = mTokenizer.hasMoreTokens()
10304 ? eExpectingEmptyTokenOrPathnameComponent
10305 : eComplete;
10307 return;
10310 HandlePathnameComponent(aToken);
10312 return;
10315 case eExpectingEmptyToken1OrHost: {
10316 MOZ_ASSERT(mSchemeType == eAbout &&
10317 mScheme.EqualsLiteral("moz-safe-about"));
10319 if (aToken.IsEmpty()) {
10320 mState = eExpectingEmptyToken2;
10321 } else {
10322 mHost = aToken;
10323 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
10326 return;
10329 case eExpectingIPV6Token: {
10330 // A safe check for preventing infinity recursion.
10331 if (++mIPGroup > 8) {
10332 mError = true;
10333 return;
10336 mHost.AppendLiteral(":");
10337 mHost.Append(aToken);
10338 if (!aToken.IsEmpty() && aToken.Last() == ']') {
10339 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
10342 return;
10345 default:
10346 MOZ_CRASH("Should never get here!");
10350 void OriginParser::HandleTrailingSeparator() {
10351 MOZ_ASSERT(mState == eComplete);
10352 MOZ_ASSERT(mSchemeType == eFile);
10354 mPathnameComponents.AppendElement(""_ns);
10356 mState = eHandledTrailingSeparator;
10359 nsresult RepositoryOperationBase::ProcessRepository() {
10360 AssertIsOnIOThread();
10362 #ifdef DEBUG
10364 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(mDirectory, Exists),
10365 QM_ASSERT_UNREACHABLE);
10366 MOZ_ASSERT(exists);
10368 #endif
10370 QM_TRY(CollectEachFileEntry(
10371 *mDirectory,
10372 [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
10373 QM_TRY_INSPECT(
10374 const auto& leafName,
10375 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, originFile, GetLeafName));
10377 // Unknown files during upgrade are allowed. Just warn if we find
10378 // them.
10379 if (!IsOSMetadata(leafName)) {
10380 UNKNOWN_FILE_WARNING(leafName);
10383 return mozilla::Ok{};
10385 [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
10386 OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
10387 QM_TRY(originProps.Init([&self](const auto& aSpec) {
10388 return self.PersistenceTypeFromSpec(aSpec);
10389 }));
10390 // Bypass invalid origins while upgrading
10391 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
10393 if (originProps.mType != OriginProps::eObsolete) {
10394 QM_TRY_INSPECT(
10395 const bool& removed,
10396 MOZ_TO_RESULT_INVOKE(self, PrepareOriginDirectory, originProps));
10397 if (removed) {
10398 return mozilla::Ok{};
10402 self.mOriginProps.AppendElement(std::move(originProps));
10404 return mozilla::Ok{};
10405 }));
10407 if (mOriginProps.IsEmpty()) {
10408 return NS_OK;
10411 QM_TRY(ProcessOriginDirectories());
10413 return NS_OK;
10416 template <typename UpgradeMethod>
10417 nsresult RepositoryOperationBase::MaybeUpgradeClients(
10418 const OriginProps& aOriginProps, UpgradeMethod aMethod) {
10419 AssertIsOnIOThread();
10420 MOZ_ASSERT(aMethod);
10422 QuotaManager* quotaManager = QuotaManager::Get();
10423 MOZ_ASSERT(quotaManager);
10425 QM_TRY(CollectEachFileEntry(
10426 *aOriginProps.mDirectory,
10427 [](const auto& file) -> Result<mozilla::Ok, nsresult> {
10428 QM_TRY_INSPECT(
10429 const auto& leafName,
10430 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
10432 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
10433 UNKNOWN_FILE_WARNING(leafName);
10436 return mozilla::Ok{};
10438 [quotaManager, &aMethod,
10439 &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
10440 QM_TRY_INSPECT(
10441 const auto& leafName,
10442 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, dir, GetLeafName));
10444 QM_TRY_INSPECT(
10445 const bool& removed,
10446 MOZ_TO_RESULT_INVOKE(self, PrepareClientDirectory, dir, leafName));
10447 if (removed) {
10448 return mozilla::Ok{};
10451 Client::Type clientType;
10452 bool ok = Client::TypeFromText(leafName, clientType, fallible);
10453 if (!ok) {
10454 UNKNOWN_FILE_WARNING(leafName);
10455 return mozilla::Ok{};
10458 Client* client = quotaManager->GetClient(clientType);
10459 MOZ_ASSERT(client);
10461 QM_TRY((client->*aMethod)(dir));
10463 return mozilla::Ok{};
10464 }));
10466 return NS_OK;
10469 nsresult RepositoryOperationBase::PrepareClientDirectory(
10470 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
10471 AssertIsOnIOThread();
10473 aRemoved = false;
10474 return NS_OK;
10477 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
10478 AssertIsOnIOThread();
10479 MOZ_ASSERT(mDirectory);
10481 const auto maybeLegacyPersistenceType =
10482 LegacyPersistenceTypeFromFile(*mDirectory, fallible);
10483 QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
10485 mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
10487 return NS_OK;
10490 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
10491 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
10492 nsIFile& aFile, const fallible_t&) {
10493 nsAutoString leafName;
10494 MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
10496 if (leafName.Equals(u"persistent"_ns)) {
10497 return Some(LegacyPersistenceType::Persistent);
10500 if (leafName.Equals(u"temporary"_ns)) {
10501 return Some(LegacyPersistenceType::Temporary);
10504 return Nothing();
10507 PersistenceType
10508 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
10509 const nsCString& aSpec) {
10510 if (QuotaManager::IsOriginInternal(aSpec)) {
10511 return PERSISTENCE_TYPE_PERSISTENT;
10514 return PERSISTENCE_TYPE_DEFAULT;
10517 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
10518 const nsCString& aSpec) {
10519 switch (*mLegacyPersistenceType) {
10520 case LegacyPersistenceType::Persistent:
10521 return PersistenceTypeFromLegacyPersistentSpec(aSpec);
10522 case LegacyPersistenceType::Temporary:
10523 return PERSISTENCE_TYPE_TEMPORARY;
10525 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
10528 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
10529 nsIFile* aDirectory) {
10530 AssertIsOnIOThread();
10531 MOZ_ASSERT(aDirectory);
10533 QM_TRY_INSPECT(
10534 const auto& metadataFile,
10535 CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
10537 QM_TRY_INSPECT(const bool& exists,
10538 MOZ_TO_RESULT_INVOKE(metadataFile, Exists));
10540 if (!exists) {
10541 // Directory structure upgrade needed.
10542 // Move all files to IDB specific directory.
10544 nsString idbDirectoryName;
10545 QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
10546 NS_ERROR_FAILURE);
10548 QM_TRY_INSPECT(const auto& idbDirectory,
10549 CloneFileAndAppend(*aDirectory, idbDirectoryName));
10551 QM_TRY(QM_OR_ELSE_WARN(
10552 ToResult(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
10553 ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
10554 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
10555 QM_TRY_INSPECT(const bool& isDirectory,
10556 MOZ_TO_RESULT_INVOKE(idbDirectory, IsDirectory));
10558 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
10560 return Ok{};
10563 return Err(rv);
10564 })));
10566 QM_TRY(CollectEachFile(
10567 *aDirectory,
10568 [&idbDirectory, &idbDirectoryName](
10569 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
10570 QM_TRY_INSPECT(
10571 const auto& leafName,
10572 MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
10574 if (!leafName.Equals(idbDirectoryName)) {
10575 QM_TRY(file->MoveTo(idbDirectory, u""_ns));
10578 return Ok{};
10579 }));
10581 QM_TRY(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644));
10584 return NS_OK;
10587 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
10588 OriginProps& aOriginProps, bool* aRemoved) {
10589 AssertIsOnIOThread();
10590 MOZ_ASSERT(aRemoved);
10592 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
10593 QM_TRY(MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get()));
10595 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10596 } else {
10597 int64_t timestamp;
10598 nsCString group;
10599 nsCString origin;
10600 Nullable<bool> isApp;
10601 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory.get(), timestamp,
10602 group, origin, isApp);
10603 if (NS_FAILED(rv)) {
10604 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10605 aOriginProps.mNeedsRestore = true;
10606 } else if (!isApp.IsNull()) {
10607 aOriginProps.mIgnore = true;
10611 *aRemoved = false;
10612 return NS_OK;
10615 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
10616 const OriginProps& aOriginProps) {
10617 AssertIsOnIOThread();
10619 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
10620 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10621 aOriginProps.mTimestamp,
10622 aOriginProps.mOriginMetadata));
10624 // Move internal origins to new persistent storage.
10625 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
10626 PERSISTENCE_TYPE_PERSISTENT) {
10627 if (!mPermanentStorageDir) {
10628 QuotaManager* quotaManager = QuotaManager::Get();
10629 MOZ_ASSERT(quotaManager);
10631 const nsString& permanentStoragePath =
10632 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
10634 QM_TRY_UNWRAP(mPermanentStorageDir,
10635 QM_NewLocalFile(permanentStoragePath));
10638 const nsAString& leafName = aOriginProps.mLeafName;
10640 QM_TRY_INSPECT(const auto& newDirectory,
10641 CloneFileAndAppend(*mPermanentStorageDir, leafName));
10643 QM_TRY_INSPECT(const bool& exists,
10644 MOZ_TO_RESULT_INVOKE(newDirectory, Exists));
10646 if (exists) {
10647 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
10648 NS_ConvertUTF16toUTF8(leafName).get());
10650 QM_TRY(aOriginProps.mDirectory->Remove(/* recursive */ true));
10651 } else {
10652 QM_TRY(aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns));
10655 } else if (aOriginProps.mNeedsRestore) {
10656 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10657 aOriginProps.mTimestamp,
10658 aOriginProps.mOriginMetadata));
10659 } else if (!aOriginProps.mIgnore) {
10660 QM_TRY_INSPECT(const auto& file,
10661 CloneFileAndAppend(*aOriginProps.mDirectory,
10662 nsLiteralString(METADATA_FILE_NAME)));
10664 QM_TRY_INSPECT(const auto& stream,
10665 GetBinaryOutputStream(*file, FileFlag::Append));
10667 MOZ_ASSERT(stream);
10669 // Currently unused (used to be isApp).
10670 QM_TRY(stream->WriteBoolean(false));
10673 return NS_OK;
10676 nsresult UpgradeStorageHelperBase::Init() {
10677 AssertIsOnIOThread();
10678 MOZ_ASSERT(mDirectory);
10680 const auto maybePersistenceType =
10681 PersistenceTypeFromFile(*mDirectory, fallible);
10682 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
10684 mPersistenceType.init(maybePersistenceType.value());
10686 return NS_OK;
10689 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
10690 const nsCString& aSpec) {
10691 // There's no moving of origin directories between repositories like in the
10692 // CreateOrUpgradeDirectoryMetadataHelper
10693 return *mPersistenceType;
10696 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
10697 OriginProps& aOriginProps, bool* aRemoved) {
10698 AssertIsOnIOThread();
10699 MOZ_ASSERT(aRemoved);
10701 int64_t timestamp;
10702 nsCString group;
10703 nsCString origin;
10704 Nullable<bool> isApp;
10705 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory.get(), timestamp,
10706 group, origin, isApp);
10707 if (NS_FAILED(rv) || isApp.IsNull()) {
10708 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10709 aOriginProps.mNeedsRestore = true;
10710 } else {
10711 aOriginProps.mTimestamp = timestamp;
10714 *aRemoved = false;
10715 return NS_OK;
10718 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
10719 const OriginProps& aOriginProps) {
10720 AssertIsOnIOThread();
10722 // This handles changes in origin string generation from nsIPrincipal,
10723 // especially the change from: appId+inMozBrowser+originNoSuffix
10724 // to: origin (with origin suffix).
10725 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
10726 if (renamed) {
10727 return NS_OK;
10730 if (aOriginProps.mNeedsRestore) {
10731 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10732 aOriginProps.mTimestamp,
10733 aOriginProps.mOriginMetadata));
10736 QM_TRY(CreateDirectoryMetadata2(
10737 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
10738 /* aPersisted */ false, aOriginProps.mOriginMetadata));
10740 return NS_OK;
10743 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
10744 const OriginProps& aOriginProps) {
10745 AssertIsOnIOThread();
10747 // The Cache API was creating top level morgue directories by accident for
10748 // a short time in nightly. This unfortunately prevents all storage from
10749 // working. So recover these profiles permanently by removing these corrupt
10750 // directories as part of this upgrade.
10752 QM_TRY_INSPECT(const auto& morgueDir,
10753 MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIFile>,
10754 *aOriginProps.mDirectory, Clone));
10756 QM_TRY(morgueDir->Append(u"morgue"_ns));
10758 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE(morgueDir, Exists));
10760 if (exists) {
10761 QM_WARNING("Deleting accidental morgue directory!");
10763 QM_TRY(morgueDir->Remove(/* recursive */ true));
10766 return NS_OK;
10769 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
10770 const OriginProps& aOriginProps) {
10771 AssertIsOnIOThread();
10773 // TODO: This method was empty for some time due to accidental changes done
10774 // in bug 1320404. This led to renaming of origin directories like:
10775 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
10776 // to:
10777 // https+++developer.cdn.mozilla.net^inBrowser=1
10778 // instead of just removing them.
10780 const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
10781 if (!originalSuffix.IsEmpty()) {
10782 MOZ_ASSERT(originalSuffix[0] == '^');
10784 if (!URLParams::Parse(
10785 Substring(originalSuffix, 1, originalSuffix.Length() - 1),
10786 [](const nsAString& aName, const nsAString& aValue) {
10787 if (aName.EqualsLiteral("appId")) {
10788 return false;
10791 return true;
10792 })) {
10793 QM_TRY(RemoveObsoleteOrigin(aOriginProps));
10795 return true;
10799 return false;
10802 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
10803 OriginProps& aOriginProps, bool* aRemoved) {
10804 AssertIsOnIOThread();
10805 MOZ_ASSERT(aRemoved);
10807 QM_TRY(MaybeRemoveMorgueDirectory(aOriginProps));
10809 QM_TRY(
10810 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0));
10812 QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
10813 if (removed) {
10814 *aRemoved = true;
10815 return NS_OK;
10818 int64_t timestamp;
10819 nsCString group;
10820 nsCString origin;
10821 Nullable<bool> isApp;
10822 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory.get(), timestamp,
10823 group, origin, isApp);
10824 if (NS_FAILED(rv) || isApp.IsNull()) {
10825 aOriginProps.mNeedsRestore = true;
10828 nsCString suffix;
10829 rv = GetDirectoryMetadata2(aOriginProps.mDirectory.get(), timestamp, suffix,
10830 group, origin, isApp.SetValue());
10831 if (NS_FAILED(rv)) {
10832 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10833 aOriginProps.mNeedsRestore2 = true;
10834 } else {
10835 aOriginProps.mTimestamp = timestamp;
10838 *aRemoved = false;
10839 return NS_OK;
10842 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
10843 const OriginProps& aOriginProps) {
10844 AssertIsOnIOThread();
10846 // This handles changes in origin string generation from nsIPrincipal,
10847 // especially the stripping of obsolete origin attributes like addonId.
10848 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
10849 if (renamed) {
10850 return NS_OK;
10853 if (aOriginProps.mNeedsRestore) {
10854 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10855 aOriginProps.mTimestamp,
10856 aOriginProps.mOriginMetadata));
10859 if (aOriginProps.mNeedsRestore2) {
10860 QM_TRY(CreateDirectoryMetadata2(
10861 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
10862 /* aPersisted */ false, aOriginProps.mOriginMetadata));
10865 return NS_OK;
10868 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
10869 OriginProps& aOriginProps, bool* aRemoved) {
10870 AssertIsOnIOThread();
10871 MOZ_ASSERT(aRemoved);
10873 QM_TRY(
10874 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1));
10876 int64_t timestamp;
10877 nsCString group;
10878 nsCString origin;
10879 Nullable<bool> isApp;
10880 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory.get(), timestamp,
10881 group, origin, isApp);
10882 if (NS_FAILED(rv) || isApp.IsNull()) {
10883 aOriginProps.mNeedsRestore = true;
10886 nsCString suffix;
10887 rv = GetDirectoryMetadata2(aOriginProps.mDirectory.get(), timestamp, suffix,
10888 group, origin, isApp.SetValue());
10889 if (NS_FAILED(rv)) {
10890 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10891 aOriginProps.mNeedsRestore2 = true;
10892 } else {
10893 aOriginProps.mTimestamp = timestamp;
10896 *aRemoved = false;
10897 return NS_OK;
10900 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
10901 const OriginProps& aOriginProps) {
10902 AssertIsOnIOThread();
10904 if (aOriginProps.mNeedsRestore) {
10905 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10906 aOriginProps.mTimestamp,
10907 aOriginProps.mOriginMetadata));
10910 if (aOriginProps.mNeedsRestore2) {
10911 QM_TRY(CreateDirectoryMetadata2(
10912 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
10913 /* aPersisted */ false, aOriginProps.mOriginMetadata));
10916 return NS_OK;
10919 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
10920 OriginProps& aOriginProps, bool* aRemoved) {
10921 AssertIsOnIOThread();
10922 MOZ_ASSERT(aRemoved);
10924 QM_TRY(
10925 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2));
10927 int64_t timestamp;
10928 nsCString group;
10929 nsCString origin;
10930 Nullable<bool> isApp;
10931 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory.get(), timestamp,
10932 group, origin, isApp);
10933 if (NS_FAILED(rv) || isApp.IsNull()) {
10934 aOriginProps.mNeedsRestore = true;
10937 nsCString suffix;
10938 rv = GetDirectoryMetadata2(aOriginProps.mDirectory.get(), timestamp, suffix,
10939 group, origin, isApp.SetValue());
10940 if (NS_FAILED(rv)) {
10941 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10942 aOriginProps.mNeedsRestore2 = true;
10943 } else {
10944 aOriginProps.mTimestamp = timestamp;
10947 *aRemoved = false;
10948 return NS_OK;
10951 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
10952 const OriginProps& aOriginProps) {
10953 AssertIsOnIOThread();
10955 if (aOriginProps.mNeedsRestore) {
10956 QM_TRY(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10957 aOriginProps.mTimestamp,
10958 aOriginProps.mOriginMetadata));
10961 if (aOriginProps.mNeedsRestore2) {
10962 QM_TRY(CreateDirectoryMetadata2(
10963 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
10964 /* aPersisted */ false, aOriginProps.mOriginMetadata));
10967 return NS_OK;
10970 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
10971 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
10972 AssertIsOnIOThread();
10974 if (Client::IsDeprecatedClient(aLeafName)) {
10975 QM_WARNING("Deleting deprecated %s client!",
10976 NS_ConvertUTF16toUTF8(aLeafName).get());
10978 QM_TRY(aFile->Remove(true));
10980 aRemoved = true;
10981 } else {
10982 aRemoved = false;
10985 return NS_OK;
10988 nsresult RestoreDirectoryMetadata2Helper::Init() {
10989 AssertIsOnIOThread();
10990 MOZ_ASSERT(mDirectory);
10992 nsCOMPtr<nsIFile> parentDir;
10993 QM_TRY(mDirectory->GetParent(getter_AddRefs(parentDir)));
10995 const auto maybePersistenceType =
10996 PersistenceTypeFromFile(*parentDir, fallible);
10997 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
10999 mPersistenceType.init(maybePersistenceType.value());
11001 return NS_OK;
11004 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
11005 OriginProps originProps(WrapMovingNotNull(mDirectory));
11006 QM_TRY(originProps.Init(
11007 [&self = *this](const auto& aSpec) { return *self.mPersistenceType; }));
11009 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
11011 originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
11013 mOriginProps.AppendElement(std::move(originProps));
11015 QM_TRY(ProcessOriginDirectories());
11017 return NS_OK;
11020 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
11021 const OriginProps& aOriginProps) {
11022 AssertIsOnIOThread();
11024 // We don't have any approach to restore aPersisted, so reset it to false.
11025 QM_TRY(CreateDirectoryMetadata2(
11026 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11027 /* aPersisted */ false, aOriginProps.mOriginMetadata));
11029 return NS_OK;
11032 } // namespace mozilla::dom::quota