Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / quota / ActorsParent.cpp
blob186b4736ffd99ec634a394d1825325d8d4324e4b
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 "CanonicalQuotaObject.h"
11 #include "ClientUsageArray.h"
12 #include "Flatten.h"
13 #include "FirstInitializationAttemptsImpl.h"
14 #include "GroupInfo.h"
15 #include "GroupInfoPair.h"
16 #include "NormalOriginOperationBase.h"
17 #include "OriginOperationBase.h"
18 #include "OriginOperations.h"
19 #include "OriginParser.h"
20 #include "OriginScope.h"
21 #include "OriginInfo.h"
22 #include "QuotaCommon.h"
23 #include "QuotaManager.h"
24 #include "ResolvableNormalOriginOp.h"
25 #include "SanitizationUtils.h"
26 #include "ScopedLogExtraInfo.h"
27 #include "UsageInfo.h"
29 // Global includes
30 #include <cinttypes>
31 #include <cstdlib>
32 #include <cstring>
33 #include <algorithm>
34 #include <cstdint>
35 #include <functional>
36 #include <new>
37 #include <numeric>
38 #include <tuple>
39 #include <type_traits>
40 #include <utility>
41 #include "DirectoryLockImpl.h"
42 #include "ErrorList.h"
43 #include "MainThreadUtils.h"
44 #include "mozIStorageAsyncConnection.h"
45 #include "mozIStorageConnection.h"
46 #include "mozIStorageService.h"
47 #include "mozIStorageStatement.h"
48 #include "mozStorageCID.h"
49 #include "mozStorageHelper.h"
50 #include "mozilla/AlreadyAddRefed.h"
51 #include "mozilla/AppShutdown.h"
52 #include "mozilla/Assertions.h"
53 #include "mozilla/Atomics.h"
54 #include "mozilla/Attributes.h"
55 #include "mozilla/AutoRestore.h"
56 #include "mozilla/BasePrincipal.h"
57 #include "mozilla/CheckedInt.h"
58 #include "mozilla/CondVar.h"
59 #include "mozilla/InitializedOnce.h"
60 #include "mozilla/Logging.h"
61 #include "mozilla/MacroForEach.h"
62 #include "mozilla/Maybe.h"
63 #include "mozilla/Mutex.h"
64 #include "mozilla/NotNull.h"
65 #include "mozilla/OriginAttributes.h"
66 #include "mozilla/Preferences.h"
67 #include "mozilla/RefPtr.h"
68 #include "mozilla/Result.h"
69 #include "mozilla/ResultExtensions.h"
70 #include "mozilla/ScopeExit.h"
71 #include "mozilla/Services.h"
72 #include "mozilla/SpinEventLoopUntil.h"
73 #include "mozilla/StaticPrefs_dom.h"
74 #include "mozilla/StaticPtr.h"
75 #include "mozilla/SystemPrincipal.h"
76 #include "mozilla/Telemetry.h"
77 #include "mozilla/TelemetryHistogramEnums.h"
78 #include "mozilla/TextUtils.h"
79 #include "mozilla/TimeStamp.h"
80 #include "mozilla/UniquePtr.h"
81 #include "mozilla/Unused.h"
82 #include "mozilla/Variant.h"
83 #include "mozilla/dom/FileSystemQuotaClientFactory.h"
84 #include "mozilla/dom/FlippedOnce.h"
85 #include "mozilla/dom/LocalStorageCommon.h"
86 #include "mozilla/dom/StorageDBUpdater.h"
87 #include "mozilla/dom/cache/QuotaClient.h"
88 #include "mozilla/dom/indexedDB/ActorsParent.h"
89 #include "mozilla/dom/ipc/IdType.h"
90 #include "mozilla/dom/localstorage/ActorsParent.h"
91 #include "mozilla/dom/quota/AssertionsImpl.h"
92 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
93 #include "mozilla/dom/quota/Client.h"
94 #include "mozilla/dom/quota/Config.h"
95 #include "mozilla/dom/quota/Constants.h"
96 #include "mozilla/dom/quota/DirectoryLock.h"
97 #include "mozilla/dom/quota/FileUtils.h"
98 #include "mozilla/dom/quota/PersistenceType.h"
99 #include "mozilla/dom/quota/QuotaManagerService.h"
100 #include "mozilla/dom/quota/ResultExtensions.h"
101 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
102 #include "mozilla/dom/quota/StreamUtils.h"
103 #include "mozilla/dom/simpledb/ActorsParent.h"
104 #include "mozilla/fallible.h"
105 #include "mozilla/ipc/BackgroundChild.h"
106 #include "mozilla/ipc/BackgroundParent.h"
107 #include "mozilla/ipc/PBackgroundChild.h"
108 #include "mozilla/ipc/PBackgroundSharedTypes.h"
109 #include "mozilla/ipc/ProtocolUtils.h"
110 #include "mozilla/net/ExtensionProtocolHandler.h"
111 #include "nsAppDirectoryServiceDefs.h"
112 #include "nsBaseHashtable.h"
113 #include "nsCOMPtr.h"
114 #include "nsCRTGlue.h"
115 #include "nsClassHashtable.h"
116 #include "nsComponentManagerUtils.h"
117 #include "nsContentUtils.h"
118 #include "nsDebug.h"
119 #include "nsDirectoryServiceUtils.h"
120 #include "nsError.h"
121 #include "nsIBinaryInputStream.h"
122 #include "nsIBinaryOutputStream.h"
123 #include "nsIConsoleService.h"
124 #include "nsIDirectoryEnumerator.h"
125 #include "nsIDUtils.h"
126 #include "nsIEventTarget.h"
127 #include "nsIFile.h"
128 #include "nsIFileStreams.h"
129 #include "nsIInputStream.h"
130 #include "nsIObjectInputStream.h"
131 #include "nsIObjectOutputStream.h"
132 #include "nsIObserver.h"
133 #include "nsIObserverService.h"
134 #include "nsIOutputStream.h"
135 #include "nsIQuotaRequests.h"
136 #include "nsIPlatformInfo.h"
137 #include "nsIPrincipal.h"
138 #include "nsIRunnable.h"
139 #include "nsIScriptObjectPrincipal.h"
140 #include "nsISupports.h"
141 #include "nsISupportsPrimitives.h"
142 #include "nsIThread.h"
143 #include "nsITimer.h"
144 #include "nsIURI.h"
145 #include "nsIWidget.h"
146 #include "nsLiteralString.h"
147 #include "nsNetUtil.h"
148 #include "nsPIDOMWindow.h"
149 #include "nsPrintfCString.h"
150 #include "nsStandardURL.h"
151 #include "nsServiceManagerUtils.h"
152 #include "nsString.h"
153 #include "nsStringFlags.h"
154 #include "nsStringFwd.h"
155 #include "nsTArray.h"
156 #include "nsTHashtable.h"
157 #include "nsTLiteralString.h"
158 #include "nsTPromiseFlatString.h"
159 #include "nsTStringRepr.h"
160 #include "nsThreadUtils.h"
161 #include "nsURLHelper.h"
162 #include "nsXPCOM.h"
163 #include "nsXPCOMCID.h"
164 #include "nsXULAppAPI.h"
165 #include "prinrval.h"
166 #include "prio.h"
167 #include "prtime.h"
169 // The amount of time, in milliseconds, that our IO thread will stay alive
170 // after the last event it processes.
171 #define DEFAULT_THREAD_TIMEOUT_MS 30000
174 * If shutdown takes this long, kill actors of a quota client, to avoid reaching
175 * the crash timeout.
177 #define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
180 * Automatically crash the browser if shutdown of a quota client takes this
181 * long. We've chosen a value that is long enough that it is unlikely for the
182 * problem to be falsely triggered by slow system I/O. We've also chosen a
183 * value long enough so that automated tests should time out and fail if
184 * shutdown of a quota client takes too long. Also, this value is long enough
185 * so that testers can notice the timeout; we want to know about the timeouts,
186 * not hide them. On the other hand this value is less than 60 seconds which is
187 * used by nsTerminator to crash a hung main process.
189 #define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
191 static_assert(
192 SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
193 "The kill actors timeout must be shorter than the crash browser one.");
195 // profile-before-change, when we need to shut down quota manager
196 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
198 #define KB *1024ULL
199 #define MB *1024ULL KB
200 #define GB *1024ULL MB
202 namespace mozilla::dom::quota {
204 using namespace mozilla::ipc;
206 // We want profiles to be platform-independent so we always need to replace
207 // the same characters on every platform. Windows has the most extensive set
208 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
209 // FILE_PATH_SEPARATOR.
210 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
211 const char16_t QuotaManager::kReplaceChars16[] =
212 u"" CONTROL_CHARACTERS "/:*?\"<>|\\";
214 namespace {
216 /*******************************************************************************
217 * Constants
218 ******************************************************************************/
220 const uint32_t kSQLitePageSizeOverride = 512;
222 // Important version history:
223 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
224 // which caused Firefox 57 release concerns because the major schema upgrade
225 // means anyone downgrading to Firefox 56 will experience a non-operational
226 // QuotaManager and all of its clients.
227 // - Bug 1404344 got very concerned about that and so we decided to effectively
228 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
229 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
230 // increases. It also works because all the upgrade did was give the DOM
231 // Cache API QuotaClient an opportunity to create its newly added .padding
232 // files during initialization/upgrade, which isn't functionally necessary as
233 // that can be done on demand.
235 // Major storage version. Bump for backwards-incompatible changes.
236 // (The next major version should be 4 to distinguish from the Bug 1290481
237 // downgrade snafu.)
238 const uint32_t kMajorStorageVersion = 2;
240 // Minor storage version. Bump for backwards-compatible changes.
241 const uint32_t kMinorStorageVersion = 3;
243 // The storage version we store in the SQLite database is a (signed) 32-bit
244 // integer. The major version is left-shifted 16 bits so the max value is
245 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
246 static_assert(kMajorStorageVersion <= 0xFFFF,
247 "Major version needs to fit in 16 bits.");
248 static_assert(kMinorStorageVersion <= 0xFFFF,
249 "Minor version needs to fit in 16 bits.");
251 const int32_t kStorageVersion =
252 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
254 // See comments above about why these are a thing.
255 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
256 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
258 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
259 const char kIndexedDBOriginPrefix[] = "indexeddb://";
260 const char kResourceOriginPrefix[] = "resource://";
262 constexpr auto kStorageName = u"storage"_ns;
264 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
265 #define ARCHIVES_DIRECTORY_NAME u"archives"
266 #define PERSISTENT_DIRECTORY_NAME u"persistent"
267 #define PERMANENT_DIRECTORY_NAME u"permanent"
268 #define TEMPORARY_DIRECTORY_NAME u"temporary"
269 #define DEFAULT_DIRECTORY_NAME u"default"
270 #define PRIVATE_DIRECTORY_NAME u"private"
271 #define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
273 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
274 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
275 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
277 const int32_t kLocalStorageArchiveVersion = 4;
279 const char kProfileDoChangeTopic[] = "profile-do-change";
280 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
282 const int32_t kCacheVersion = 2;
284 /******************************************************************************
285 * SQLite functions
286 ******************************************************************************/
288 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
289 uint32_t aMinorStorageVersion) {
290 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
293 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
294 return uint32_t(aStorageVersion >> 16);
297 nsresult CreateTables(mozIStorageConnection* aConnection) {
298 AssertIsOnIOThread();
299 MOZ_ASSERT(aConnection);
301 // Table `database`
302 QM_TRY(MOZ_TO_RESULT(
303 aConnection->ExecuteSimpleSQL("CREATE TABLE database"
304 "( cache_version INTEGER NOT NULL DEFAULT 0"
305 ");"_ns)));
307 #ifdef DEBUG
309 QM_TRY_INSPECT(const int32_t& storageVersion,
310 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
312 MOZ_ASSERT(storageVersion == 0);
314 #endif
316 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
318 return NS_OK;
321 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
322 AssertIsOnIOThread();
324 QM_TRY_INSPECT(const auto& stmt,
325 CreateAndExecuteSingleStepStatement<
326 SingleStepResult::ReturnNullIfNoResult>(
327 aConnection, "SELECT cache_version FROM database"_ns));
329 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
331 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
334 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
335 int32_t aVersion) {
336 AssertIsOnIOThread();
338 QM_TRY_INSPECT(
339 const auto& stmt,
340 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
341 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
342 "UPDATE database SET cache_version = :version;"_ns));
344 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
346 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
348 return NS_OK;
351 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
352 AssertIsOnIOThread();
354 // Table `cache`
355 QM_TRY(MOZ_TO_RESULT(
356 aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
357 "( valid INTEGER NOT NULL DEFAULT 0"
358 ", build_id TEXT NOT NULL DEFAULT ''"
359 ");"_ns)));
361 // Table `repository`
362 QM_TRY(
363 MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
364 "( id INTEGER PRIMARY KEY"
365 ", name TEXT NOT NULL"
366 ");"_ns)));
368 // Table `origin`
369 QM_TRY(MOZ_TO_RESULT(
370 aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
371 "( repository_id INTEGER NOT NULL"
372 ", suffix TEXT"
373 ", group_ TEXT NOT NULL"
374 ", origin TEXT NOT NULL"
375 ", client_usages TEXT NOT NULL"
376 ", usage INTEGER NOT NULL"
377 ", last_access_time INTEGER NOT NULL"
378 ", accessed INTEGER NOT NULL"
379 ", persisted INTEGER NOT NULL"
380 ", PRIMARY KEY (repository_id, origin)"
381 ", FOREIGN KEY (repository_id) "
382 "REFERENCES repository(id) "
383 ");"_ns)));
385 #ifdef DEBUG
387 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
388 MOZ_ASSERT(cacheVersion == 0);
390 #endif
392 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
394 return NS_OK;
397 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
398 AssertIsOnIOThread();
400 static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
401 static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
403 QM_TRY(QM_OR_ELSE_WARN(
404 // Expression.
405 ([&]() -> OkOrErr {
406 mozStorageTransaction transaction(&aConnection,
407 /*aCommitOnComplete */ false);
409 QM_TRY(QM_TO_RESULT(transaction.Start()));
410 QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
411 QM_TRY(
412 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
413 QM_TRY(QM_TO_RESULT(transaction.Commit()));
415 return Ok{};
416 }()),
417 // Fallback.
418 ([&](const QMResult& rv) -> OkOrErr {
419 QM_TRY(
420 QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
422 return Ok{};
423 })));
425 return Ok{};
428 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
429 AssertIsOnIOThread();
431 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
432 "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
434 QM_TRY(InvalidateCache(aConnection));
436 #ifdef DEBUG
438 QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
440 MOZ_ASSERT(cacheVersion == 1);
442 #endif
444 QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
446 return NS_OK;
449 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
450 mozIStorageConnection& aConnection) {
451 bool cacheUsable = true;
453 QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
455 if (cacheVersion > kCacheVersion) {
456 cacheUsable = false;
457 } else if (cacheVersion != kCacheVersion) {
458 const bool newCache = !cacheVersion;
460 mozStorageTransaction transaction(
461 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
463 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
465 if (newCache) {
466 QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
468 #ifdef DEBUG
470 QM_TRY_INSPECT(const int32_t& cacheVersion,
471 LoadCacheVersion(aConnection));
472 MOZ_ASSERT(cacheVersion == kCacheVersion);
474 #endif
476 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
477 nsLiteralCString("INSERT INTO cache (valid, build_id) "
478 "VALUES (0, '')"))));
480 nsCOMPtr<mozIStorageStatement> insertStmt;
482 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
483 if (insertStmt) {
484 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
485 } else {
486 QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
487 nsCOMPtr<mozIStorageStatement>,
488 aConnection, CreateStatement,
489 "INSERT INTO repository (id, name) "
490 "VALUES (:id, :name)"_ns));
493 QM_TRY(MOZ_TO_RESULT(
494 insertStmt->BindInt32ByName("id"_ns, persistenceType)));
496 QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
497 "name"_ns, PersistenceTypeToString(persistenceType))));
499 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
501 } else {
502 // This logic needs to change next time we change the cache!
503 static_assert(kCacheVersion == 2,
504 "Upgrade function needed due to cache version increase.");
506 while (cacheVersion != kCacheVersion) {
507 if (cacheVersion == 1) {
508 QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
509 } else {
510 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
511 QM_WARNING(
512 "Unable to initialize cache, no upgrade path is "
513 "available!");
517 QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
520 MOZ_ASSERT(cacheVersion == kCacheVersion);
523 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
526 return cacheUsable;
529 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
530 nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
531 AssertIsOnIOThread();
533 // Check if the old database exists at all.
534 QM_TRY_INSPECT(const bool& exists,
535 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
537 if (!exists) {
538 // webappsstore.sqlite doesn't exist, return a null connection.
539 return nsCOMPtr<mozIStorageConnection>{};
542 QM_TRY_INSPECT(const bool& isDirectory,
543 MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
545 if (isDirectory) {
546 QM_WARNING("webappsstore.sqlite is not a file!");
547 return nsCOMPtr<mozIStorageConnection>{};
550 QM_TRY_INSPECT(const auto& connection,
551 QM_OR_ELSE_WARN_IF(
552 // Expression.
553 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
554 nsCOMPtr<mozIStorageConnection>, aStorageService,
555 OpenUnsharedDatabase, &aWebAppsStoreFile,
556 mozIStorageService::CONNECTION_DEFAULT),
557 // Predicate.
558 IsDatabaseCorruptionError,
559 // Fallback. Don't throw an error, leave a corrupted
560 // webappsstore database as it is.
561 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
563 if (connection) {
564 // Don't propagate an error, leave a non-updateable webappsstore database as
565 // it is.
566 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
567 nsCOMPtr<mozIStorageConnection>{});
570 return connection;
573 Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
574 const nsAString& aDirectoryPath) {
575 AssertIsOnIOThread();
576 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
578 QM_TRY_UNWRAP(auto lsArchiveFile,
579 QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));
581 QM_TRY(QM_TO_RESULT(
582 lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
584 return lsArchiveFile;
587 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
588 const nsAString& aDirectoryPath) {
589 AssertIsOnIOThread();
590 MOZ_ASSERT(!aDirectoryPath.IsEmpty());
592 QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
594 QM_TRY(MOZ_TO_RESULT(
595 lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
597 return lsArchiveTmpFile;
600 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
601 mozIStorageConnection& aConnection) {
602 AssertIsOnIOThread();
604 QM_TRY_RETURN(
605 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
608 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
609 AssertIsOnIOThread();
610 MOZ_ASSERT(aConnection);
612 #ifdef DEBUG
614 QM_TRY_INSPECT(const auto& initialized,
615 IsLocalStorageArchiveInitialized(*aConnection));
616 MOZ_ASSERT(!initialized);
618 #endif
620 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
621 "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
623 QM_TRY_INSPECT(
624 const auto& stmt,
625 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
626 nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
627 "INSERT INTO database (version) VALUES (:version)"_ns));
629 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
630 QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
632 return NS_OK;
635 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
636 mozIStorageConnection& aConnection) {
637 AssertIsOnIOThread();
639 QM_TRY_INSPECT(const auto& stmt,
640 CreateAndExecuteSingleStepStatement<
641 SingleStepResult::ReturnNullIfNoResult>(
642 aConnection, "SELECT version FROM database"_ns));
644 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
646 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
649 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
650 int32_t aVersion) {
651 AssertIsOnIOThread();
652 MOZ_ASSERT(aConnection);
654 nsCOMPtr<mozIStorageStatement> stmt;
655 nsresult rv = aConnection->CreateStatement(
656 "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
657 if (NS_WARN_IF(NS_FAILED(rv))) {
658 return rv;
661 rv = stmt->BindInt32ByName("version"_ns, aVersion);
662 if (NS_WARN_IF(NS_FAILED(rv))) {
663 return rv;
666 rv = stmt->Execute();
667 if (NS_WARN_IF(NS_FAILED(rv))) {
668 return rv;
671 return NS_OK;
674 template <typename FileFunc, typename DirectoryFunc>
675 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
676 nsIFile& aDirectory, const FileFunc& aFileFunc,
677 const DirectoryFunc& aDirectoryFunc) {
678 AssertIsOnIOThread();
680 return CollectEachFile(
681 aDirectory,
682 [&aFileFunc, &aDirectoryFunc](
683 const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
684 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
686 switch (dirEntryKind) {
687 case nsIFileKind::ExistsAsDirectory:
688 return aDirectoryFunc(file);
690 case nsIFileKind::ExistsAsFile:
691 return aFileFunc(file);
693 case nsIFileKind::DoesNotExist:
694 // Ignore files that got removed externally while iterating.
695 break;
698 return Ok{};
702 /******************************************************************************
703 * Quota manager class declarations
704 ******************************************************************************/
706 } // namespace
708 class QuotaManager::Observer final : public nsIObserver {
709 static Observer* sInstance;
711 bool mPendingProfileChange;
712 bool mShutdownComplete;
714 public:
715 static nsresult Initialize();
717 static nsIObserver* GetInstance();
719 static void ShutdownCompleted();
721 private:
722 Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
723 MOZ_ASSERT(NS_IsMainThread());
726 ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
728 nsresult Init();
730 nsresult Shutdown();
732 NS_DECL_ISUPPORTS
733 NS_DECL_NSIOBSERVER
736 namespace {
738 /*******************************************************************************
739 * Local class declarations
740 ******************************************************************************/
742 } // namespace
744 namespace {
746 class CollectOriginsHelper final : public Runnable {
747 uint64_t mMinSizeToBeFreed;
749 Mutex& mMutex;
750 CondVar mCondVar;
752 // The members below are protected by mMutex.
753 nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
754 uint64_t mSizeToBeFreed;
755 bool mWaiting;
757 public:
758 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
760 // Blocks the current thread until origins are collected on the main thread.
761 // The returned value contains an aggregate size of those origins.
762 int64_t BlockAndReturnOriginsForEviction(
763 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
765 private:
766 ~CollectOriginsHelper() = default;
768 NS_IMETHOD
769 Run() override;
772 /*******************************************************************************
773 * Other class declarations
774 ******************************************************************************/
776 class StoragePressureRunnable final : public Runnable {
777 const uint64_t mUsage;
779 public:
780 explicit StoragePressureRunnable(uint64_t aUsage)
781 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
782 mUsage(aUsage) {}
784 private:
785 ~StoragePressureRunnable() = default;
787 NS_DECL_NSIRUNNABLE
790 class RecordTimeDeltaHelper final : public Runnable {
791 const Telemetry::HistogramID mHistogram;
793 // TimeStamps that are set on the IO thread.
794 LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
795 LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
797 // A TimeStamp that is set on the main thread.
798 LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
800 public:
801 explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram)
802 : Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram) {}
804 TimeStamp Start();
806 TimeStamp End();
808 private:
809 ~RecordTimeDeltaHelper() = default;
811 NS_DECL_NSIRUNNABLE
814 /*******************************************************************************
815 * Helper classes
816 ******************************************************************************/
818 /*******************************************************************************
819 * Helper Functions
820 ******************************************************************************/
822 // Return whether the group was actually updated.
823 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
824 OriginMetadata& aOriginMetadata) {
825 MOZ_ASSERT(!NS_IsMainThread());
827 bool updated = false;
829 if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
830 if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
831 aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
832 updated = true;
834 } else {
835 nsCOMPtr<nsIPrincipal> principal =
836 BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
837 QM_TRY(MOZ_TO_RESULT(principal));
839 QM_TRY_INSPECT(const auto& baseDomain,
840 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
841 GetBaseDomain));
843 const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
845 if (aOriginMetadata.mGroup != upToDateGroup) {
846 aOriginMetadata.mGroup = upToDateGroup;
847 updated = true;
851 return updated;
854 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
855 FullOriginMetadata& aFullOriginMetadata) {
856 MOZ_ASSERT(!NS_IsMainThread());
858 if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
859 QuotaManager* quotaManager = QuotaManager::Get();
860 MOZ_ASSERT(quotaManager);
862 QM_TRY_INSPECT(const auto& metadataFile,
863 quotaManager->GetOriginDirectory(aFullOriginMetadata));
865 QM_TRY(MOZ_TO_RESULT(
866 metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
868 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
869 metadataFile, GetLastModifiedTime));
871 // Need to convert from milliseconds to microseconds.
872 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
873 timestamp *= int64_t(PR_USEC_PER_MSEC);
875 aFullOriginMetadata.mLastAccessTime = timestamp;
877 return true;
880 return false;
883 } // namespace
885 BackgroundThreadObject::BackgroundThreadObject()
886 : mOwningThread(GetCurrentSerialEventTarget()) {
887 AssertIsOnOwningThread();
890 BackgroundThreadObject::BackgroundThreadObject(
891 nsISerialEventTarget* aOwningThread)
892 : mOwningThread(aOwningThread) {}
894 #ifdef DEBUG
896 void BackgroundThreadObject::AssertIsOnOwningThread() const {
897 AssertIsOnBackgroundThread();
898 MOZ_ASSERT(mOwningThread);
899 bool current;
900 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
901 MOZ_ASSERT(current);
904 #endif // DEBUG
906 nsISerialEventTarget* BackgroundThreadObject::OwningThread() const {
907 MOZ_ASSERT(mOwningThread);
908 return mOwningThread;
911 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
912 // Get leaf of file path
913 for (const char* p = aFile; *p; ++p) {
914 if (*p == '/' && *(p + 1)) {
915 aFile = p + 1;
919 nsContentUtils::LogSimpleConsoleError(
920 NS_ConvertUTF8toUTF16(
921 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
922 "quota"_ns,
923 false /* Quota Manager is not active in private browsing mode */,
924 true /* Quota Manager runs always in a chrome context */);
927 namespace {
929 bool gInvalidateQuotaCache = false;
930 StaticAutoPtr<nsString> gBasePath;
931 StaticAutoPtr<nsString> gStorageName;
932 StaticAutoPtr<nsCString> gBuildId;
934 #ifdef DEBUG
935 bool gQuotaManagerInitialized = false;
936 #endif
938 StaticRefPtr<QuotaManager> gInstance;
939 mozilla::Atomic<bool> gShutdown(false);
941 // A time stamp that can only be accessed on the main thread.
942 TimeStamp gLastOSWake;
944 // XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
945 // separate and includable file.
946 using NormalOriginOpArray =
947 nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
948 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
950 class StorageOperationBase {
951 protected:
952 struct OriginProps {
953 enum Type { eChrome, eContent, eObsolete, eInvalid };
955 NotNull<nsCOMPtr<nsIFile>> mDirectory;
956 nsString mLeafName;
957 nsCString mSpec;
958 OriginAttributes mAttrs;
959 int64_t mTimestamp;
960 OriginMetadata mOriginMetadata;
961 nsCString mOriginalSuffix;
963 LazyInitializedOnceEarlyDestructible<const PersistenceType>
964 mPersistenceType;
965 Type mType;
966 bool mNeedsRestore;
967 bool mNeedsRestore2;
968 bool mIgnore;
970 public:
971 explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
972 : mDirectory(std::move(aDirectory)),
973 mTimestamp(0),
974 mType(eContent),
975 mNeedsRestore(false),
976 mNeedsRestore2(false),
977 mIgnore(false) {}
979 template <typename PersistenceTypeFunc>
980 nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
983 nsTArray<OriginProps> mOriginProps;
985 nsCOMPtr<nsIFile> mDirectory;
987 public:
988 explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
989 AssertIsOnIOThread();
992 NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
994 protected:
995 virtual ~StorageOperationBase() = default;
997 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
998 nsACString& aGroup, nsACString& aOrigin,
999 Nullable<bool>& aIsApp);
1001 // Upgrade helper to load the contents of ".metadata-v2" files from previous
1002 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
1003 // method, it is only intended to read current version ".metadata-v2" files.
1004 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
1005 // because our "storage.sqlite" lets us track the overall version of the
1006 // storage directory.
1007 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
1008 nsACString& aSuffix, nsACString& aGroup,
1009 nsACString& aOrigin, bool& aIsApp);
1011 int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
1013 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
1016 * Rename the origin if the origin string generation from nsIPrincipal
1017 * changed. This consists of renaming the origin in the metadata files and
1018 * renaming the origin directory itself. For simplicity, the origin in
1019 * metadata files is not actually updated, but the metadata files are
1020 * recreated instead.
1022 * @param aOriginProps the properties of the origin to check.
1024 * @return whether origin was renamed.
1026 Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
1028 nsresult ProcessOriginDirectories();
1030 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
1033 class RepositoryOperationBase : public StorageOperationBase {
1034 public:
1035 explicit RepositoryOperationBase(nsIFile* aDirectory)
1036 : StorageOperationBase(aDirectory) {}
1038 nsresult ProcessRepository();
1040 protected:
1041 virtual ~RepositoryOperationBase() = default;
1043 template <typename UpgradeMethod>
1044 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
1045 UpgradeMethod aMethod);
1047 private:
1048 virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
1050 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1051 bool* aRemoved) = 0;
1053 virtual nsresult PrepareClientDirectory(nsIFile* aFile,
1054 const nsAString& aLeafName,
1055 bool& aRemoved);
1058 class CreateOrUpgradeDirectoryMetadataHelper final
1059 : public RepositoryOperationBase {
1060 nsCOMPtr<nsIFile> mPermanentStorageDir;
1062 // The legacy PersistenceType, before the default repository introduction.
1063 enum class LegacyPersistenceType {
1064 Persistent = 0,
1065 Temporary
1066 // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
1067 // it here.
1070 LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
1072 public:
1073 explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
1074 : RepositoryOperationBase(aDirectory) {}
1076 nsresult Init();
1078 private:
1079 Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
1080 const fallible_t&);
1082 PersistenceType PersistenceTypeFromLegacyPersistentSpec(
1083 const nsCString& aSpec);
1085 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1087 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
1089 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1090 bool* aRemoved) override;
1092 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1095 class UpgradeStorageHelperBase : public RepositoryOperationBase {
1096 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1098 public:
1099 explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
1100 : RepositoryOperationBase(aDirectory) {}
1102 nsresult Init();
1104 private:
1105 PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
1108 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
1109 public:
1110 explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
1111 : UpgradeStorageHelperBase(aDirectory) {}
1113 private:
1114 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1115 bool* aRemoved) override;
1117 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1120 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
1121 public:
1122 explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
1123 : UpgradeStorageHelperBase(aDirectory) {}
1125 private:
1126 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
1129 * Remove the origin directory if appId is present in origin attributes.
1131 * @param aOriginProps the properties of the origin to check.
1133 * @return whether the origin directory was removed.
1135 Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
1137 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1138 bool* aRemoved) override;
1140 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1143 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
1144 public:
1145 explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
1146 : UpgradeStorageHelperBase(aDirectory) {}
1148 private:
1149 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1150 bool* aRemoved) override;
1152 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1155 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
1156 public:
1157 explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
1158 : UpgradeStorageHelperBase(aDirectory) {}
1160 private:
1161 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1162 bool* aRemoved) override;
1164 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1166 nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
1167 bool& aRemoved) override;
1170 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
1171 LazyInitializedOnce<const PersistenceType> mPersistenceType;
1173 public:
1174 explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
1175 : StorageOperationBase(aDirectory) {}
1177 nsresult Init();
1179 nsresult RestoreMetadata2File();
1181 private:
1182 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1185 Result<nsAutoString, nsresult> GetPathForStorage(
1186 nsIFile& aBaseDir, const nsAString& aStorageName) {
1187 QM_TRY_INSPECT(const auto& storageDir,
1188 CloneFileAndAppend(aBaseDir, aStorageName));
1190 QM_TRY_RETURN(
1191 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
1194 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
1195 AssertIsOnIOThread();
1197 class MOZ_STACK_CLASS Helper final {
1198 public:
1199 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
1200 AssertIsOnIOThread();
1201 MOZ_ASSERT(aFile);
1202 MOZ_ASSERT(aTimestamp);
1204 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
1206 switch (dirEntryKind) {
1207 case nsIFileKind::ExistsAsDirectory:
1208 QM_TRY(CollectEachFile(
1209 *aFile,
1210 [&aTimestamp](const nsCOMPtr<nsIFile>& file)
1211 -> Result<mozilla::Ok, nsresult> {
1212 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));
1214 return Ok{};
1215 }));
1216 break;
1218 case nsIFileKind::ExistsAsFile: {
1219 QM_TRY_INSPECT(const auto& leafName,
1220 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
1221 GetLeafName));
1223 // Bug 1595445 will handle unknown files here.
1225 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
1226 IsDotFile(leafName)) {
1227 return NS_OK;
1230 QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
1231 aFile, GetLastModifiedTime));
1233 // Need to convert from milliseconds to microseconds.
1234 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
1235 timestamp *= int64_t(PR_USEC_PER_MSEC);
1237 if (timestamp > *aTimestamp) {
1238 *aTimestamp = timestamp;
1240 break;
1243 case nsIFileKind::DoesNotExist:
1244 // Ignore files that got removed externally while iterating.
1245 break;
1248 return NS_OK;
1252 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1253 return PR_Now();
1256 int64_t timestamp = INT64_MIN;
1257 nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
1258 if (NS_FAILED(rv)) {
1259 timestamp = PR_Now();
1262 // XXX if there were no suitable files for getting last modified time
1263 // (timestamp is still set to INT64_MIN), we should return the current time
1264 // instead of returning INT64_MIN.
1266 return timestamp;
1269 // Returns a bool indicating whether the directory was newly created.
1270 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
1271 AssertIsOnIOThread();
1273 // Callers call this function without checking if the directory already
1274 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
1275 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
1276 // reports.
1277 QM_TRY_INSPECT(const auto& exists,
1278 QM_OR_ELSE_LOG_VERBOSE_IF(
1279 // Expression.
1280 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
1281 nsIFile::DIRECTORY_TYPE, 0755,
1282 /* aSkipAncestors = */ false)
1283 .map([](Ok) { return false; }),
1284 // Predicate.
1285 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
1286 // Fallback.
1287 ErrToOk<true>));
1289 if (exists) {
1290 QM_TRY_INSPECT(const bool& isDirectory,
1291 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
1292 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
1295 return !exists;
1298 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
1299 aJarPrefix.Truncate();
1301 // Fallback.
1302 if (!aInIsolatedMozBrowser) {
1303 return;
1306 // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
1307 // 1320404).
1308 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
1309 aJarPrefix.AppendInt(0); // TODO: this is the appId, to be removed.
1310 aJarPrefix.Append('+');
1311 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
1312 aJarPrefix.Append('+');
1315 // This method computes and returns our best guess for the temporary storage
1316 // limit (in bytes), based on disk capacity.
1317 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
1318 // The fixed limit pref can be used to override temporary storage limit
1319 // calculation.
1320 if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
1321 return static_cast<uint64_t>(
1322 StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
1323 1024;
1326 constexpr int64_t teraByte = (1024LL * 1024LL * 1024LL * 1024LL);
1327 constexpr int64_t maxAllowedCapacity = 8LL * teraByte;
1329 // Check for disk capacity of user's device on which storage directory lives.
1330 int64_t diskCapacity = maxAllowedCapacity;
1332 // Log error when default disk capacity is returned due to the error
1333 QM_WARNONLY_TRY(MOZ_TO_RESULT(aStorageDir.GetDiskCapacity(&diskCapacity)));
1335 MOZ_ASSERT(diskCapacity >= 0LL);
1337 // Allow temporary storage to consume up to 50% of disk capacity.
1338 int64_t capacityLimit = diskCapacity / 2LL;
1340 // If the disk capacity reported by the operating system is very
1341 // large and potentially incorrect due to hardware issues,
1342 // a hardcoded limit is supplied instead.
1343 QM_WARNONLY_TRY(
1344 OkIf(capacityLimit < maxAllowedCapacity),
1345 ([&capacityLimit](const auto&) { capacityLimit = maxAllowedCapacity; }));
1347 return capacityLimit;
1350 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
1351 const int64_t aRecentTime) {
1352 if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
1353 return false;
1356 return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
1357 StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
1360 } // namespace
1362 /*******************************************************************************
1363 * Exported functions
1364 ******************************************************************************/
1366 void InitializeQuotaManager() {
1367 MOZ_ASSERT(XRE_IsParentProcess());
1368 MOZ_ASSERT(NS_IsMainThread());
1369 MOZ_ASSERT(!gQuotaManagerInitialized);
1371 if (!QuotaManager::IsRunningGTests()) {
1372 // These services have to be started on the main thread currently.
1373 const nsCOMPtr<mozIStorageService> ss =
1374 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
1375 QM_WARNONLY_TRY(OkIf(ss));
1377 RefPtr<net::ExtensionProtocolHandler> extensionProtocolHandler =
1378 net::ExtensionProtocolHandler::GetSingleton();
1379 QM_WARNONLY_TRY(MOZ_TO_RESULT(extensionProtocolHandler));
1382 QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
1384 #ifdef DEBUG
1385 gQuotaManagerInitialized = true;
1386 #endif
1389 void InitializeScopedLogExtraInfo() {
1390 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
1391 ScopedLogExtraInfo::Initialize();
1392 #endif
1395 bool RecvShutdownQuotaManager() {
1396 AssertIsOnBackgroundThread();
1398 // If we are already in shutdown, don't call ShutdownInstance()
1399 // again and return true immediately. We shall see this incident
1400 // in Telemetry.
1401 // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
1402 // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
1403 QM_TRY(OkIf(!gShutdown), true);
1405 QuotaManager::ShutdownInstance();
1407 return true;
1410 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
1412 // static
1413 nsresult QuotaManager::Observer::Initialize() {
1414 MOZ_ASSERT(NS_IsMainThread());
1416 RefPtr<Observer> observer = new Observer();
1418 nsresult rv = observer->Init();
1419 if (NS_WARN_IF(NS_FAILED(rv))) {
1420 return rv;
1423 sInstance = observer;
1425 return NS_OK;
1428 // static
1429 nsIObserver* QuotaManager::Observer::GetInstance() {
1430 MOZ_ASSERT(NS_IsMainThread());
1432 return sInstance;
1435 // static
1436 void QuotaManager::Observer::ShutdownCompleted() {
1437 MOZ_ASSERT(NS_IsMainThread());
1438 MOZ_ASSERT(sInstance);
1440 sInstance->mShutdownComplete = true;
1443 nsresult QuotaManager::Observer::Init() {
1444 MOZ_ASSERT(NS_IsMainThread());
1446 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1447 if (NS_WARN_IF(!obs)) {
1448 return NS_ERROR_FAILURE;
1451 // XXX: Improve the way that we remove observer in failure cases.
1452 nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1453 if (NS_WARN_IF(NS_FAILED(rv))) {
1454 return rv;
1457 rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
1458 if (NS_WARN_IF(NS_FAILED(rv))) {
1459 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1460 return rv;
1463 rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
1464 if (NS_WARN_IF(NS_FAILED(rv))) {
1465 obs->RemoveObserver(this, kProfileDoChangeTopic);
1466 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1467 return rv;
1470 rv = obs->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
1471 if (NS_WARN_IF(NS_FAILED(rv))) {
1472 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
1473 obs->RemoveObserver(this, kProfileDoChangeTopic);
1474 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1475 return rv;
1478 rv = obs->AddObserver(this, kPrivateBrowsingObserverTopic, false);
1479 if (NS_WARN_IF(NS_FAILED(rv))) {
1480 obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1481 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
1482 obs->RemoveObserver(this, kProfileDoChangeTopic);
1483 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1484 return rv;
1487 return NS_OK;
1490 nsresult QuotaManager::Observer::Shutdown() {
1491 MOZ_ASSERT(NS_IsMainThread());
1493 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1494 if (NS_WARN_IF(!obs)) {
1495 return NS_ERROR_FAILURE;
1498 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kPrivateBrowsingObserverTopic));
1499 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
1500 MOZ_ALWAYS_SUCCEEDS(
1501 obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
1502 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
1503 MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
1505 sInstance = nullptr;
1507 // In general, the instance will have died after the latter removal call, so
1508 // it's not safe to do anything after that point.
1509 // However, Shutdown is currently called from Observe which is called by the
1510 // Observer Service which holds a strong reference to the observer while the
1511 // Observe method is being called.
1513 return NS_OK;
1516 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
1518 NS_IMETHODIMP
1519 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
1520 const char16_t* aData) {
1521 MOZ_ASSERT(NS_IsMainThread());
1523 nsresult rv;
1525 if (!strcmp(aTopic, kProfileDoChangeTopic)) {
1526 if (NS_WARN_IF(gBasePath)) {
1527 NS_WARNING(
1528 "profile-before-change-qm must precede repeated "
1529 "profile-do-change!");
1530 return NS_OK;
1533 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, true);
1535 gBasePath = new nsString();
1537 nsCOMPtr<nsIFile> baseDir;
1538 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
1539 getter_AddRefs(baseDir));
1540 if (NS_FAILED(rv)) {
1541 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1542 getter_AddRefs(baseDir));
1544 if (NS_WARN_IF(NS_FAILED(rv))) {
1545 return rv;
1548 rv = baseDir->GetPath(*gBasePath);
1549 if (NS_WARN_IF(NS_FAILED(rv))) {
1550 return rv;
1553 gStorageName = new nsString();
1555 rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
1556 if (NS_FAILED(rv)) {
1557 *gStorageName = kStorageName;
1560 gBuildId = new nsCString();
1562 nsCOMPtr<nsIPlatformInfo> platformInfo =
1563 do_GetService("@mozilla.org/xre/app-info;1");
1564 if (NS_WARN_IF(!platformInfo)) {
1565 return NS_ERROR_FAILURE;
1568 rv = platformInfo->GetPlatformBuildID(*gBuildId);
1569 if (NS_WARN_IF(NS_FAILED(rv))) {
1570 return rv;
1573 return NS_OK;
1576 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
1577 if (NS_WARN_IF(!gBasePath)) {
1578 NS_WARNING("profile-do-change must precede profile-before-change-qm!");
1579 return NS_OK;
1582 // mPendingProfileChange is our re-entrancy guard (the nested event loop
1583 // below may cause re-entrancy).
1584 if (mPendingProfileChange) {
1585 return NS_OK;
1588 AutoRestore<bool> pending(mPendingProfileChange);
1589 mPendingProfileChange = true;
1591 mShutdownComplete = false;
1593 PBackgroundChild* backgroundActor =
1594 BackgroundChild::GetOrCreateForCurrentThread();
1595 if (NS_WARN_IF(!backgroundActor)) {
1596 return NS_ERROR_FAILURE;
1599 if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
1600 return NS_ERROR_FAILURE;
1603 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
1604 "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
1605 [&]() { return mShutdownComplete; }));
1607 gBasePath = nullptr;
1609 gStorageName = nullptr;
1611 gBuildId = nullptr;
1613 Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, false);
1615 return NS_OK;
1618 if (!strcmp(aTopic, kPrivateBrowsingObserverTopic)) {
1619 auto* const quotaManagerService = QuotaManagerService::GetOrCreate();
1620 if (NS_WARN_IF(!quotaManagerService)) {
1621 return NS_ERROR_FAILURE;
1624 nsCOMPtr<nsIQuotaRequest> request;
1625 rv = quotaManagerService->ClearStoragesForPrivateBrowsing(
1626 nsGetterAddRefs(request));
1627 if (NS_WARN_IF(NS_FAILED(rv))) {
1628 return rv;
1631 return NS_OK;
1634 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1635 rv = Shutdown();
1636 if (NS_WARN_IF(NS_FAILED(rv))) {
1637 return rv;
1640 return NS_OK;
1643 if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
1644 gLastOSWake = TimeStamp::Now();
1646 return NS_OK;
1649 NS_WARNING("Unknown observer topic!");
1650 return NS_OK;
1653 /*******************************************************************************
1654 * Quota manager
1655 ******************************************************************************/
1657 QuotaManager::QuotaManager(const nsAString& aBasePath,
1658 const nsAString& aStorageName)
1659 : mQuotaMutex("QuotaManager.mQuotaMutex"),
1660 mBasePath(aBasePath),
1661 mStorageName(aStorageName),
1662 mTemporaryStorageUsage(0),
1663 mNextDirectoryLockId(0),
1664 mShutdownStorageOpCount(0),
1665 mStorageInitialized(false),
1666 mTemporaryStorageInitialized(false),
1667 mTemporaryStorageInitializedInternal(false),
1668 mCacheUsable(false) {
1669 AssertIsOnOwningThread();
1670 MOZ_ASSERT(!gInstance);
1673 QuotaManager::~QuotaManager() {
1674 AssertIsOnOwningThread();
1675 MOZ_ASSERT(!gInstance || gInstance == this);
1678 // static
1679 nsresult QuotaManager::Initialize() {
1680 MOZ_ASSERT(NS_IsMainThread());
1682 nsresult rv = Observer::Initialize();
1683 if (NS_WARN_IF(NS_FAILED(rv))) {
1684 return rv;
1687 return NS_OK;
1690 // static
1691 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
1692 QuotaManager::GetOrCreate() {
1693 AssertIsOnBackgroundThread();
1695 if (gInstance) {
1696 return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
1699 QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
1700 NS_WARNING(
1701 "Trying to create QuotaManager before profile-do-change! "
1702 "Forgot to call do_get_profile()?");
1705 QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
1706 MOZ_ASSERT(false,
1707 "Trying to create QuotaManager after profile-before-change-qm!");
1710 auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
1712 QM_TRY(MOZ_TO_RESULT(instance->Init()));
1714 gInstance = instance;
1716 // Do this before clients have a chance to acquire a directory lock for the
1717 // private repository.
1718 gInstance->ClearPrivateRepository();
1720 return WrapMovingNotNullUnchecked(std::move(instance));
1723 Result<Ok, nsresult> QuotaManager::EnsureCreated() {
1724 AssertIsOnBackgroundThread();
1726 QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
1729 // static
1730 QuotaManager* QuotaManager::Get() {
1731 // Does not return an owning reference.
1732 return gInstance;
1735 // static
1736 nsIObserver* QuotaManager::GetObserver() {
1737 MOZ_ASSERT(NS_IsMainThread());
1739 return Observer::GetInstance();
1742 // static
1743 bool QuotaManager::IsShuttingDown() { return gShutdown; }
1745 // static
1746 void QuotaManager::ShutdownInstance() {
1747 AssertIsOnBackgroundThread();
1749 if (gInstance) {
1750 auto recordTimeDeltaHelper =
1751 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_SHUTDOWN_TIME_V0);
1753 recordTimeDeltaHelper->Start();
1755 gInstance->Shutdown();
1757 recordTimeDeltaHelper->End();
1759 gInstance = nullptr;
1760 } else {
1761 // If we were never initialized, just set the flag to avoid late creation.
1762 gShutdown = true;
1765 RefPtr<Runnable> runnable =
1766 NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
1767 []() { Observer::ShutdownCompleted(); });
1768 MOZ_ASSERT(runnable);
1770 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
1773 // static
1774 void QuotaManager::Reset() {
1775 AssertIsOnBackgroundThread();
1776 MOZ_ASSERT(!gInstance);
1777 MOZ_ASSERT(gShutdown);
1779 gShutdown = false;
1782 // static
1783 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
1784 return mozilla::dom::quota::IsOSMetadata(aFileName);
1787 // static
1788 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
1789 return mozilla::dom::quota::IsDotFile(aFileName);
1792 void QuotaManager::RegisterNormalOriginOp(
1793 NormalOriginOperationBase& aNormalOriginOp) {
1794 AssertIsOnBackgroundThread();
1796 if (!gNormalOriginOps) {
1797 gNormalOriginOps = new NormalOriginOpArray();
1800 gNormalOriginOps->AppendElement(&aNormalOriginOp);
1803 void QuotaManager::UnregisterNormalOriginOp(
1804 NormalOriginOperationBase& aNormalOriginOp) {
1805 AssertIsOnBackgroundThread();
1806 MOZ_ASSERT(gNormalOriginOps);
1808 gNormalOriginOps->RemoveElement(&aNormalOriginOp);
1810 if (gNormalOriginOps->IsEmpty()) {
1811 gNormalOriginOps = nullptr;
1815 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
1816 AssertIsOnOwningThread();
1818 mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
1820 if (aLock.ShouldUpdateLockIdTable()) {
1821 MutexAutoLock lock(mQuotaMutex);
1823 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
1824 mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
1825 WrapNotNullUnchecked(&aLock));
1828 if (aLock.ShouldUpdateLockTable()) {
1829 DirectoryLockTable& directoryLockTable =
1830 GetDirectoryLockTable(aLock.GetPersistenceType());
1832 // XXX It seems that the contents of the array are never actually used, we
1833 // just use that like an inefficient use counter. Can't we just change
1834 // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
1835 directoryLockTable
1836 .LookupOrInsertWith(
1837 aLock.Origin(),
1838 [this, &aLock] {
1839 if (!IsShuttingDown()) {
1840 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1841 aLock.OriginMetadata());
1843 return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
1845 ->AppendElement(WrapNotNullUnchecked(&aLock));
1848 aLock.SetRegistered(true);
1851 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
1852 AssertIsOnOwningThread();
1854 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
1856 if (aLock.ShouldUpdateLockIdTable()) {
1857 MutexAutoLock lock(mQuotaMutex);
1859 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
1860 mDirectoryLockIdTable.Remove(aLock.Id());
1863 if (aLock.ShouldUpdateLockTable()) {
1864 DirectoryLockTable& directoryLockTable =
1865 GetDirectoryLockTable(aLock.GetPersistenceType());
1867 // ClearDirectoryLockTables may have been called, so the element or entire
1868 // array may not exist anymre.
1869 nsTArray<NotNull<DirectoryLockImpl*>>* array;
1870 if (directoryLockTable.Get(aLock.Origin(), &array) &&
1871 array->RemoveElement(&aLock) && array->IsEmpty()) {
1872 directoryLockTable.Remove(aLock.Origin());
1874 if (!IsShuttingDown()) {
1875 UpdateOriginAccessTime(aLock.GetPersistenceType(),
1876 aLock.OriginMetadata());
1881 aLock.SetRegistered(false);
1884 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
1885 AssertIsOnOwningThread();
1887 mPendingDirectoryLocks.AppendElement(&aLock);
1890 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
1891 AssertIsOnOwningThread();
1893 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
1896 uint64_t QuotaManager::CollectOriginsForEviction(
1897 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
1898 AssertIsOnOwningThread();
1899 MOZ_ASSERT(aLocks.IsEmpty());
1901 // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
1902 // or maybe a generalization if that.
1904 struct MOZ_STACK_CLASS Helper final {
1905 static void GetInactiveOriginInfos(
1906 const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
1907 const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
1908 OriginInfosFlatTraversable& aInactiveOriginInfos) {
1909 for (const auto& originInfo : aOriginInfos) {
1910 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
1911 PERSISTENCE_TYPE_PERSISTENT);
1913 if (originInfo->LockedPersisted()) {
1914 continue;
1917 // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
1918 // moz-extension origin, unlike websites (which may more likely using
1919 // the local data as a cache but still able to retrieve the same data
1920 // from the server side) extensions do not have the same data stored
1921 // anywhere else and evicting the data would result into potential data
1922 // loss for the users.
1924 // Also, unlike a website the extensions are explicitly installed and
1925 // uninstalled by the user and all data associated to the extension
1926 // principal will be completely removed once the addon is uninstalled.
1927 if (originInfo->mGroupInfo->mPersistenceType !=
1928 PERSISTENCE_TYPE_TEMPORARY &&
1929 originInfo->IsExtensionOrigin()) {
1930 continue;
1933 const auto originScope = OriginScope::FromOrigin(originInfo->mOrigin);
1935 const bool match =
1936 std::any_of(aLocks.begin(), aLocks.end(),
1937 [&originScope](const DirectoryLockImpl* const lock) {
1938 return originScope.Matches(lock->GetOriginScope());
1941 if (!match) {
1942 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count(),
1943 "Inactive origin shouldn't have open files!");
1944 aInactiveOriginInfos.InsertElementSorted(
1945 originInfo, OriginInfoAccessTimeComparator());
1951 // Split locks into separate arrays and filter out locks for persistent
1952 // storage, they can't block us.
1953 auto [temporaryStorageLocks, defaultStorageLocks,
1954 privateStorageLocks] = [this] {
1955 nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
1956 nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
1957 nsTArray<NotNull<const DirectoryLockImpl*>> privateStorageLocks;
1959 for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
1960 const Nullable<PersistenceType>& persistenceType =
1961 lock->NullablePersistenceType();
1963 if (persistenceType.IsNull()) {
1964 temporaryStorageLocks.AppendElement(lock);
1965 defaultStorageLocks.AppendElement(lock);
1966 } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
1967 temporaryStorageLocks.AppendElement(lock);
1968 } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
1969 defaultStorageLocks.AppendElement(lock);
1970 } else if (persistenceType.Value() == PERSISTENCE_TYPE_PRIVATE) {
1971 privateStorageLocks.AppendElement(lock);
1972 } else {
1973 MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
1975 // Do nothing here, persistent origins don't need to be collected ever.
1979 return std::make_tuple(std::move(temporaryStorageLocks),
1980 std::move(defaultStorageLocks),
1981 std::move(privateStorageLocks));
1982 }();
1984 // Enumerate and process inactive origins. This must be protected by the
1985 // mutex.
1986 MutexAutoLock lock(mQuotaMutex);
1988 const auto [inactiveOrigins, sizeToBeFreed] =
1989 [this, &temporaryStorageLocks = temporaryStorageLocks,
1990 &defaultStorageLocks = defaultStorageLocks,
1991 &privateStorageLocks = privateStorageLocks, aMinSizeToBeFreed] {
1992 nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
1993 for (const auto& entry : mGroupInfoPairs) {
1994 const auto& pair = entry.GetData();
1996 MOZ_ASSERT(!entry.GetKey().IsEmpty());
1997 MOZ_ASSERT(pair);
1999 RefPtr<GroupInfo> groupInfo =
2000 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2001 if (groupInfo) {
2002 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
2003 temporaryStorageLocks,
2004 inactiveOrigins);
2007 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2008 if (groupInfo) {
2009 Helper::GetInactiveOriginInfos(
2010 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
2013 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2014 if (groupInfo) {
2015 Helper::GetInactiveOriginInfos(
2016 groupInfo->mOriginInfos, privateStorageLocks, inactiveOrigins);
2020 #ifdef DEBUG
2021 // Make sure the array is sorted correctly.
2022 const bool inactiveOriginsSorted =
2023 std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
2024 [](const auto& lhs, const auto& rhs) {
2025 return lhs->mAccessTime < rhs->mAccessTime;
2027 MOZ_ASSERT(inactiveOriginsSorted);
2028 #endif
2030 // Create a list of inactive and the least recently used origins
2031 // whose aggregate size is greater or equals the minimal size to be
2032 // freed.
2033 uint64_t sizeToBeFreed = 0;
2034 for (uint32_t count = inactiveOrigins.Length(), index = 0;
2035 index < count; index++) {
2036 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2037 inactiveOrigins.TruncateLength(index);
2038 break;
2041 sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
2044 return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
2045 }();
2047 if (sizeToBeFreed >= aMinSizeToBeFreed) {
2048 // Success, add directory locks for these origins, so any other
2049 // operations for them will be delayed (until origin eviction is finalized).
2051 for (const auto& originInfo : inactiveOrigins) {
2052 auto lock = DirectoryLockImpl::CreateForEviction(
2053 WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
2054 originInfo->FlattenToOriginMetadata());
2056 lock->AcquireImmediately();
2058 aLocks.AppendElement(lock.forget());
2061 return sizeToBeFreed;
2064 return 0;
2067 nsresult QuotaManager::Init() {
2068 AssertIsOnOwningThread();
2070 #ifdef XP_WIN
2071 CacheUseDOSDevicePathSyntaxPrefValue();
2072 #endif
2074 QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
2076 QM_TRY_UNWRAP(
2077 do_Init(mIndexedDBPath),
2078 GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
2080 QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
2082 QM_TRY_UNWRAP(do_Init(mStoragePath),
2083 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
2085 QM_TRY_UNWRAP(
2086 do_Init(mStorageArchivesPath),
2087 GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
2089 QM_TRY_UNWRAP(
2090 do_Init(mPermanentStoragePath),
2091 GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
2093 QM_TRY_UNWRAP(
2094 do_Init(mTemporaryStoragePath),
2095 GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
2097 QM_TRY_UNWRAP(
2098 do_Init(mDefaultStoragePath),
2099 GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
2101 QM_TRY_UNWRAP(
2102 do_Init(mPrivateStoragePath),
2103 GetPathForStorage(*baseDir, nsLiteralString(PRIVATE_DIRECTORY_NAME)));
2105 QM_TRY_UNWRAP(
2106 do_Init(mToBeRemovedStoragePath),
2107 GetPathForStorage(*baseDir, nsLiteralString(TOBEREMOVED_DIRECTORY_NAME)));
2109 QM_TRY_UNWRAP(do_Init(mIOThread),
2110 MOZ_TO_RESULT_INVOKE_TYPED(
2111 nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
2112 "QuotaManager IO"));
2114 static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
2115 Client::FILESYSTEM == 3 && Client::LS == 4 &&
2116 Client::TYPE_MAX == 5,
2117 "Fix the registration!");
2119 // Register clients.
2120 auto clients = decltype(mClients)::ValueType{};
2121 clients.AppendElement(indexedDB::CreateQuotaClient());
2122 clients.AppendElement(cache::CreateQuotaClient());
2123 clients.AppendElement(simpledb::CreateQuotaClient());
2124 clients.AppendElement(fs::CreateQuotaClient());
2125 if (NextGenLocalStorageEnabled()) {
2126 clients.AppendElement(localstorage::CreateQuotaClient());
2127 } else {
2128 clients.SetLength(Client::TypeMax());
2131 mClients.init(std::move(clients));
2133 MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
2134 "Should be using an auto array with correct capacity!");
2136 mAllClientTypes.init(ClientTypesArray{
2137 Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB,
2138 Client::Type::FILESYSTEM, Client::Type::LS});
2139 mAllClientTypesExceptLS.init(
2140 ClientTypesArray{Client::Type::IDB, Client::Type::DOMCACHE,
2141 Client::Type::SDB, Client::Type::FILESYSTEM});
2143 return NS_OK;
2146 // static
2147 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
2148 const Client::Type aClientType, const nsACString& aStepDescription) {
2149 // Callable on any thread.
2151 auto* const quotaManager = QuotaManager::Get();
2152 MOZ_DIAGNOSTIC_ASSERT(quotaManager);
2154 if (quotaManager->IsShuttingDown()) {
2155 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2159 // static
2160 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
2161 const Client::Type aClientType, const nsACString& aStepDescription) {
2162 // Callable on any thread.
2164 auto* const quotaManager = QuotaManager::Get();
2166 if (quotaManager && quotaManager->IsShuttingDown()) {
2167 quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
2171 void QuotaManager::RecordQuotaManagerShutdownStep(
2172 const nsACString& aStepDescription) {
2173 // Callable on any thread.
2174 MOZ_ASSERT(IsShuttingDown());
2176 RecordShutdownStep(Nothing{}, aStepDescription);
2179 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
2180 const nsACString& aStepDescription) {
2181 // Callable on any thread.
2183 if (IsShuttingDown()) {
2184 RecordQuotaManagerShutdownStep(aStepDescription);
2188 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
2189 const nsACString& aStepDescription) {
2190 MOZ_ASSERT(IsShuttingDown());
2192 const TimeDuration elapsedSinceShutdownStart =
2193 TimeStamp::NowLoRes() - *mShutdownStartedAt;
2195 const auto stepString =
2196 nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
2197 nsPromiseFlatCString(aStepDescription).get());
2199 if (aClientType) {
2200 AssertIsOnBackgroundThread();
2202 mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
2203 } else {
2204 // Callable on any thread.
2205 MutexAutoLock lock(mQuotaMutex);
2207 mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
2210 #ifdef DEBUG
2211 // XXX Probably this isn't the mechanism that should be used here.
2213 NS_DebugBreak(
2214 NS_DEBUG_WARNING,
2215 nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
2216 : "quota manager"_ns + " shutdown step"_ns)
2217 .get(),
2218 stepString.get(), __FILE__, __LINE__);
2219 #endif
2222 void QuotaManager::Shutdown() {
2223 AssertIsOnOwningThread();
2224 MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
2226 // Define some local helper functions
2228 auto flagShutdownStarted = [this]() {
2229 mShutdownStartedAt.init(TimeStamp::NowLoRes());
2231 // Setting this flag prevents the service from being recreated and prevents
2232 // further storages from being created.
2233 gShutdown = true;
2236 nsCOMPtr<nsITimer> crashBrowserTimer;
2238 auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2239 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2241 nsCString annotation;
2243 for (Client::Type type : quotaManager->AllClientTypes()) {
2244 auto& quotaClient = *(*quotaManager->mClients)[type];
2246 if (!quotaClient.IsShutdownCompleted()) {
2247 annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
2248 Client::TypeToText(type).get(),
2249 quotaClient.GetShutdownStatus().get(),
2250 quotaManager->mShutdownSteps[type].get());
2254 if (gNormalOriginOps) {
2255 annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
2256 gNormalOriginOps->Length());
2257 #ifdef QM_COLLECTING_OPERATION_TELEMETRY
2258 for (const auto& op : *gNormalOriginOps) {
2259 annotation.AppendPrintf("Op: %s pending\n", op->Name());
2261 #endif
2264 MutexAutoLock lock(quotaManager->mQuotaMutex);
2266 annotation.AppendPrintf("Intermediate steps:\n%s\n",
2267 quotaManager->mQuotaManagerShutdownSteps.get());
2270 CrashReporter::AnnotateCrashReport(
2271 CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
2273 MOZ_CRASH("Quota manager shutdown timed out");
2276 auto startCrashBrowserTimer = [&]() {
2277 crashBrowserTimer = NS_NewTimer();
2278 MOZ_ASSERT(crashBrowserTimer);
2279 if (crashBrowserTimer) {
2280 RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
2281 MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
2282 crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
2283 nsITimer::TYPE_ONE_SHOT,
2284 "quota::QuotaManager::Shutdown::crashBrowserTimer"));
2288 auto stopCrashBrowserTimer = [&]() {
2289 if (crashBrowserTimer) {
2290 RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
2291 QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
2295 auto initiateShutdownWorkThreads = [this]() {
2296 RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
2297 bool needsToWait = false;
2298 for (Client::Type type : AllClientTypes()) {
2299 // Clients are supposed to also AbortAllOperations from this point on
2300 // to speed up shutdown, if possible. Thus pending operations
2301 // might not be executed anymore.
2302 needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
2305 return needsToWait;
2308 nsCOMPtr<nsITimer> killActorsTimer;
2310 auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
2311 auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
2313 quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
2315 // XXX: This abort is a workaround to unblock shutdown, which
2316 // ought to be removed by bug 1682326. We probably need more
2317 // checks to immediately abort new operations during
2318 // shutdown.
2319 quotaManager->GetClient(Client::IDB)->AbortAllOperations();
2321 for (Client::Type type : quotaManager->AllClientTypes()) {
2322 quotaManager->GetClient(type)->ForceKillActors();
2326 auto startKillActorsTimer = [&]() {
2327 killActorsTimer = NS_NewTimer();
2328 MOZ_ASSERT(killActorsTimer);
2329 if (killActorsTimer) {
2330 RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
2331 MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
2332 killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
2333 nsITimer::TYPE_ONE_SHOT,
2334 "quota::QuotaManager::Shutdown::killActorsTimer"));
2338 auto stopKillActorsTimer = [&]() {
2339 if (killActorsTimer) {
2340 RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
2341 QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
2345 auto isAllClientsShutdownComplete = [this] {
2346 return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
2347 [&self = *this](const auto type) {
2348 return (*self.mClients)[type]->IsShutdownCompleted();
2352 auto shutdownAndJoinWorkThreads = [this]() {
2353 RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
2354 for (Client::Type type : AllClientTypes()) {
2355 (*mClients)[type]->FinalizeShutdownWorkThreads();
2359 auto shutdownAndJoinIOThread = [this]() {
2360 RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
2362 // Make sure to join with our IO thread.
2363 QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
2366 auto invalidatePendingDirectoryLocks = [this]() {
2367 RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
2368 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
2369 lock->Invalidate();
2373 // Body of the function
2375 ScopedLogExtraInfo scope{ScopedLogExtraInfo::kTagContext,
2376 "dom::quota::QuotaManager::Shutdown"_ns};
2378 // This must be called before `flagShutdownStarted`, it would fail otherwise.
2379 // `ShutdownStorageOp` needs to acquire an exclusive directory lock over
2380 // entire <profile>/storage which will abort any existing operations and wait
2381 // for all existing directory locks to be released. So the shutdown operation
2382 // will effectively run after all existing operations.
2383 // We don't need to use the returned promise here because `ShutdownStorage`
2384 // registers `ShudownStorageOp` in `gNormalOriginOps`.
2385 ShutdownStorage();
2387 flagShutdownStarted();
2389 startCrashBrowserTimer();
2391 // XXX: StopIdleMaintenance now just notifies all clients to abort any
2392 // maintenance work.
2393 // This could be done as part of QuotaClient::AbortAllOperations.
2394 StopIdleMaintenance();
2396 // XXX In theory, we could simplify the code below (and also the `Client`
2397 // interface) by removing the `initiateShutdownWorkThreads` and
2398 // `isAllClientsShutdownComplete` calls because it should be sufficient
2399 // to rely on `ShutdownStorage` to abort all existing operations and to
2400 // wait for all existing directory locks to be released as well.
2402 const bool needsToWait =
2403 initiateShutdownWorkThreads() || static_cast<bool>(gNormalOriginOps);
2405 // If any clients cannot shutdown immediately, spin the event loop while we
2406 // wait on all the threads to close.
2407 if (needsToWait) {
2408 startKillActorsTimer();
2410 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
2411 "QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
2412 return !gNormalOriginOps && isAllClientsShutdownComplete();
2413 }));
2415 stopKillActorsTimer();
2418 shutdownAndJoinWorkThreads();
2420 shutdownAndJoinIOThread();
2422 invalidatePendingDirectoryLocks();
2424 stopCrashBrowserTimer();
2427 void QuotaManager::InitQuotaForOrigin(
2428 const FullOriginMetadata& aFullOriginMetadata,
2429 const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
2430 AssertIsOnIOThread();
2431 MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
2433 MutexAutoLock lock(mQuotaMutex);
2435 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2436 aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
2437 aFullOriginMetadata.mGroup);
2439 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2440 groupInfo, aFullOriginMetadata.mOrigin,
2441 aFullOriginMetadata.mStorageOrigin, aFullOriginMetadata.mIsPrivate,
2442 aClientUsages, aUsageBytes, aFullOriginMetadata.mLastAccessTime,
2443 aFullOriginMetadata.mPersisted,
2444 /* aDirectoryExists */ true));
2447 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata& aOriginMetadata) {
2448 AssertIsOnIOThread();
2449 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
2451 MutexAutoLock lock(mQuotaMutex);
2453 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2454 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
2455 aOriginMetadata.mGroup);
2457 RefPtr<OriginInfo> originInfo =
2458 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2459 if (!originInfo) {
2460 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2461 groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
2462 aOriginMetadata.mIsPrivate, ClientUsageArray(),
2463 /* aUsageBytes */ 0,
2464 /* aAccessTime */ PR_Now(), /* aPersisted */ false,
2465 /* aDirectoryExists */ false));
2469 int64_t QuotaManager::NoteOriginDirectoryCreated(
2470 const OriginMetadata& aOriginMetadata, bool aPersisted) {
2471 AssertIsOnIOThread();
2472 MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
2474 int64_t timestamp;
2476 MutexAutoLock lock(mQuotaMutex);
2478 RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
2479 aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
2480 aOriginMetadata.mGroup);
2482 RefPtr<OriginInfo> originInfo =
2483 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2484 if (originInfo) {
2485 timestamp = originInfo->LockedAccessTime();
2486 originInfo->mPersisted = aPersisted;
2487 originInfo->mDirectoryExists = true;
2488 } else {
2489 timestamp = PR_Now();
2490 groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
2491 groupInfo, aOriginMetadata.mOrigin, aOriginMetadata.mStorageOrigin,
2492 aOriginMetadata.mIsPrivate, ClientUsageArray(),
2493 /* aUsageBytes */ 0,
2494 /* aAccessTime */ timestamp, aPersisted, /* aDirectoryExists */ true));
2497 return timestamp;
2500 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
2501 int64_t aSize) {
2502 MOZ_ASSERT(!NS_IsMainThread());
2503 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2505 MutexAutoLock lock(mQuotaMutex);
2507 GroupInfoPair* pair;
2508 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2509 return;
2512 RefPtr<GroupInfo> groupInfo =
2513 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2514 if (!groupInfo) {
2515 return;
2518 RefPtr<OriginInfo> originInfo =
2519 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2520 if (originInfo) {
2521 originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
2525 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
2526 MOZ_ASSERT(!NS_IsMainThread());
2527 MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
2529 MutexAutoLock lock(mQuotaMutex);
2531 GroupInfoPair* pair;
2532 if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
2533 return;
2536 RefPtr<GroupInfo> groupInfo =
2537 pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
2538 if (!groupInfo) {
2539 return;
2542 RefPtr<OriginInfo> originInfo =
2543 groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
2544 if (originInfo) {
2545 originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
2549 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
2550 const OriginMetadata& aOriginMetadata,
2551 Client::Type aClientType) {
2552 MOZ_ASSERT(!NS_IsMainThread());
2553 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2555 MutexAutoLock lock(mQuotaMutex);
2557 GroupInfoPair* pair;
2558 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2559 return UsageInfo{};
2562 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2563 if (!groupInfo) {
2564 return UsageInfo{};
2567 RefPtr<OriginInfo> originInfo =
2568 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2569 if (!originInfo) {
2570 return UsageInfo{};
2573 return originInfo->LockedGetUsageForClient(aClientType);
2576 void QuotaManager::UpdateOriginAccessTime(
2577 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
2578 AssertIsOnOwningThread();
2579 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
2580 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
2581 MOZ_ASSERT(!IsShuttingDown());
2583 MutexAutoLock lock(mQuotaMutex);
2585 GroupInfoPair* pair;
2586 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
2587 return;
2590 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2591 if (!groupInfo) {
2592 return;
2595 RefPtr<OriginInfo> originInfo =
2596 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
2597 if (originInfo) {
2598 int64_t timestamp = PR_Now();
2599 originInfo->LockedUpdateAccessTime(timestamp);
2601 MutexAutoUnlock autoUnlock(mQuotaMutex);
2603 auto op = CreateSaveOriginAccessTimeOp(WrapMovingNotNullUnchecked(this),
2604 aOriginMetadata, timestamp);
2606 RegisterNormalOriginOp(*op);
2608 op->RunImmediately();
2612 void QuotaManager::RemoveQuota() {
2613 AssertIsOnIOThread();
2615 MutexAutoLock lock(mQuotaMutex);
2617 for (const auto& entry : mGroupInfoPairs) {
2618 const auto& pair = entry.GetData();
2620 MOZ_ASSERT(!entry.GetKey().IsEmpty());
2621 MOZ_ASSERT(pair);
2623 RefPtr<GroupInfo> groupInfo =
2624 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2625 if (groupInfo) {
2626 groupInfo->LockedRemoveOriginInfos();
2629 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
2630 if (groupInfo) {
2631 groupInfo->LockedRemoveOriginInfos();
2634 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
2635 if (groupInfo) {
2636 groupInfo->LockedRemoveOriginInfos();
2640 mGroupInfoPairs.Clear();
2642 MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
2645 nsresult QuotaManager::LoadQuota() {
2646 AssertIsOnIOThread();
2647 MOZ_ASSERT(mStorageConnection);
2648 MOZ_ASSERT(!mTemporaryStorageInitializedInternal);
2650 // A list of all unaccessed default or temporary origins.
2651 nsTArray<FullOriginMetadata> unaccessedOrigins;
2653 auto MaybeCollectUnaccessedOrigin =
2654 [loadQuotaInfoStartTime = PR_Now(),
2655 &unaccessedOrigins](auto& fullOriginMetadata) {
2656 if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
2657 unaccessedOrigins.AppendElement(std::move(fullOriginMetadata));
2661 auto recordTimeDeltaHelper =
2662 MakeRefPtr<RecordTimeDeltaHelper>(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0);
2664 const auto startTime = recordTimeDeltaHelper->Start();
2666 auto LoadQuotaFromCache = [&]() -> nsresult {
2667 QM_TRY_INSPECT(
2668 const auto& stmt,
2669 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2670 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
2671 "SELECT repository_id, suffix, group_, "
2672 "origin, client_usages, usage, "
2673 "last_access_time, accessed, persisted "
2674 "FROM origin"_ns));
2676 auto autoRemoveQuota = MakeScopeExit([&] {
2677 RemoveQuota();
2678 unaccessedOrigins.Clear();
2681 QM_TRY(quota::CollectWhileHasResult(
2682 *stmt,
2683 [this,
2684 &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
2685 QM_TRY_INSPECT(const int32_t& repositoryId,
2686 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2688 const auto maybePersistenceType =
2689 PersistenceTypeFromInt32(repositoryId, fallible);
2690 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
2692 FullOriginMetadata fullOriginMetadata;
2694 fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
2696 QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
2697 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2698 GetUTF8String, 1));
2700 QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
2701 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2702 GetUTF8String, 2));
2704 QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
2705 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2706 GetUTF8String, 3));
2708 fullOriginMetadata.mStorageOrigin = fullOriginMetadata.mOrigin;
2710 fullOriginMetadata.mIsPrivate = false;
2712 QM_TRY_INSPECT(const auto& clientUsagesText,
2713 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
2714 GetUTF8String, 4));
2716 ClientUsageArray clientUsages;
2717 QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
2719 QM_TRY_INSPECT(const int64_t& usage,
2720 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
2721 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
2722 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
2723 QM_TRY_INSPECT(const int64_t& accessed,
2724 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
2725 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
2726 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
2728 QM_TRY_INSPECT(const bool& groupUpdated,
2729 MaybeUpdateGroupForOrigin(fullOriginMetadata));
2731 Unused << groupUpdated;
2733 QM_TRY_INSPECT(
2734 const bool& lastAccessTimeUpdated,
2735 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
2737 Unused << lastAccessTimeUpdated;
2739 // We don't need to update the .metadata-v2 file on disk here,
2740 // EnsureTemporaryOriginIsInitialized is responsible for doing that.
2741 // We just need to use correct group and last access time before
2742 // initializing quota for the given origin. (Note that calling
2743 // LoadFullOriginMetadataWithRestore below might update the group in
2744 // the metadata file, but only as a side-effect. The actual place we
2745 // ensure consistency is in EnsureTemporaryOriginIsInitialized.)
2747 if (accessed) {
2748 QM_TRY_INSPECT(const auto& directory,
2749 GetOriginDirectory(fullOriginMetadata));
2751 QM_TRY_INSPECT(const bool& exists,
2752 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
2754 QM_TRY(OkIf(exists), Err(NS_ERROR_FILE_NOT_FOUND));
2756 QM_TRY_INSPECT(const bool& isDirectory,
2757 MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
2759 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FILE_DESTINATION_NOT_DIR));
2761 // Calling LoadFullOriginMetadataWithRestore might update the group
2762 // in the metadata file, but only as a side-effect. The actual place
2763 // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
2765 QM_TRY_INSPECT(const auto& metadata,
2766 LoadFullOriginMetadataWithRestore(directory));
2768 QM_WARNONLY_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
2769 metadata.mLastAccessTime));
2771 QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
2772 Err(NS_ERROR_FAILURE));
2774 QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
2775 metadata.mPersistenceType),
2776 Err(NS_ERROR_FAILURE));
2778 QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
2779 Err(NS_ERROR_FAILURE));
2781 QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
2782 Err(NS_ERROR_FAILURE));
2784 QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
2785 Err(NS_ERROR_FAILURE));
2787 QM_TRY(OkIf(fullOriginMetadata.mStorageOrigin ==
2788 metadata.mStorageOrigin),
2789 Err(NS_ERROR_FAILURE));
2791 QM_TRY(OkIf(fullOriginMetadata.mIsPrivate == metadata.mIsPrivate),
2792 Err(NS_ERROR_FAILURE));
2794 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
2795 fullOriginMetadata.mPersistenceType, fullOriginMetadata,
2796 fullOriginMetadata.mLastAccessTime,
2797 fullOriginMetadata.mPersisted, directory)));
2798 } else {
2799 InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
2802 MaybeCollectUnaccessedOrigin(fullOriginMetadata);
2804 return Ok{};
2805 }));
2807 autoRemoveQuota.release();
2809 return NS_OK;
2812 QM_TRY_INSPECT(
2813 const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
2814 if (mCacheUsable) {
2815 QM_TRY_INSPECT(
2816 const auto& stmt,
2817 CreateAndExecuteSingleStepStatement<
2818 SingleStepResult::ReturnNullIfNoResult>(
2819 *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
2821 QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
2823 QM_TRY_INSPECT(const int32_t& valid,
2824 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
2826 if (valid) {
2827 if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
2828 return true;
2831 QM_TRY_INSPECT(const auto& buildId,
2832 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2833 nsAutoCString, stmt, GetUTF8String, 1));
2835 return buildId == *gBuildId;
2839 return false;
2840 }()));
2842 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
2844 if (!loadQuotaFromCache ||
2845 !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
2846 ![&LoadQuotaFromCache] {
2847 QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
2848 return static_cast<bool>(res);
2849 }()) {
2850 // A keeper to defer the return only in Nightly, so that the telemetry data
2851 // for whole profile can be collected.
2852 #ifdef NIGHTLY_BUILD
2853 nsresult statusKeeper = NS_OK;
2854 #endif
2856 const auto statusKeeperFunc = [&](const nsresult rv) {
2857 RECORD_IN_NIGHTLY(statusKeeper, rv);
2860 for (const PersistenceType type :
2861 kInitializableBestEffortPersistenceTypes) {
2862 if (NS_WARN_IF(IsShuttingDown())) {
2863 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
2866 QM_TRY(([&]() -> Result<Ok, nsresult> {
2867 QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
2868 const auto innerFunc = [&](const auto&) -> nsresult {
2869 return InitializeRepository(type,
2870 MaybeCollectUnaccessedOrigin);
2873 return ExecuteInitialization(
2874 type == PERSISTENCE_TYPE_DEFAULT
2875 ? Initialization::DefaultRepository
2876 : Initialization::TemporaryRepository,
2877 innerFunc);
2878 }())),
2879 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
2881 return Ok{};
2882 }()));
2885 #ifdef NIGHTLY_BUILD
2886 if (NS_FAILED(statusKeeper)) {
2887 return statusKeeper;
2889 #endif
2892 autoRemoveQuota.release();
2894 const auto endTime = recordTimeDeltaHelper->End();
2896 if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
2897 static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
2898 StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
2899 !unaccessedOrigins.IsEmpty()) {
2900 QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
2903 return NS_OK;
2906 void QuotaManager::UnloadQuota() {
2907 AssertIsOnIOThread();
2908 MOZ_ASSERT(mStorageConnection);
2909 MOZ_ASSERT(mTemporaryStorageInitializedInternal);
2910 MOZ_ASSERT(mCacheUsable);
2912 auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
2914 mozStorageTransaction transaction(
2915 mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
2917 QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
2919 QM_TRY(MOZ_TO_RESULT(
2920 mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
2921 QM_VOID);
2923 nsCOMPtr<mozIStorageStatement> insertStmt;
2926 MutexAutoLock lock(mQuotaMutex);
2928 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
2929 MOZ_ASSERT(!iter.Key().IsEmpty());
2931 GroupInfoPair* const pair = iter.UserData();
2932 MOZ_ASSERT(pair);
2934 for (const PersistenceType type : kBestEffortPersistenceTypes) {
2935 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
2936 if (!groupInfo) {
2937 continue;
2940 for (const auto& originInfo : groupInfo->mOriginInfos) {
2941 MOZ_ASSERT(!originInfo->mCanonicalQuotaObjects.Count());
2943 if (!originInfo->mDirectoryExists) {
2944 continue;
2947 if (originInfo->mIsPrivate) {
2948 continue;
2951 if (insertStmt) {
2952 MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
2953 } else {
2954 QM_TRY_UNWRAP(
2955 insertStmt,
2956 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2957 nsCOMPtr<mozIStorageStatement>, mStorageConnection,
2958 CreateStatement,
2959 "INSERT INTO origin (repository_id, suffix, group_, "
2960 "origin, client_usages, usage, last_access_time, "
2961 "accessed, persisted) "
2962 "VALUES (:repository_id, :suffix, :group_, :origin, "
2963 ":client_usages, :usage, :last_access_time, :accessed, "
2964 ":persisted)"_ns),
2965 QM_VOID);
2968 QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
2969 QM_VOID);
2971 QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
2974 groupInfo->LockedRemoveOriginInfos();
2977 iter.Remove();
2981 QM_TRY_INSPECT(
2982 const auto& stmt,
2983 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2984 nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
2985 "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
2986 QM_VOID);
2988 QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
2989 QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
2990 QM_VOID);
2991 QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
2992 QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
2995 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
2996 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
2997 Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
2998 int64_t* aFileSizeOut /* = nullptr */) {
2999 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3000 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
3002 if (aFileSizeOut) {
3003 *aFileSizeOut = 0;
3006 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3007 return nullptr;
3010 QM_TRY_INSPECT(const auto& path,
3011 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
3012 nullptr);
3014 #ifdef DEBUG
3016 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
3017 nullptr);
3019 nsAutoString clientType;
3020 QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
3021 nullptr);
3023 QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
3025 QM_TRY_INSPECT(
3026 const auto& directoryPath,
3027 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
3028 nullptr);
3030 MOZ_ASSERT(StringBeginsWith(path, directoryPath));
3032 #endif
3034 QM_TRY_INSPECT(
3035 const int64_t fileSize,
3036 ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
3037 if (aFileSize == -1) {
3038 QM_TRY_INSPECT(const bool& exists,
3039 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
3041 if (exists) {
3042 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
3045 return 0;
3048 return aFileSize;
3049 }()),
3050 nullptr);
3052 RefPtr<QuotaObject> result;
3054 MutexAutoLock lock(mQuotaMutex);
3056 GroupInfoPair* pair;
3057 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
3058 return nullptr;
3061 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3063 if (!groupInfo) {
3064 return nullptr;
3067 RefPtr<OriginInfo> originInfo =
3068 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3070 if (!originInfo) {
3071 return nullptr;
3074 // We need this extra raw pointer because we can't assign to the smart
3075 // pointer directly since QuotaObject::AddRef would try to acquire the same
3076 // mutex.
3077 const NotNull<CanonicalQuotaObject*> canonicalQuotaObject =
3078 originInfo->mCanonicalQuotaObjects.LookupOrInsertWith(path, [&] {
3079 // Create a new QuotaObject. The hashtable is not responsible to
3080 // delete the QuotaObject.
3081 return WrapNotNullUnchecked(new CanonicalQuotaObject(
3082 originInfo, aClientType, path, fileSize));
3085 // Addref the QuotaObject and move the ownership to the result. This must
3086 // happen before we unlock!
3087 result = canonicalQuotaObject->LockedAddRef();
3090 if (aFileSizeOut) {
3091 *aFileSizeOut = fileSize;
3094 // The caller becomes the owner of the QuotaObject, that is, the caller is
3095 // is responsible to delete it when the last reference is removed.
3096 return result.forget();
3099 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3100 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
3101 Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
3102 int64_t* aFileSizeOut /* = nullptr */) {
3103 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3105 if (aFileSizeOut) {
3106 *aFileSizeOut = 0;
3109 QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
3111 return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
3112 aFileSize, aFileSizeOut);
3115 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3116 const int64_t aDirectoryLockId, const nsAString& aPath) {
3117 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3119 Maybe<MutexAutoLock> lock;
3121 // See the comment for mDirectoryLockIdTable in QuotaManager.h
3122 if (!IsOnBackgroundThread()) {
3123 lock.emplace(mQuotaMutex);
3126 if (auto maybeDirectoryLock =
3127 mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
3128 const auto& directoryLock = *maybeDirectoryLock;
3129 MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
3131 const PersistenceType persistenceType = directoryLock->GetPersistenceType();
3132 const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
3133 const Client::Type clientType = directoryLock->ClientType();
3135 lock.reset();
3137 return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
3140 MOZ_ASSERT(aDirectoryLockId == -1);
3141 return nullptr;
3144 Nullable<bool> QuotaManager::OriginPersisted(
3145 const OriginMetadata& aOriginMetadata) {
3146 AssertIsOnIOThread();
3148 MutexAutoLock lock(mQuotaMutex);
3150 RefPtr<OriginInfo> originInfo =
3151 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3152 if (originInfo) {
3153 return Nullable<bool>(originInfo->LockedPersisted());
3156 return Nullable<bool>();
3159 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
3160 AssertIsOnIOThread();
3162 MutexAutoLock lock(mQuotaMutex);
3164 RefPtr<OriginInfo> originInfo =
3165 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
3166 if (originInfo && !originInfo->LockedPersisted()) {
3167 originInfo->LockedPersist();
3171 void QuotaManager::AbortOperationsForLocks(
3172 const DirectoryLockIdTableArray& aLockIds) {
3173 for (Client::Type type : AllClientTypes()) {
3174 if (aLockIds[type].Filled()) {
3175 (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
3180 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
3181 AssertIsOnOwningThread();
3183 for (const RefPtr<Client>& client : *mClients) {
3184 client->AbortOperationsForProcess(aContentParentId);
3188 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetOriginDirectory(
3189 const OriginMetadata& aOriginMetadata) const {
3190 QM_TRY_UNWRAP(
3191 auto directory,
3192 QM_NewLocalFile(GetStoragePath(aOriginMetadata.mPersistenceType)));
3194 QM_TRY(MOZ_TO_RESULT(directory->Append(
3195 MakeSanitizedOriginString(aOriginMetadata.mStorageOrigin))));
3197 return directory;
3200 // static
3201 nsresult QuotaManager::CreateDirectoryMetadata(
3202 nsIFile& aDirectory, int64_t aTimestamp,
3203 const OriginMetadata& aOriginMetadata) {
3204 AssertIsOnIOThread();
3206 OriginAttributes groupAttributes;
3208 nsCString groupNoSuffix;
3209 QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
3210 groupNoSuffix)),
3211 NS_ERROR_FAILURE);
3213 nsCString groupPrefix;
3214 GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
3216 nsCString group = groupPrefix + groupNoSuffix;
3218 OriginAttributes originAttributes;
3220 nsCString originNoSuffix;
3221 QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
3222 originNoSuffix)),
3223 NS_ERROR_FAILURE);
3225 nsCString originPrefix;
3226 GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
3228 nsCString origin = originPrefix + originNoSuffix;
3230 MOZ_ASSERT(groupPrefix == originPrefix);
3232 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3233 nsCOMPtr<nsIFile>, aDirectory, Clone));
3235 QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
3237 QM_TRY_INSPECT(const auto& stream,
3238 GetBinaryOutputStream(*file, FileFlag::Truncate));
3239 MOZ_ASSERT(stream);
3241 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3243 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
3245 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
3247 // Currently unused (used to be isApp).
3248 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
3250 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3252 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3254 QM_TRY(MOZ_TO_RESULT(
3255 file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
3257 return NS_OK;
3260 // static
3261 nsresult QuotaManager::CreateDirectoryMetadata2(
3262 nsIFile& aDirectory, int64_t aTimestamp, bool aPersisted,
3263 const OriginMetadata& aOriginMetadata) {
3264 AssertIsOnIOThread();
3266 QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3267 nsCOMPtr<nsIFile>, aDirectory, Clone));
3269 QM_TRY(
3270 MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
3272 QM_TRY_INSPECT(const auto& stream,
3273 GetBinaryOutputStream(*file, FileFlag::Truncate));
3274 MOZ_ASSERT(stream);
3276 QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
3278 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
3280 // Reserved data 1
3281 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3283 // Reserved data 2
3284 QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
3286 // Currently unused (used to be suffix).
3287 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3289 // Currently unused (used to be group).
3290 QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ("")));
3292 QM_TRY(MOZ_TO_RESULT(
3293 stream->WriteStringZ(aOriginMetadata.mStorageOrigin.get())));
3295 // Currently used for isPrivate (used to be used for isApp).
3296 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aOriginMetadata.mIsPrivate)));
3298 QM_TRY(MOZ_TO_RESULT(stream->Flush()));
3300 QM_TRY(MOZ_TO_RESULT(stream->Close()));
3302 QM_TRY(MOZ_TO_RESULT(
3303 file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
3305 return NS_OK;
3308 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
3309 AssertIsOnIOThread();
3310 MOZ_ASSERT(aDirectory);
3311 MOZ_ASSERT(mStorageConnection);
3313 RefPtr<RestoreDirectoryMetadata2Helper> helper =
3314 new RestoreDirectoryMetadata2Helper(aDirectory);
3316 QM_TRY(MOZ_TO_RESULT(helper->Init()));
3318 QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
3320 return NS_OK;
3323 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
3324 nsIFile* aDirectory, PersistenceType aPersistenceType) {
3325 MOZ_ASSERT(!NS_IsMainThread());
3326 MOZ_ASSERT(aDirectory);
3327 MOZ_ASSERT(mStorageConnection);
3329 QM_TRY_INSPECT(const auto& binaryStream,
3330 GetBinaryInputStream(*aDirectory,
3331 nsLiteralString(METADATA_V2_FILE_NAME)));
3333 FullOriginMetadata fullOriginMetadata;
3335 QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
3336 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
3338 QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
3339 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3341 QM_TRY_INSPECT(const bool& reservedData1,
3342 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3343 Unused << reservedData1;
3345 // XXX Use for the persistence type.
3346 QM_TRY_INSPECT(const bool& reservedData2,
3347 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
3348 Unused << reservedData2;
3350 fullOriginMetadata.mPersistenceType = aPersistenceType;
3352 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3353 nsCString, binaryStream, ReadCString));
3354 Unused << suffix;
3356 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3357 nsCString, binaryStream, ReadCString));
3358 Unused << group;
3360 QM_TRY_UNWRAP(
3361 fullOriginMetadata.mStorageOrigin,
3362 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
3364 // Currently used for isPrivate (used to be used for isApp).
3365 QM_TRY_UNWRAP(fullOriginMetadata.mIsPrivate,
3366 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
3368 QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
3370 auto principal =
3371 [&storageOrigin =
3372 fullOriginMetadata.mStorageOrigin]() -> nsCOMPtr<nsIPrincipal> {
3373 if (storageOrigin.EqualsLiteral(kChromeOrigin)) {
3374 return SystemPrincipal::Get();
3376 return BasePrincipal::CreateContentPrincipal(storageOrigin);
3377 }();
3378 QM_TRY(MOZ_TO_RESULT(principal));
3380 PrincipalInfo principalInfo;
3381 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3383 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3384 Err(NS_ERROR_MALFORMED_URI));
3386 QM_TRY_UNWRAP(auto principalMetadata,
3387 GetInfoFromValidatedPrincipalInfo(principalInfo));
3389 fullOriginMetadata.mSuffix = std::move(principalMetadata.mSuffix);
3390 fullOriginMetadata.mGroup = std::move(principalMetadata.mGroup);
3391 fullOriginMetadata.mOrigin = std::move(principalMetadata.mOrigin);
3393 QM_TRY_INSPECT(const bool& groupUpdated,
3394 MaybeUpdateGroupForOrigin(fullOriginMetadata));
3396 // A workaround for a bug in GetLastModifiedTime implementation which should
3397 // have returned the current time instead of INT64_MIN when there were no
3398 // suitable files for getting last modified time.
3399 QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
3400 MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
3402 if (groupUpdated || lastAccessTimeUpdated) {
3403 // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
3404 // I/O.
3405 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
3406 *aDirectory, fullOriginMetadata.mLastAccessTime,
3407 fullOriginMetadata.mPersisted, fullOriginMetadata)));
3410 return fullOriginMetadata;
3413 Result<FullOriginMetadata, nsresult>
3414 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
3415 // XXX Once the persistence type is stored in the metadata file, this block
3416 // for getting the persistence type from the parent directory name can be
3417 // removed.
3418 nsCOMPtr<nsIFile> parentDir;
3419 QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
3421 const auto maybePersistenceType =
3422 PersistenceTypeFromFile(*parentDir, fallible);
3423 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
3425 const auto& persistenceType = maybePersistenceType.value();
3427 QM_TRY_RETURN(QM_OR_ELSE_WARN(
3428 // Expression.
3429 LoadFullOriginMetadata(aDirectory, persistenceType),
3430 // Fallback.
3431 ([&aDirectory, &persistenceType,
3432 this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
3433 QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
3435 QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
3436 })));
3439 Result<OriginMetadata, nsresult> QuotaManager::GetOriginMetadata(
3440 nsIFile* aDirectory) {
3441 MOZ_ASSERT(aDirectory);
3442 MOZ_ASSERT(mStorageConnection);
3444 QM_TRY_INSPECT(
3445 const auto& leafName,
3446 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aDirectory, GetLeafName));
3448 // XXX Consider using QuotaManager::ParseOrigin here.
3449 nsCString spec;
3450 OriginAttributes attrs;
3451 nsCString originalSuffix;
3452 OriginParser::ResultType result = OriginParser::ParseOrigin(
3453 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
3454 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
3456 QM_TRY_INSPECT(
3457 const auto& principal,
3458 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
3459 if (spec.EqualsLiteral(kChromeOrigin)) {
3460 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
3463 nsCOMPtr<nsIURI> uri;
3464 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
3466 return nsCOMPtr<nsIPrincipal>(
3467 BasePrincipal::CreateContentPrincipal(uri, attrs));
3468 }()));
3469 QM_TRY(MOZ_TO_RESULT(principal));
3471 PrincipalInfo principalInfo;
3472 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
3474 QM_TRY(MOZ_TO_RESULT(IsPrincipalInfoValid(principalInfo)),
3475 Err(NS_ERROR_MALFORMED_URI));
3477 QM_TRY_UNWRAP(auto principalMetadata,
3478 GetInfoFromValidatedPrincipalInfo(principalInfo));
3480 QM_TRY_INSPECT(const auto& parentDirectory,
3481 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIFile>,
3482 aDirectory, GetParent));
3484 const auto maybePersistenceType =
3485 PersistenceTypeFromFile(*parentDirectory, fallible);
3486 QM_TRY(MOZ_TO_RESULT(maybePersistenceType.isSome()));
3488 return OriginMetadata{std::move(principalMetadata),
3489 maybePersistenceType.value()};
3492 Result<Ok, nsresult> QuotaManager::RemoveOriginDirectory(nsIFile& aDirectory) {
3493 AssertIsOnIOThread();
3495 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
3496 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.Remove(true)));
3499 QM_TRY_INSPECT(const auto& toBeRemovedStorageDir,
3500 QM_NewLocalFile(*mToBeRemovedStoragePath));
3502 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*toBeRemovedStorageDir));
3504 (void)created;
3506 QM_TRY_RETURN(MOZ_TO_RESULT(aDirectory.MoveTo(
3507 toBeRemovedStorageDir, NSID_TrimBracketsUTF16(nsID::GenerateUUID()))));
3510 template <typename OriginFunc>
3511 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
3512 OriginFunc&& aOriginFunc) {
3513 AssertIsOnIOThread();
3514 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
3515 aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
3517 QM_TRY_INSPECT(const auto& directory,
3518 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
3520 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
3522 Unused << created;
3524 // A keeper to defer the return only in Nightly, so that the telemetry data
3525 // for whole profile can be collected
3526 #ifdef NIGHTLY_BUILD
3527 nsresult statusKeeper = NS_OK;
3528 #endif
3530 const auto statusKeeperFunc = [&](const nsresult rv) {
3531 RECORD_IN_NIGHTLY(statusKeeper, rv);
3534 struct RenameAndInitInfo {
3535 nsCOMPtr<nsIFile> mOriginDirectory;
3536 FullOriginMetadata mFullOriginMetadata;
3537 int64_t mTimestamp;
3538 bool mPersisted;
3540 nsTArray<RenameAndInitInfo> renameAndInitInfos;
3542 QM_TRY(([&]() -> Result<Ok, nsresult> {
3543 QM_TRY(
3544 CollectEachFile(
3545 *directory,
3546 [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
3547 if (NS_WARN_IF(IsShuttingDown())) {
3548 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
3551 QM_TRY(
3552 ([this, &childDirectory, &renameAndInitInfos,
3553 aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
3554 QM_TRY_INSPECT(
3555 const auto& leafName,
3556 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3557 nsAutoString, childDirectory, GetLeafName));
3559 QM_TRY_INSPECT(const auto& dirEntryKind,
3560 GetDirEntryKind(*childDirectory));
3562 switch (dirEntryKind) {
3563 case nsIFileKind::ExistsAsDirectory: {
3564 QM_TRY_UNWRAP(
3565 auto maybeMetadata,
3566 QM_OR_ELSE_WARN_IF(
3567 // Expression
3568 LoadFullOriginMetadataWithRestore(
3569 childDirectory)
3570 .map([](auto metadata)
3571 -> Maybe<FullOriginMetadata> {
3572 return Some(std::move(metadata));
3574 // Predicate.
3575 IsSpecificError<NS_ERROR_MALFORMED_URI>,
3576 // Fallback.
3577 ErrToDefaultOk<Maybe<FullOriginMetadata>>));
3579 if (!maybeMetadata) {
3580 // Unknown directories during initialization are
3581 // allowed. Just warn if we find them.
3582 UNKNOWN_FILE_WARNING(leafName);
3583 break;
3586 auto metadata = maybeMetadata.extract();
3588 MOZ_ASSERT(metadata.mPersistenceType ==
3589 aPersistenceType);
3591 // FIXME(tt): The check for origin name consistency can
3592 // be removed once we have an upgrade to traverse origin
3593 // directories and check through the directory metadata
3594 // files.
3595 const auto originSanitized =
3596 MakeSanitizedOriginCString(metadata.mOrigin);
3598 NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
3599 if (!originSanitized.Equals(utf8LeafName)) {
3600 QM_WARNING(
3601 "The name of the origin directory (%s) doesn't "
3602 "match the sanitized origin string (%s) in the "
3603 "metadata file!",
3604 utf8LeafName.get(), originSanitized.get());
3606 // If it's the known case, we try to restore the
3607 // origin directory name if it's possible.
3608 if (originSanitized.Equals(utf8LeafName + "."_ns)) {
3609 const int64_t lastAccessTime =
3610 metadata.mLastAccessTime;
3611 const bool persisted = metadata.mPersisted;
3612 renameAndInitInfos.AppendElement(RenameAndInitInfo{
3613 std::move(childDirectory), std::move(metadata),
3614 lastAccessTime, persisted});
3615 break;
3618 // XXXtt: Try to restore the unknown cases base on the
3619 // content for their metadata files. Note that if the
3620 // restore fails, QM should maintain a list and ensure
3621 // they won't be accessed after initialization.
3624 QM_TRY(QM_OR_ELSE_WARN_IF(
3625 // Expression.
3626 MOZ_TO_RESULT(InitializeOrigin(
3627 aPersistenceType, metadata,
3628 metadata.mLastAccessTime, metadata.mPersisted,
3629 childDirectory)),
3630 // Predicate.
3631 IsDatabaseCorruptionError,
3632 // Fallback.
3633 ([&childDirectory](
3634 const nsresult rv) -> Result<Ok, nsresult> {
3635 // If the origin can't be initialized due to
3636 // corruption, this is a permanent
3637 // condition, and we need to remove all data
3638 // for the origin on disk.
3640 QM_TRY(
3641 MOZ_TO_RESULT(childDirectory->Remove(true)));
3643 return Ok{};
3644 })));
3646 std::forward<OriginFunc>(aOriginFunc)(metadata);
3648 break;
3651 case nsIFileKind::ExistsAsFile:
3652 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3653 break;
3656 // Unknown files during initialization are now allowed.
3657 // Just warn if we find them.
3658 UNKNOWN_FILE_WARNING(leafName);
3659 break;
3661 case nsIFileKind::DoesNotExist:
3662 // Ignore files that got removed externally while
3663 // iterating.
3664 break;
3667 return Ok{};
3668 }()),
3669 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3671 return Ok{};
3673 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3675 return Ok{};
3676 }()));
3678 for (auto& info : renameAndInitInfos) {
3679 QM_TRY(([&]() -> Result<Ok, nsresult> {
3680 QM_TRY(([&directory, &info, this, aPersistenceType,
3681 &aOriginFunc]() -> Result<Ok, nsresult> {
3682 const auto originDirName =
3683 MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
3685 // Check if targetDirectory exist.
3686 QM_TRY_INSPECT(const auto& targetDirectory,
3687 CloneFileAndAppend(*directory, originDirName));
3689 QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
3690 targetDirectory, Exists));
3692 if (exists) {
3693 QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
3695 return Ok{};
3698 QM_TRY(MOZ_TO_RESULT(
3699 info.mOriginDirectory->RenameTo(nullptr, originDirName)));
3701 // XXX We don't check corruption here ?
3702 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
3703 aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
3704 info.mPersisted, targetDirectory)));
3706 std::forward<OriginFunc>(aOriginFunc)(info.mFullOriginMetadata);
3708 return Ok{};
3709 }()),
3710 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3712 return Ok{};
3713 }()));
3716 #ifdef NIGHTLY_BUILD
3717 if (NS_FAILED(statusKeeper)) {
3718 return statusKeeper;
3720 #endif
3722 return NS_OK;
3725 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
3726 const OriginMetadata& aOriginMetadata,
3727 int64_t aAccessTime, bool aPersisted,
3728 nsIFile* aDirectory) {
3729 AssertIsOnIOThread();
3731 const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
3733 // We need to initialize directories of all clients if they exists and also
3734 // get the total usage to initialize the quota.
3736 ClientUsageArray clientUsages;
3738 // A keeper to defer the return only in Nightly, so that the telemetry data
3739 // for whole profile can be collected
3740 #ifdef NIGHTLY_BUILD
3741 nsresult statusKeeper = NS_OK;
3742 #endif
3744 QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
3745 RECORD_IN_NIGHTLY(statusKeeper, rv);
3746 }]() -> Result<Ok, nsresult> {
3747 QM_TRY(
3748 CollectEachFile(
3749 *aDirectory,
3750 [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
3751 if (NS_WARN_IF(IsShuttingDown())) {
3752 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
3755 QM_TRY(
3756 ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
3757 &clientUsages]() -> Result<Ok, nsresult> {
3758 QM_TRY_INSPECT(const auto& leafName,
3759 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
3760 nsAutoString, file, GetLeafName));
3762 QM_TRY_INSPECT(const auto& dirEntryKind,
3763 GetDirEntryKind(*file));
3765 switch (dirEntryKind) {
3766 case nsIFileKind::ExistsAsDirectory: {
3767 Client::Type clientType;
3768 const bool ok = Client::TypeFromText(
3769 leafName, clientType, fallible);
3770 if (!ok) {
3771 // Unknown directories during initialization are now
3772 // allowed. Just warn if we find them.
3773 UNKNOWN_FILE_WARNING(leafName);
3774 break;
3777 if (trackQuota) {
3778 QM_TRY_INSPECT(
3779 const auto& usageInfo,
3780 (*mClients)[clientType]->InitOrigin(
3781 aPersistenceType, aOriginMetadata,
3782 /* aCanceled */ Atomic<bool>(false)));
3784 MOZ_ASSERT(!clientUsages[clientType]);
3786 if (usageInfo.TotalUsage()) {
3787 // XXX(Bug 1683863) Until we identify the root cause
3788 // of seemingly converted-from-negative usage
3789 // values, we will just treat them as unset here,
3790 // but log a warning to the browser console.
3791 if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
3792 0) {
3793 clientUsages[clientType] = usageInfo.TotalUsage();
3794 } else {
3795 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
3796 const nsCOMPtr<nsIConsoleService> console =
3797 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
3798 if (console) {
3799 console->LogStringMessage(
3800 nsString(
3801 u"QuotaManager warning: client "_ns +
3802 leafName +
3803 u" reported negative usage for group "_ns +
3804 NS_ConvertUTF8toUTF16(
3805 aOriginMetadata.mGroup) +
3806 u", origin "_ns +
3807 NS_ConvertUTF8toUTF16(
3808 aOriginMetadata.mOrigin))
3809 .get());
3811 #endif
3814 } else {
3815 QM_TRY(MOZ_TO_RESULT(
3816 (*mClients)[clientType]
3817 ->InitOriginWithoutTracking(
3818 aPersistenceType, aOriginMetadata,
3819 /* aCanceled */ Atomic<bool>(false))));
3822 break;
3825 case nsIFileKind::ExistsAsFile:
3826 if (IsOriginMetadata(leafName)) {
3827 break;
3830 if (IsTempMetadata(leafName)) {
3831 QM_TRY(MOZ_TO_RESULT(
3832 file->Remove(/* recursive */ false)));
3834 break;
3837 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3838 break;
3841 // Unknown files during initialization are now allowed.
3842 // Just warn if we find them.
3843 UNKNOWN_FILE_WARNING(leafName);
3844 // Bug 1595448 will handle the case for unknown files
3845 // like idb, cache, or ls.
3846 break;
3848 case nsIFileKind::DoesNotExist:
3849 // Ignore files that got removed externally while
3850 // iterating.
3851 break;
3854 return Ok{};
3855 }()),
3856 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3858 return Ok{};
3860 OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
3862 return Ok{};
3863 }()));
3865 #ifdef NIGHTLY_BUILD
3866 if (NS_FAILED(statusKeeper)) {
3867 return statusKeeper;
3869 #endif
3871 if (trackQuota) {
3872 const auto usage = std::accumulate(
3873 clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
3874 [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
3875 return value + clientUsage.valueOr(0);
3878 // XXX Should we log more information, i.e. the whole clientUsages array, in
3879 // case usage is not valid?
3881 QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
3883 InitQuotaForOrigin(
3884 FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
3885 clientUsages, usage.value());
3888 return NS_OK;
3891 nsresult
3892 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
3893 nsIFile* aIndexedDBDir) {
3894 AssertIsOnIOThread();
3895 MOZ_ASSERT(aIndexedDBDir);
3897 const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
3898 bool isDirectory;
3899 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
3901 if (!isDirectory) {
3902 NS_WARNING("indexedDB entry is not a directory!");
3903 return NS_OK;
3906 auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
3907 if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
3908 return persistentStorageDirOrErr.unwrapErr();
3911 nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
3913 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
3914 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
3916 bool exists;
3917 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
3919 if (exists) {
3920 QM_WARNING("Deleting old <profile>/indexedDB directory!");
3922 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
3924 return NS_OK;
3927 nsCOMPtr<nsIFile> storageDir;
3928 QM_TRY(MOZ_TO_RESULT(
3929 persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
3931 // MoveTo() is atomic if the move happens on the same volume which should
3932 // be our case, so even if we crash in the middle of the operation nothing
3933 // breaks next time we try to initialize.
3934 // However there's a theoretical possibility that the indexedDB directory
3935 // is on different volume, but it should be rare enough that we don't have
3936 // to worry about it.
3937 QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
3938 storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
3940 return NS_OK;
3943 return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
3944 innerFunc);
3947 nsresult
3948 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
3949 nsIFile* aPersistentStorageDir) {
3950 AssertIsOnIOThread();
3951 MOZ_ASSERT(aPersistentStorageDir);
3953 const auto innerFunc = [this,
3954 &aPersistentStorageDir](const auto&) -> nsresult {
3955 QM_TRY_INSPECT(
3956 const bool& isDirectory,
3957 MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
3959 if (!isDirectory) {
3960 NS_WARNING("persistent entry is not a directory!");
3961 return NS_OK;
3965 QM_TRY_INSPECT(const auto& defaultStorageDir,
3966 QM_NewLocalFile(*mDefaultStoragePath));
3968 QM_TRY_INSPECT(const bool& exists,
3969 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
3971 if (exists) {
3972 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
3974 QM_TRY(MOZ_TO_RESULT(
3975 aPersistentStorageDir->Remove(/* aRecursive */ true)));
3977 return NS_OK;
3982 // Create real metadata files for origin directories in persistent
3983 // storage.
3984 auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
3985 aPersistentStorageDir);
3987 QM_TRY(MOZ_TO_RESULT(helper->Init()));
3989 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
3991 // Upgrade metadata files for origin directories in temporary storage.
3992 QM_TRY_INSPECT(const auto& temporaryStorageDir,
3993 QM_NewLocalFile(*mTemporaryStoragePath));
3995 QM_TRY_INSPECT(const bool& exists,
3996 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
3998 if (exists) {
3999 QM_TRY_INSPECT(
4000 const bool& isDirectory,
4001 MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
4003 if (!isDirectory) {
4004 NS_WARNING("temporary entry is not a directory!");
4005 return NS_OK;
4008 helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
4009 temporaryStorageDir);
4011 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4013 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4017 // And finally rename persistent to default.
4018 QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
4019 nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
4021 return NS_OK;
4024 return ExecuteInitialization(
4025 Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
4028 template <typename Helper>
4029 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
4030 const int32_t aNewVersion,
4031 mozIStorageConnection* aConnection) {
4032 AssertIsOnIOThread();
4033 MOZ_ASSERT(aNewVersion > aOldVersion);
4034 MOZ_ASSERT(aNewVersion <= kStorageVersion);
4035 MOZ_ASSERT(aConnection);
4037 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
4038 QM_TRY_UNWRAP(auto directory,
4039 QM_NewLocalFile(GetStoragePath(persistenceType)));
4041 QM_TRY_INSPECT(const bool& exists,
4042 MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
4044 if (!exists) {
4045 continue;
4048 RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
4050 QM_TRY(MOZ_TO_RESULT(helper->Init()));
4052 QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
4055 #ifdef DEBUG
4057 QM_TRY_INSPECT(const int32_t& storageVersion,
4058 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4060 MOZ_ASSERT(storageVersion == aOldVersion);
4062 #endif
4064 QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
4066 return NS_OK;
4069 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
4070 mozIStorageConnection* aConnection) {
4071 AssertIsOnIOThread();
4072 MOZ_ASSERT(aConnection);
4074 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4075 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
4076 0, MakeStorageVersion(1, 0), aConnection)));
4078 return NS_OK;
4081 return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
4082 innerFunc);
4085 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
4086 mozIStorageConnection* aConnection) {
4087 AssertIsOnIOThread();
4088 MOZ_ASSERT(aConnection);
4090 // The upgrade consists of a number of logically distinct bugs that
4091 // intentionally got fixed at the same time to trigger just one major
4092 // version bump.
4095 // Morgue directory cleanup
4096 // [Feature/Bug]:
4097 // The original bug that added "on demand" morgue cleanup is 1165119.
4099 // [Mutations]:
4100 // Morgue directories are removed from all origin directories during the
4101 // upgrade process. Origin initialization and usage calculation doesn't try
4102 // to remove morgue directories anymore.
4104 // [Downgrade-incompatible changes]:
4105 // Morgue directories can reappear if user runs an already upgraded profile
4106 // in an older version of Firefox. Morgue directories then prevent current
4107 // Firefox from initializing and using the storage.
4110 // App data removal
4111 // [Feature/Bug]:
4112 // The bug that removes isApp flags is 1311057.
4114 // [Mutations]:
4115 // Origin directories with appIds are removed during the upgrade process.
4117 // [Downgrade-incompatible changes]:
4118 // Origin directories with appIds can reappear if user runs an already
4119 // upgraded profile in an older version of Firefox. Origin directories with
4120 // appIds don't prevent current Firefox from initializing and using the
4121 // storage, but they wouldn't ever be removed again, potentially causing
4122 // problems once appId is removed from origin attributes.
4125 // Strip obsolete origin attributes
4126 // [Feature/Bug]:
4127 // The bug that strips obsolete origin attributes is 1314361.
4129 // [Mutations]:
4130 // Origin directories with obsolete origin attributes are renamed and their
4131 // metadata files are updated during the upgrade process.
4133 // [Downgrade-incompatible changes]:
4134 // Origin directories with obsolete origin attributes can reappear if user
4135 // runs an already upgraded profile in an older version of Firefox. Origin
4136 // directories with obsolete origin attributes don't prevent current Firefox
4137 // from initializing and using the storage, but they wouldn't ever be upgraded
4138 // again, potentially causing problems in future.
4141 // File manager directory renaming (client specific)
4142 // [Feature/Bug]:
4143 // The original bug that added "on demand" file manager directory renaming is
4144 // 1056939.
4146 // [Mutations]:
4147 // All file manager directories are renamed to contain the ".files" suffix.
4149 // [Downgrade-incompatible changes]:
4150 // File manager directories with the ".files" suffix prevent older versions of
4151 // Firefox from initializing and using the storage.
4152 // File manager directories without the ".files" suffix can appear if user
4153 // runs an already upgraded profile in an older version of Firefox. File
4154 // manager directories without the ".files" suffix then prevent current
4155 // Firefox from initializing and using the storage.
4157 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4158 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
4159 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
4161 return NS_OK;
4164 return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
4165 innerFunc);
4168 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
4169 mozIStorageConnection* aConnection) {
4170 AssertIsOnIOThread();
4171 MOZ_ASSERT(aConnection);
4173 // The upgrade is mainly to create a directory padding file in DOM Cache
4174 // directory to record the overall padding size of an origin.
4176 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4177 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
4178 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
4180 return NS_OK;
4183 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
4184 innerFunc);
4187 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
4188 mozIStorageConnection* aConnection) {
4189 AssertIsOnIOThread();
4190 MOZ_ASSERT(aConnection);
4192 // The upgrade is mainly to clean obsolete origins in the repositoies, remove
4193 // asmjs client, and ".tmp" file in the idb folers.
4195 const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
4196 QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
4197 MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
4199 return NS_OK;
4202 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
4203 innerFunc);
4206 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
4207 mozIStorageConnection* aConnection) {
4208 AssertIsOnIOThread();
4209 MOZ_ASSERT(aConnection);
4211 const auto innerFunc = [&aConnection](const auto&) -> nsresult {
4212 // Table `database`
4213 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4214 nsLiteralCString("CREATE TABLE database"
4215 "( cache_version INTEGER NOT NULL DEFAULT 0"
4216 ");"))));
4218 QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
4219 nsLiteralCString("INSERT INTO database (cache_version) "
4220 "VALUES (0)"))));
4222 #ifdef DEBUG
4224 QM_TRY_INSPECT(
4225 const int32_t& storageVersion,
4226 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4228 MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
4230 #endif
4232 QM_TRY(
4233 MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
4235 return NS_OK;
4238 return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
4239 innerFunc);
4242 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
4243 nsIFile& aLsArchiveFile) {
4244 AssertIsOnIOThread();
4245 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
4247 QM_TRY_INSPECT(const bool& exists,
4248 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4250 if (!exists) {
4251 // If the ls archive doesn't exist then ls directories can't exist either.
4252 return NS_OK;
4255 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4257 InvalidateQuotaCache();
4259 // Finally remove the ls archive, so we don't have to check all origin
4260 // directories next time this method is called.
4261 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4263 return NS_OK;
4266 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
4267 AssertIsOnIOThread();
4269 QM_TRY_INSPECT(const auto& defaultStorageDir,
4270 QM_NewLocalFile(*mDefaultStoragePath));
4272 QM_TRY_INSPECT(const bool& exists,
4273 MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
4275 if (!exists) {
4276 return NS_OK;
4279 QM_TRY(CollectEachFile(
4280 *defaultStorageDir,
4281 [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
4282 #ifdef DEBUG
4284 QM_TRY_INSPECT(const bool& exists,
4285 MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
4286 MOZ_ASSERT(exists);
4288 #endif
4290 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
4292 switch (dirEntryKind) {
4293 case nsIFileKind::ExistsAsDirectory: {
4294 QM_TRY_INSPECT(
4295 const auto& lsDir,
4296 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
4297 LS_DIRECTORY_NAME)));
4300 QM_TRY_INSPECT(const bool& exists,
4301 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
4303 if (!exists) {
4304 return Ok{};
4309 QM_TRY_INSPECT(const bool& isDirectory,
4310 MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
4312 if (!isDirectory) {
4313 QM_WARNING("ls entry is not a directory!");
4315 return Ok{};
4319 nsString path;
4320 QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
4322 QM_WARNING("Deleting %s directory!",
4323 NS_ConvertUTF16toUTF8(path).get());
4325 QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
4327 break;
4330 case nsIFileKind::ExistsAsFile: {
4331 QM_TRY_INSPECT(const auto& leafName,
4332 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4333 nsAutoString, originDir, GetLeafName));
4335 // Unknown files during upgrade are allowed. Just warn if we find
4336 // them.
4337 if (!IsOSMetadata(leafName)) {
4338 UNKNOWN_FILE_WARNING(leafName);
4341 break;
4344 case nsIFileKind::DoesNotExist:
4345 // Ignore files that got removed externally while iterating.
4346 break;
4348 return Ok{};
4349 }));
4351 return NS_OK;
4354 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
4355 nsIFile& aLsArchiveFile) const {
4356 AssertIsOnIOThread();
4357 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4359 #ifdef DEBUG
4361 QM_TRY_INSPECT(const bool& exists,
4362 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4363 MOZ_ASSERT(!exists);
4365 #endif
4367 // Get the storage service first, we will need it at multiple places.
4368 QM_TRY_INSPECT(const auto& ss,
4369 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4370 MOZ_SELECT_OVERLOAD(do_GetService),
4371 MOZ_STORAGE_SERVICE_CONTRACTID));
4373 // Get the web apps store file.
4374 QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
4376 QM_TRY(MOZ_TO_RESULT(
4377 webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
4379 // Now check if the web apps store is useable.
4380 QM_TRY_INSPECT(const auto& connection,
4381 CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
4383 if (connection) {
4384 // Find out the journal mode.
4385 QM_TRY_INSPECT(const auto& stmt,
4386 CreateAndExecuteSingleStepStatement(
4387 *connection, "PRAGMA journal_mode;"_ns));
4389 QM_TRY_INSPECT(const auto& journalMode,
4390 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
4391 GetUTF8String, 0));
4393 QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
4395 if (journalMode.EqualsLiteral("wal")) {
4396 // We don't copy the WAL file, so make sure the old database is fully
4397 // checkpointed.
4398 QM_TRY(MOZ_TO_RESULT(
4399 connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
4402 // Explicitely close the connection before the old database is copied.
4403 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4405 // Copy the old database. The database is copied from
4406 // <profile>/webappsstore.sqlite to
4407 // <profile>/storage/ls-archive-tmp.sqlite
4408 // We use a "-tmp" postfix since we are not done yet.
4409 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4411 QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
4412 storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
4414 QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
4415 GetLocalStorageArchiveTmpFile(*mStoragePath));
4417 if (journalMode.EqualsLiteral("wal")) {
4418 QM_TRY_INSPECT(
4419 const auto& lsArchiveTmpConnection,
4420 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4421 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4422 lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
4424 // The archive will only be used for lazy data migration. There won't be
4425 // any concurrent readers and writers that could benefit from Write-Ahead
4426 // Logging. So switch to a standard rollback journal. The standard
4427 // rollback journal also provides atomicity across multiple attached
4428 // databases which is import for the lazy data migration to work safely.
4429 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
4430 "PRAGMA journal_mode = DELETE;"_ns)));
4432 // Close the connection explicitly. We are going to rename the file below.
4433 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
4436 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
4437 QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
4438 nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
4440 return Ok{};
4443 // If webappsstore database is not useable, just create an empty archive.
4444 // XXX The code below should be removed and the caller should call us only
4445 // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
4446 // reworked to propagate database corruption instead of returning null
4447 // connection.
4448 // So, if there's no webappsstore.sqlite
4449 // MaybeCreateOrUpgradeLocalStorageArchive will call
4450 // CreateEmptyLocalStorageArchive instead of
4451 // CopyLocalStorageArchiveFromWebAppsStore.
4452 // If there's any corruption detected during
4453 // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
4454 // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
4455 // EnsureStorageIsInitializedInternal will fallback to
4456 // CreateEmptyLocalStorageArchive.
4458 // Ensure the storage directory actually exists.
4459 QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
4461 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
4463 Unused << created;
4465 QM_TRY_UNWRAP(auto lsArchiveConnection,
4466 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4467 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4468 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4470 QM_TRY(MOZ_TO_RESULT(
4471 StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
4473 return Ok{};
4476 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4477 QuotaManager::CreateLocalStorageArchiveConnection(
4478 nsIFile& aLsArchiveFile) const {
4479 AssertIsOnIOThread();
4480 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4482 #ifdef DEBUG
4484 QM_TRY_INSPECT(const bool& exists,
4485 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4486 MOZ_ASSERT(exists);
4488 #endif
4490 QM_TRY_INSPECT(const bool& isDirectory,
4491 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
4493 // A directory with the name of the archive file is treated as corruption
4494 // (similarly as wrong content of the file).
4495 QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
4497 QM_TRY_INSPECT(const auto& ss,
4498 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4499 MOZ_SELECT_OVERLOAD(do_GetService),
4500 MOZ_STORAGE_SERVICE_CONTRACTID));
4502 // This may return NS_ERROR_FILE_CORRUPTED too.
4503 QM_TRY_UNWRAP(auto connection,
4504 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4505 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4506 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4508 // The legacy LS implementation removes the database and creates an empty one
4509 // when the schema can't be updated. The same effect can be achieved here by
4510 // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
4511 // sub test case 3 of dom/localstorage/test/unit/test_archive.js
4512 QM_TRY(
4513 MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
4514 .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
4516 return connection;
4519 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4520 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
4521 nsIFile& aLsArchiveFile) {
4522 AssertIsOnIOThread();
4523 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4525 QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
4527 #ifdef DEBUG
4529 QM_TRY_INSPECT(const bool& exists,
4530 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4532 MOZ_ASSERT(exists);
4534 #endif
4536 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
4538 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4540 QM_TRY_UNWRAP(auto connection,
4541 CreateLocalStorageArchiveConnection(aLsArchiveFile));
4543 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4545 return connection;
4548 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4549 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
4550 AssertIsOnIOThread();
4551 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4553 QM_TRY_UNWRAP(auto connection,
4554 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4556 QM_TRY(MOZ_TO_RESULT(
4557 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
4559 return connection;
4562 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
4563 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
4564 nsIFile& aLsArchiveFile) {
4565 AssertIsOnIOThread();
4566 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4568 QM_TRY_UNWRAP(auto connection,
4569 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4571 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
4573 return connection;
4577 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
4578 nsCOMPtr<mozIStorageConnection>& aConnection) {
4579 AssertIsOnIOThread();
4580 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4582 nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
4583 if (NS_WARN_IF(NS_FAILED(rv))) {
4584 return rv;
4587 return NS_OK;
4591 #ifdef DEBUG
4593 void QuotaManager::AssertStorageIsInitializedInternal() const {
4594 AssertIsOnIOThread();
4595 MOZ_ASSERT(IsStorageInitializedInternal());
4598 #endif // DEBUG
4600 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
4601 nsIFile& aStorageFile) {
4602 AssertIsOnIOThread();
4604 QM_TRY_INSPECT(const auto& storageFileExists,
4605 MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
4607 if (!storageFileExists) {
4608 QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
4610 QM_TRY_INSPECT(const auto& indexedDBDirExists,
4611 MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
4613 if (indexedDBDirExists) {
4614 QM_TRY(MOZ_TO_RESULT(
4615 UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
4616 indexedDBDir)));
4619 QM_TRY_INSPECT(const auto& persistentStorageDir,
4620 QM_NewLocalFile(*mStoragePath));
4622 QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
4623 nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
4625 QM_TRY_INSPECT(const auto& persistentStorageDirExists,
4626 MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
4628 if (persistentStorageDirExists) {
4629 QM_TRY(MOZ_TO_RESULT(
4630 UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
4631 persistentStorageDir)));
4635 return NS_OK;
4638 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
4639 mozIStorageConnection& aConnection) {
4640 AssertIsOnIOThread();
4642 QM_TRY_UNWRAP(auto storageVersion,
4643 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
4645 // Hacky downgrade logic!
4646 // If we see major.minor of 3.0, downgrade it to be 2.1.
4647 if (storageVersion == kHackyPreDowngradeStorageVersion) {
4648 storageVersion = kHackyPostDowngradeStorageVersion;
4649 QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
4650 QM_PROPAGATE,
4651 [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
4654 QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
4655 NS_ERROR_FAILURE, [](const auto&) {
4656 NS_WARNING("Unable to initialize storage, version is too high!");
4659 if (storageVersion < kStorageVersion) {
4660 const bool newDatabase = !storageVersion;
4662 QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
4664 QM_TRY_INSPECT(const auto& storageDirExists,
4665 MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
4667 const bool newDirectory = !storageDirExists;
4669 if (newDatabase) {
4670 // Set the page size first.
4671 if (kSQLitePageSizeOverride) {
4672 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
4673 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
4677 mozStorageTransaction transaction(
4678 &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4680 QM_TRY(MOZ_TO_RESULT(transaction.Start()));
4682 // An upgrade method can upgrade the database, the storage or both.
4683 // The upgrade loop below can only be avoided when there's no database and
4684 // no storage yet (e.g. new profile).
4685 if (newDatabase && newDirectory) {
4686 QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
4688 #ifdef DEBUG
4690 QM_TRY_INSPECT(
4691 const int32_t& storageVersion,
4692 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
4693 QM_ASSERT_UNREACHABLE);
4694 MOZ_ASSERT(storageVersion == kStorageVersion);
4696 #endif
4698 QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
4699 nsLiteralCString("INSERT INTO database (cache_version) "
4700 "VALUES (0)"))));
4701 } else {
4702 // This logic needs to change next time we change the storage!
4703 static_assert(kStorageVersion == int32_t((2 << 16) + 3),
4704 "Upgrade function needed due to storage version increase.");
4706 while (storageVersion != kStorageVersion) {
4707 if (storageVersion == 0) {
4708 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
4709 } else if (storageVersion == MakeStorageVersion(1, 0)) {
4710 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
4711 } else if (storageVersion == MakeStorageVersion(2, 0)) {
4712 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
4713 } else if (storageVersion == MakeStorageVersion(2, 1)) {
4714 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
4715 } else if (storageVersion == MakeStorageVersion(2, 2)) {
4716 QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
4717 } else {
4718 QM_FAIL(NS_ERROR_FAILURE, []() {
4719 NS_WARNING(
4720 "Unable to initialize storage, no upgrade path is "
4721 "available!");
4725 QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
4726 aConnection, GetSchemaVersion));
4729 MOZ_ASSERT(storageVersion == kStorageVersion);
4732 QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
4735 return NS_OK;
4738 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
4739 AssertIsOnIOThread();
4741 QM_TRY_INSPECT(
4742 const auto& lsArchiveTmpFile,
4743 QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
4745 QM_TRY_INSPECT(const bool& exists,
4746 QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
4748 if (exists) {
4749 QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
4752 return Ok{};
4755 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
4756 nsIFile& aLsArchiveFile) {
4757 AssertIsOnIOThread();
4759 QM_TRY_INSPECT(
4760 const bool& lsArchiveFileExisted,
4761 ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
4762 QM_TRY_INSPECT(const bool& exists,
4763 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4765 if (!exists) {
4766 QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
4769 return exists;
4770 }()));
4772 QM_TRY_UNWRAP(auto connection,
4773 CreateLocalStorageArchiveConnection(aLsArchiveFile));
4775 QM_TRY_INSPECT(const auto& initialized,
4776 IsLocalStorageArchiveInitialized(*connection));
4778 if (!initialized) {
4779 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4782 QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
4784 if (version > kLocalStorageArchiveVersion) {
4785 // Close local storage archive connection. We are going to remove underlying
4786 // file.
4787 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4789 // This will wipe the archive and any migrated data and recopy the archive
4790 // from webappsstore.sqlite.
4791 QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
4793 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
4795 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
4796 } else if (version != kLocalStorageArchiveVersion) {
4797 // The version can be zero either when the archive didn't exist or it did
4798 // exist, but the archive was created without any version information.
4799 // We don't need to do any upgrades only if it didn't exist because existing
4800 // archives without version information must be recopied to really fix bug
4801 // 1542104. See also bug 1546305 which introduced archive versions.
4802 if (!lsArchiveFileExisted) {
4803 MOZ_ASSERT(version == 0);
4805 QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
4806 connection, kLocalStorageArchiveVersion)));
4807 } else {
4808 static_assert(kLocalStorageArchiveVersion == 4,
4809 "Upgrade function needed due to LocalStorage archive "
4810 "version increase.");
4812 while (version != kLocalStorageArchiveVersion) {
4813 if (version < 4) {
4814 // Close local storage archive connection. We are going to remove
4815 // underlying file.
4816 QM_TRY(MOZ_TO_RESULT(connection->Close()));
4818 // This won't do an "upgrade" in a normal sense. It will wipe the
4819 // archive and any migrated data and recopy the archive from
4820 // webappsstore.sqlite
4821 QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
4822 aLsArchiveFile));
4823 } /* else if (version == 4) {
4824 QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
4825 } */
4826 else {
4827 QM_FAIL(Err(NS_ERROR_FAILURE), []() {
4828 QM_WARNING(
4829 "Unable to initialize LocalStorage archive, no upgrade path "
4830 "is available!");
4834 QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
4837 MOZ_ASSERT(version == kLocalStorageArchiveVersion);
4841 // At this point, we have finished initializing the local storage archive, and
4842 // can continue storage initialization. We don't know though if the actual
4843 // data in the archive file is readable. We can't do a PRAGMA integrity_check
4844 // here though, because that would be too heavyweight.
4846 return Ok{};
4849 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
4850 nsIFile& aLsArchiveFile) const {
4851 AssertIsOnIOThread();
4853 QM_TRY_INSPECT(const bool& exists,
4854 MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
4856 // If it exists, remove it. It might be a directory, so remove it recursively.
4857 if (exists) {
4858 QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
4860 // XXX If we crash right here, the next session will copy the archive from
4861 // webappsstore.sqlite again!
4862 // XXX Create a marker file before removing the archive which can be
4863 // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
4864 // archive instead of recopying it from webapppstore.sqlite (in other
4865 // words, finishing what was started here).
4868 QM_TRY_INSPECT(const auto& ss,
4869 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4870 MOZ_SELECT_OVERLOAD(do_GetService),
4871 MOZ_STORAGE_SERVICE_CONTRACTID));
4873 QM_TRY_UNWRAP(const auto connection,
4874 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4875 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4876 &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
4878 QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
4880 QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
4882 QM_TRY(MOZ_TO_RESULT(
4883 SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
4885 return Ok{};
4888 RefPtr<BoolPromise> QuotaManager::InitializeStorage() {
4889 AssertIsOnOwningThread();
4891 // If storage is initialized but there's a clear storage or shutdown storage
4892 // operation already scheduled, we can't immediately resolve the promise and
4893 // return from the function because the clear or shutdown storage operation
4894 // uninitializes storage.
4895 if (mStorageInitialized && !mShutdownStorageOpCount) {
4896 return BoolPromise::CreateAndResolve(true, __func__);
4899 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
4900 Nullable<PersistenceType>(), OriginScope::FromNull(),
4901 Nullable<Client::Type>(),
4902 /* aExclusive */ false);
4904 return directoryLock->Acquire()->Then(
4905 GetCurrentSerialEventTarget(), __func__,
4906 [self = RefPtr(this),
4907 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
4908 if (aValue.IsReject()) {
4909 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
4912 return self->InitializeStorage(std::move(directoryLock));
4916 RefPtr<BoolPromise> QuotaManager::InitializeStorage(
4917 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
4918 AssertIsOnOwningThread();
4919 MOZ_ASSERT(aDirectoryLock);
4921 if (mStorageInitialized && !mShutdownStorageOpCount) {
4922 return BoolPromise::CreateAndResolve(true, __func__);
4925 auto initializeStorageOp =
4926 CreateInitOp(WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
4928 RegisterNormalOriginOp(*initializeStorageOp);
4930 initializeStorageOp->RunImmediately();
4932 return initializeStorageOp->OnResults()->Then(
4933 GetCurrentSerialEventTarget(), __func__,
4934 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
4935 if (aValue.IsReject()) {
4936 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
4939 self->mStorageInitialized = true;
4941 return BoolPromise::CreateAndResolve(true, __func__);
4945 RefPtr<BoolPromise> QuotaManager::StorageInitialized() {
4946 AssertIsOnOwningThread();
4948 auto storageInitializedOp =
4949 CreateStorageInitializedOp(WrapMovingNotNullUnchecked(this));
4951 RegisterNormalOriginOp(*storageInitializedOp);
4953 storageInitializedOp->RunImmediately();
4955 return storageInitializedOp->OnResults();
4958 nsresult QuotaManager::EnsureStorageIsInitializedInternal() {
4959 DiagnosticAssertIsOnIOThread();
4961 const auto innerFunc =
4962 [&](const auto& firstInitializationAttempt) -> nsresult {
4963 if (mStorageConnection) {
4964 MOZ_ASSERT(firstInitializationAttempt.Recorded());
4965 return NS_OK;
4968 QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
4969 QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
4971 QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
4973 QM_TRY_INSPECT(const auto& ss,
4974 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
4975 MOZ_SELECT_OVERLOAD(do_GetService),
4976 MOZ_STORAGE_SERVICE_CONTRACTID));
4978 QM_TRY_UNWRAP(
4979 auto connection,
4980 QM_OR_ELSE_WARN_IF(
4981 // Expression.
4982 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4983 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
4984 storageFile, mozIStorageService::CONNECTION_DEFAULT),
4985 // Predicate.
4986 IsDatabaseCorruptionError,
4987 // Fallback.
4988 ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
4990 if (!connection) {
4991 // Nuke the database file.
4992 QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
4994 QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4995 nsCOMPtr<mozIStorageConnection>, ss,
4996 OpenUnsharedDatabase, storageFile,
4997 mozIStorageService::CONNECTION_DEFAULT));
5000 // We want extra durability for this important file.
5001 QM_TRY(MOZ_TO_RESULT(
5002 connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
5004 // Check to make sure that the storage version is correct.
5005 QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
5007 QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
5009 QM_TRY_INSPECT(const auto& lsArchiveFile,
5010 GetLocalStorageArchiveFile(*mStoragePath));
5012 if (CachedNextGenLocalStorageEnabled()) {
5013 QM_TRY(QM_OR_ELSE_WARN_IF(
5014 // Expression.
5015 MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
5016 // Predicate.
5017 IsDatabaseCorruptionError,
5018 // Fallback.
5019 ([&](const nsresult rv) -> Result<Ok, nsresult> {
5020 QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
5021 })));
5022 } else {
5023 QM_TRY(
5024 MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
5027 QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
5029 if (mCacheUsable && gInvalidateQuotaCache) {
5030 QM_TRY(InvalidateCache(*connection));
5032 gInvalidateQuotaCache = false;
5035 mStorageConnection = std::move(connection);
5037 return NS_OK;
5040 return ExecuteInitialization(
5041 Initialization::Storage,
5042 "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
5045 RefPtr<BoolPromise> QuotaManager::TemporaryStorageInitialized() {
5046 AssertIsOnOwningThread();
5048 auto temporaryStorageInitializedOp =
5049 CreateTemporaryStorageInitializedOp(WrapMovingNotNullUnchecked(this));
5051 RegisterNormalOriginOp(*temporaryStorageInitializedOp);
5053 temporaryStorageInitializedOp->RunImmediately();
5055 return temporaryStorageInitializedOp->OnResults();
5058 RefPtr<UniversalDirectoryLockPromise> QuotaManager::OpenStorageDirectory(
5059 const Nullable<PersistenceType>& aPersistenceType,
5060 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
5061 bool aExclusive,
5062 Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut) {
5063 AssertIsOnOwningThread();
5065 RefPtr<UniversalDirectoryLock> storageDirectoryLock;
5067 RefPtr<BoolPromise> storageDirectoryLockPromise;
5069 if (mStorageInitialized && !mShutdownStorageOpCount) {
5070 storageDirectoryLockPromise = BoolPromise::CreateAndResolve(true, __func__);
5071 } else {
5072 storageDirectoryLock = CreateDirectoryLockInternal(
5073 Nullable<PersistenceType>(), OriginScope::FromNull(),
5074 Nullable<Client::Type>(),
5075 /* aExclusive */ false);
5077 storageDirectoryLockPromise = storageDirectoryLock->Acquire();
5080 RefPtr<UniversalDirectoryLock> universalDirectoryLock =
5081 CreateDirectoryLockInternal(aPersistenceType, aOriginScope, aClientType,
5082 aExclusive);
5084 RefPtr<BoolPromise> universalDirectoryLockPromise =
5085 universalDirectoryLock->Acquire();
5087 if (aPendingDirectoryLockOut.isSome()) {
5088 aPendingDirectoryLockOut.ref() = universalDirectoryLock;
5091 return storageDirectoryLockPromise
5092 ->Then(GetCurrentSerialEventTarget(), __func__,
5093 [self = RefPtr(this),
5094 storageDirectoryLock = std::move(storageDirectoryLock)](
5095 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5096 if (aValue.IsReject()) {
5097 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5098 __func__);
5101 if (!storageDirectoryLock) {
5102 return BoolPromise::CreateAndResolve(true, __func__);
5105 return self->InitializeStorage(std::move(storageDirectoryLock));
5107 ->Then(GetCurrentSerialEventTarget(), __func__,
5108 [universalDirectoryLockPromise =
5109 std::move(universalDirectoryLockPromise)](
5110 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5111 if (aValue.IsReject()) {
5112 return BoolPromise::CreateAndReject(aValue.RejectValue(),
5113 __func__);
5116 return std::move(universalDirectoryLockPromise);
5118 ->Then(GetCurrentSerialEventTarget(), __func__,
5119 [universalDirectoryLock = std::move(universalDirectoryLock)](
5120 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5121 if (aValue.IsReject()) {
5122 return UniversalDirectoryLockPromise::CreateAndReject(
5123 aValue.RejectValue(), __func__);
5126 return UniversalDirectoryLockPromise::CreateAndResolve(
5127 std::move(universalDirectoryLock), __func__);
5131 RefPtr<ClientDirectoryLockPromise> QuotaManager::OpenClientDirectory(
5132 const ClientMetadata& aClientMetadata,
5133 Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut) {
5134 AssertIsOnOwningThread();
5136 nsTArray<RefPtr<BoolPromise>> promises;
5138 RefPtr<UniversalDirectoryLock> storageDirectoryLock;
5140 if (!mStorageInitialized || mShutdownStorageOpCount) {
5141 storageDirectoryLock = CreateDirectoryLockInternal(
5142 Nullable<PersistenceType>(), OriginScope::FromNull(),
5143 Nullable<Client::Type>(),
5144 /* aExclusive */ false);
5145 promises.AppendElement(storageDirectoryLock->Acquire());
5148 RefPtr<ClientDirectoryLock> clientDirectoryLock =
5149 CreateDirectoryLock(aClientMetadata, /* aExclusive */ false);
5151 promises.AppendElement(clientDirectoryLock->Acquire());
5153 if (aPendingDirectoryLockOut.isSome()) {
5154 aPendingDirectoryLockOut.ref() = clientDirectoryLock;
5157 return BoolPromise::All(GetCurrentSerialEventTarget(), promises)
5158 ->Then(
5159 GetCurrentSerialEventTarget(), __func__,
5160 [self = RefPtr(this),
5161 storageDirectoryLock = std::move(storageDirectoryLock)](
5162 const CopyableTArray<bool>& aResolveValues) mutable {
5163 if (!storageDirectoryLock) {
5164 return BoolPromise::CreateAndResolve(true, __func__);
5167 return self->InitializeStorage(std::move(storageDirectoryLock));
5169 [](nsresult aRejectValue) {
5170 return BoolPromise::CreateAndReject(aRejectValue, __func__);
5172 ->Then(GetCurrentSerialEventTarget(), __func__,
5173 [clientDirectoryLock = std::move(clientDirectoryLock)](
5174 const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5175 if (aValue.IsReject()) {
5176 return ClientDirectoryLockPromise::CreateAndReject(
5177 aValue.RejectValue(), __func__);
5179 return ClientDirectoryLockPromise::CreateAndResolve(
5180 std::move(clientDirectoryLock), __func__);
5184 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
5185 const ClientMetadata& aClientMetadata, bool aExclusive) {
5186 AssertIsOnOwningThread();
5188 return DirectoryLockImpl::Create(
5189 WrapNotNullUnchecked(this), aClientMetadata.mPersistenceType,
5190 aClientMetadata, aClientMetadata.mClientType, aExclusive);
5193 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
5194 const Nullable<PersistenceType>& aPersistenceType,
5195 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
5196 bool aExclusive) {
5197 AssertIsOnOwningThread();
5199 return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
5200 aPersistenceType, aOriginScope,
5201 aClientType, aExclusive);
5204 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5205 QuotaManager::EnsurePersistentOriginIsInitialized(
5206 const OriginMetadata& aOriginMetadata) {
5207 AssertIsOnIOThread();
5208 MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5209 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5211 const auto innerFunc = [&aOriginMetadata,
5212 this](const auto& firstInitializationAttempt)
5213 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
5214 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
5216 if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) {
5217 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5218 return std::pair(std::move(directory), false);
5221 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
5223 QM_TRY_INSPECT(
5224 const int64_t& timestamp,
5225 ([this, created, &directory,
5226 &aOriginMetadata]() -> Result<int64_t, nsresult> {
5227 if (created) {
5228 const int64_t timestamp = PR_Now();
5230 // Only creating .metadata-v2 to reduce IO.
5231 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
5232 /* aPersisted */ true,
5233 aOriginMetadata)));
5235 return timestamp;
5238 // Get the metadata. We only use the timestamp.
5239 QM_TRY_INSPECT(const auto& metadata,
5240 LoadFullOriginMetadataWithRestore(directory));
5242 MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
5244 return metadata.mLastAccessTime;
5245 }()));
5247 QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT,
5248 aOriginMetadata, timestamp,
5249 /* aPersisted */ true, directory)));
5251 mInitializedOrigins.AppendElement(aOriginMetadata.mOrigin);
5253 return std::pair(std::move(directory), created);
5256 return ExecuteOriginInitialization(
5257 aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
5258 "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
5259 innerFunc);
5262 bool QuotaManager::IsTemporaryOriginInitialized(
5263 const OriginMetadata& aOriginMetadata) const {
5264 AssertIsOnIOThread();
5265 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5267 MutexAutoLock lock(mQuotaMutex);
5269 RefPtr<OriginInfo> originInfo =
5270 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
5272 return static_cast<bool>(originInfo);
5275 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5276 QuotaManager::EnsureTemporaryOriginIsInitialized(
5277 PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
5278 AssertIsOnIOThread();
5279 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5280 MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType);
5281 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5282 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
5284 const auto innerFunc = [&aOriginMetadata, this](const auto&)
5285 -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
5286 // Get directory for this origin and persistence type.
5287 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aOriginMetadata));
5289 QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
5291 if (created) {
5292 const int64_t timestamp =
5293 NoteOriginDirectoryCreated(aOriginMetadata, /* aPersisted */ false);
5295 // Only creating .metadata-v2 to reduce IO.
5296 QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
5297 /* aPersisted */ false,
5298 aOriginMetadata)));
5301 // TODO: If the metadata file exists and we didn't call
5302 // LoadFullOriginMetadataWithRestore for it (because the quota info
5303 // was loaded from the cache), then the group in the metadata file
5304 // may be wrong, so it should be checked and eventually updated.
5305 // It's not a big deal that we are not doing it here, because the
5306 // origin will be marked as "accessed", so
5307 // LoadFullOriginMetadataWithRestore will be called for the metadata
5308 // file in next session in LoadQuotaFromCache.
5310 return std::pair(std::move(directory), created);
5313 return ExecuteOriginInitialization(
5314 aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
5315 "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
5316 innerFunc);
5319 RefPtr<BoolPromise> QuotaManager::InitializePersistentClient(
5320 const PrincipalInfo& aPrincipalInfo, Client::Type aClientType) {
5321 AssertIsOnOwningThread();
5323 auto initializePersistentClientOp = CreateInitializePersistentClientOp(
5324 WrapMovingNotNullUnchecked(this), aPrincipalInfo, aClientType);
5326 RegisterNormalOriginOp(*initializePersistentClientOp);
5328 initializePersistentClientOp->RunImmediately();
5330 return initializePersistentClientOp->OnResults();
5333 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5334 QuotaManager::EnsurePersistentClientIsInitialized(
5335 const ClientMetadata& aClientMetadata) {
5336 AssertIsOnIOThread();
5337 MOZ_ASSERT(aClientMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
5338 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
5339 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5340 MOZ_DIAGNOSTIC_ASSERT(IsOriginInitialized(aClientMetadata.mOrigin));
5342 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
5344 QM_TRY(MOZ_TO_RESULT(
5345 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
5347 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
5349 return std::pair(std::move(directory), created);
5352 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryClient(
5353 PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo,
5354 Client::Type aClientType) {
5355 AssertIsOnOwningThread();
5357 auto initializeTemporaryClientOp = CreateInitializeTemporaryClientOp(
5358 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo,
5359 aClientType);
5361 RegisterNormalOriginOp(*initializeTemporaryClientOp);
5363 initializeTemporaryClientOp->RunImmediately();
5365 return initializeTemporaryClientOp->OnResults();
5368 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
5369 QuotaManager::EnsureTemporaryClientIsInitialized(
5370 const ClientMetadata& aClientMetadata) {
5371 AssertIsOnIOThread();
5372 MOZ_ASSERT(aClientMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5373 MOZ_ASSERT(Client::IsValidType(aClientMetadata.mClientType));
5374 MOZ_DIAGNOSTIC_ASSERT(IsStorageInitializedInternal());
5375 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryStorageInitializedInternal());
5376 MOZ_DIAGNOSTIC_ASSERT(IsTemporaryOriginInitialized(aClientMetadata));
5378 QM_TRY_UNWRAP(auto directory, GetOriginDirectory(aClientMetadata));
5380 QM_TRY(MOZ_TO_RESULT(
5381 directory->Append(Client::TypeToString(aClientMetadata.mClientType))));
5383 QM_TRY_UNWRAP(bool created, EnsureDirectory(*directory));
5385 return std::pair(std::move(directory), created);
5388 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage() {
5389 AssertIsOnOwningThread();
5391 // If temporary storage is initialized but there's a clear storage or
5392 // shutdown storage operation already scheduled, we can't immediately resolve
5393 // the promise and return from the function because the clear or shutdown
5394 // storage operation uninitializes storage.
5395 if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
5396 return BoolPromise::CreateAndResolve(true, __func__);
5399 RefPtr<UniversalDirectoryLock> directoryLock = CreateDirectoryLockInternal(
5400 Nullable<PersistenceType>(), OriginScope::FromNull(),
5401 Nullable<Client::Type>(),
5402 /* aExclusive */ false);
5404 return directoryLock->Acquire()->Then(
5405 GetCurrentSerialEventTarget(), __func__,
5406 [self = RefPtr(this),
5407 directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) mutable {
5408 if (aValue.IsReject()) {
5409 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5412 return self->InitializeTemporaryStorage(std::move(directoryLock));
5416 RefPtr<BoolPromise> QuotaManager::InitializeTemporaryStorage(
5417 RefPtr<UniversalDirectoryLock> aDirectoryLock) {
5418 AssertIsOnOwningThread();
5419 MOZ_ASSERT(aDirectoryLock);
5421 if (mTemporaryStorageInitialized && !mShutdownStorageOpCount) {
5422 return BoolPromise::CreateAndResolve(true, __func__);
5425 auto initializeTemporaryStorageOp = CreateInitTemporaryStorageOp(
5426 WrapMovingNotNullUnchecked(this), std::move(aDirectoryLock));
5428 RegisterNormalOriginOp(*initializeTemporaryStorageOp);
5430 initializeTemporaryStorageOp->RunImmediately();
5432 return initializeTemporaryStorageOp->OnResults()->Then(
5433 GetCurrentSerialEventTarget(), __func__,
5434 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5435 if (aValue.IsReject()) {
5436 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5439 self->mTemporaryStorageInitialized = true;
5441 return BoolPromise::CreateAndResolve(true, __func__);
5445 nsresult QuotaManager::EnsureTemporaryStorageIsInitializedInternal() {
5446 AssertIsOnIOThread();
5447 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5449 const auto innerFunc =
5450 [&](const auto& firstInitializationAttempt) -> nsresult {
5451 if (mTemporaryStorageInitializedInternal) {
5452 MOZ_ASSERT(firstInitializationAttempt.Recorded());
5453 return NS_OK;
5456 QM_TRY_INSPECT(
5457 const auto& storageDir,
5458 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFile>,
5459 MOZ_SELECT_OVERLOAD(do_CreateInstance),
5460 NS_LOCAL_FILE_CONTRACTID));
5462 QM_TRY(MOZ_TO_RESULT(storageDir->InitWithPath(GetStoragePath())));
5464 // The storage directory must exist before calling GetTemporaryStorageLimit.
5465 QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
5467 Unused << created;
5469 QM_TRY_UNWRAP(mTemporaryStorageLimit,
5470 GetTemporaryStorageLimit(*storageDir));
5472 QM_TRY(MOZ_TO_RESULT(LoadQuota()));
5474 mTemporaryStorageInitializedInternal = true;
5476 CleanupTemporaryStorage();
5478 if (mCacheUsable) {
5479 QM_TRY(InvalidateCache(*mStorageConnection));
5482 return NS_OK;
5485 return ExecuteInitialization(
5486 Initialization::TemporaryStorage,
5487 "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
5490 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOrigin(
5491 const Maybe<PersistenceType>& aPersistenceType,
5492 const PrincipalInfo& aPrincipalInfo,
5493 const Maybe<Client::Type>& aClientType) {
5494 AssertIsOnOwningThread();
5496 auto clearOriginOp =
5497 CreateClearOriginOp(WrapMovingNotNullUnchecked(this), aPersistenceType,
5498 aPrincipalInfo, aClientType);
5500 RegisterNormalOriginOp(*clearOriginOp);
5502 clearOriginOp->RunImmediately();
5504 return clearOriginOp->OnResults();
5507 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginPrefix(
5508 const Maybe<PersistenceType>& aPersistenceType,
5509 const PrincipalInfo& aPrincipalInfo) {
5510 AssertIsOnOwningThread();
5512 auto clearStoragesForOriginPrefixOp = CreateClearStoragesForOriginPrefixOp(
5513 WrapMovingNotNullUnchecked(this), aPersistenceType, aPrincipalInfo);
5515 RegisterNormalOriginOp(*clearStoragesForOriginPrefixOp);
5517 clearStoragesForOriginPrefixOp->RunImmediately();
5519 return clearStoragesForOriginPrefixOp->OnResults();
5522 RefPtr<BoolPromise> QuotaManager::ClearStoragesForOriginAttributesPattern(
5523 const OriginAttributesPattern& aPattern) {
5524 AssertIsOnOwningThread();
5526 auto clearDataOp =
5527 CreateClearDataOp(WrapMovingNotNullUnchecked(this), aPattern);
5529 RegisterNormalOriginOp(*clearDataOp);
5531 clearDataOp->RunImmediately();
5533 return clearDataOp->OnResults();
5536 RefPtr<BoolPromise> QuotaManager::ClearPrivateRepository() {
5537 AssertIsOnOwningThread();
5539 auto clearPrivateRepositoryOp =
5540 CreateClearPrivateRepositoryOp(WrapMovingNotNullUnchecked(this));
5542 RegisterNormalOriginOp(*clearPrivateRepositoryOp);
5544 clearPrivateRepositoryOp->RunImmediately();
5546 return clearPrivateRepositoryOp->OnResults();
5549 RefPtr<BoolPromise> QuotaManager::ClearStorage() {
5550 AssertIsOnOwningThread();
5552 auto clearStorageOp = CreateClearStorageOp(WrapMovingNotNullUnchecked(this));
5554 RegisterNormalOriginOp(*clearStorageOp);
5556 clearStorageOp->RunImmediately();
5558 // Storage clearing also shuts it down, so we need to increses the counter
5559 // here as well.
5560 mShutdownStorageOpCount++;
5562 return clearStorageOp->OnResults()->Then(
5563 GetCurrentSerialEventTarget(), __func__,
5564 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5565 self->mShutdownStorageOpCount--;
5567 if (aValue.IsReject()) {
5568 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5571 self->mTemporaryStorageInitialized = false;
5572 self->mStorageInitialized = false;
5574 return BoolPromise::CreateAndResolve(true, __func__);
5578 RefPtr<BoolPromise> QuotaManager::ShutdownStorage() {
5579 AssertIsOnOwningThread();
5581 auto shutdownStorageOp =
5582 CreateShutdownStorageOp(WrapMovingNotNullUnchecked(this));
5584 RegisterNormalOriginOp(*shutdownStorageOp);
5586 shutdownStorageOp->RunImmediately();
5588 mShutdownStorageOpCount++;
5590 return shutdownStorageOp->OnResults()->Then(
5591 GetCurrentSerialEventTarget(), __func__,
5592 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue& aValue) {
5593 self->mShutdownStorageOpCount--;
5595 if (aValue.IsReject()) {
5596 return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
5599 self->mTemporaryStorageInitialized = false;
5600 self->mStorageInitialized = false;
5602 return BoolPromise::CreateAndResolve(true, __func__);
5606 void QuotaManager::ShutdownStorageInternal() {
5607 AssertIsOnIOThread();
5609 if (mStorageConnection) {
5610 mInitializationInfo.ResetOriginInitializationInfos();
5611 mInitializedOrigins.Clear();
5613 if (mTemporaryStorageInitializedInternal) {
5614 if (mCacheUsable) {
5615 UnloadQuota();
5616 } else {
5617 RemoveQuota();
5620 mTemporaryStorageInitializedInternal = false;
5623 ReleaseIOThreadObjects();
5625 mStorageConnection = nullptr;
5626 mCacheUsable = false;
5629 mInitializationInfo.ResetFirstInitializationAttempts();
5632 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
5633 nsIFile& aDirectory) {
5634 AssertIsOnIOThread();
5636 QM_TRY_INSPECT(const bool& exists,
5637 MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
5639 if (!exists) {
5640 QM_TRY_INSPECT(
5641 const auto& leafName,
5642 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
5643 .map([](const auto& leafName) {
5644 return NS_ConvertUTF16toUTF8(leafName);
5645 }));
5647 QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
5648 [](const auto&) {
5649 QM_WARNING(
5650 "Preventing creation of a new origin directory which is not "
5651 "supported by our origin parser or is obsolete!");
5655 QM_TRY_RETURN(EnsureDirectory(aDirectory));
5658 nsresult QuotaManager::AboutToClearOrigins(
5659 const Nullable<PersistenceType>& aPersistenceType,
5660 const OriginScope& aOriginScope,
5661 const Nullable<Client::Type>& aClientType) {
5662 AssertIsOnIOThread();
5664 if (aClientType.IsNull()) {
5665 for (Client::Type type : AllClientTypes()) {
5666 QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
5667 aPersistenceType, aOriginScope)));
5669 } else {
5670 QM_TRY(MOZ_TO_RESULT((*mClients)[aClientType.Value()]->AboutToClearOrigins(
5671 aPersistenceType, aOriginScope)));
5674 return NS_OK;
5677 void QuotaManager::OriginClearCompleted(
5678 PersistenceType aPersistenceType, const nsACString& aOrigin,
5679 const Nullable<Client::Type>& aClientType) {
5680 AssertIsOnIOThread();
5682 if (aClientType.IsNull()) {
5683 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5684 mInitializedOrigins.RemoveElement(aOrigin);
5687 for (Client::Type type : AllClientTypes()) {
5688 (*mClients)[type]->OnOriginClearCompleted(aPersistenceType, aOrigin);
5690 } else {
5691 (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
5692 aOrigin);
5696 void QuotaManager::RepositoryClearCompleted(PersistenceType aPersistenceType) {
5697 AssertIsOnIOThread();
5699 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5700 mInitializedOrigins.Clear();
5703 for (Client::Type type : AllClientTypes()) {
5704 (*mClients)[type]->OnRepositoryClearCompleted(aPersistenceType);
5708 Client* QuotaManager::GetClient(Client::Type aClientType) {
5709 MOZ_ASSERT(aClientType >= Client::IDB);
5710 MOZ_ASSERT(aClientType < Client::TypeMax());
5712 return (*mClients)[aClientType];
5715 const AutoTArray<Client::Type, Client::TYPE_MAX>&
5716 QuotaManager::AllClientTypes() {
5717 if (CachedNextGenLocalStorageEnabled()) {
5718 return *mAllClientTypes;
5720 return *mAllClientTypesExceptLS;
5723 uint64_t QuotaManager::GetGroupLimit() const {
5724 // To avoid one group evicting all the rest, limit the amount any one group
5725 // can use to 20% resp. a fifth. To prevent individual sites from using
5726 // exorbitant amounts of storage where there is a lot of free space, cap the
5727 // group limit to 10GB.
5728 const auto x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 10 GB);
5730 // In low-storage situations, make an exception (while not exceeding the total
5731 // storage limit).
5732 return std::min<uint64_t>(mTemporaryStorageLimit,
5733 std::max<uint64_t>(x, 10 MB));
5736 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
5737 const OriginMetadata& aOriginMetadata) {
5738 AssertIsOnIOThread();
5740 uint64_t totalGroupUsage = 0;
5743 MutexAutoLock lock(mQuotaMutex);
5745 GroupInfoPair* pair;
5746 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
5747 for (const PersistenceType type : kBestEffortPersistenceTypes) {
5748 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
5749 if (groupInfo) {
5750 if (type == PERSISTENCE_TYPE_DEFAULT) {
5751 RefPtr<OriginInfo> originInfo =
5752 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
5754 if (originInfo && originInfo->LockedPersisted()) {
5755 return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
5759 AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
5760 totalGroupUsage += groupInfo->mUsage;
5766 return std::pair(totalGroupUsage, GetGroupLimit());
5769 uint64_t QuotaManager::GetOriginUsage(
5770 const PrincipalMetadata& aPrincipalMetadata) {
5771 AssertIsOnIOThread();
5773 uint64_t usage = 0;
5776 MutexAutoLock lock(mQuotaMutex);
5778 GroupInfoPair* pair;
5779 if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
5780 for (const PersistenceType type : kBestEffortPersistenceTypes) {
5781 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
5782 if (groupInfo) {
5783 RefPtr<OriginInfo> originInfo =
5784 groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
5785 if (originInfo) {
5786 AssertNoOverflow(usage, originInfo->LockedUsage());
5787 usage += originInfo->LockedUsage();
5794 return usage;
5797 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
5798 const OriginMetadata& aOriginMetadata) {
5799 AssertIsOnIOThread();
5800 MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
5801 MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitializedInternal);
5803 MutexAutoLock lock(mQuotaMutex);
5805 RefPtr<OriginInfo> originInfo =
5806 LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
5807 if (originInfo) {
5808 return Some(originInfo->LockedFlattenToFullOriginMetadata());
5811 return Nothing();
5814 void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
5815 mQuotaMutex.AssertNotCurrentThreadOwns();
5817 RefPtr<StoragePressureRunnable> storagePressureRunnable =
5818 new StoragePressureRunnable(aUsage);
5820 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
5823 // static
5824 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
5825 const nsACString& aOrigin,
5826 Client::Type aClientType,
5827 nsACString& aDatabaseId) {
5828 nsAutoCString str;
5829 str.AppendInt(aPersistenceType);
5830 str.Append('*');
5831 str.Append(aOrigin);
5832 str.Append('*');
5833 str.AppendInt(aClientType);
5835 aDatabaseId = str;
5838 // static
5839 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
5840 switch (aPrincipalInfo.type()) {
5841 // A system principal is acceptable.
5842 case PrincipalInfo::TSystemPrincipalInfo: {
5843 return true;
5846 // Validate content principals to ensure that the spec, originNoSuffix and
5847 // baseDomain are sane.
5848 case PrincipalInfo::TContentPrincipalInfo: {
5849 const ContentPrincipalInfo& info =
5850 aPrincipalInfo.get_ContentPrincipalInfo();
5852 // Verify the principal spec parses.
5853 nsCOMPtr<nsIURI> uri;
5854 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), info.spec())), false);
5856 nsCOMPtr<nsIPrincipal> principal =
5857 BasePrincipal::CreateContentPrincipal(uri, info.attrs());
5858 QM_TRY(MOZ_TO_RESULT(principal), false);
5860 // Verify the principal originNoSuffix matches spec.
5861 QM_TRY_INSPECT(const auto& originNoSuffix,
5862 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
5863 GetOriginNoSuffix),
5864 false);
5866 if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
5867 QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
5868 originNoSuffix.get(), info.originNoSuffix().get());
5869 return false;
5872 if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
5873 return false;
5876 if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
5877 QM_WARNING("originNoSuffix (%s) contains the '^' character!",
5878 info.originNoSuffix().get());
5879 return false;
5882 // Verify the principal baseDomain exists.
5883 if (NS_WARN_IF(info.baseDomain().IsVoid())) {
5884 return false;
5887 // Verify the principal baseDomain matches spec.
5888 QM_TRY_INSPECT(const auto& baseDomain,
5889 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
5890 GetBaseDomain),
5891 false);
5893 if (NS_WARN_IF(baseDomain != info.baseDomain())) {
5894 QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
5895 baseDomain.get(), info.baseDomain().get());
5896 return false;
5899 return true;
5902 default: {
5903 break;
5907 // Null and expanded principals are not acceptable.
5908 return false;
5911 Result<PrincipalMetadata, nsresult>
5912 QuotaManager::GetInfoFromValidatedPrincipalInfo(
5913 const PrincipalInfo& aPrincipalInfo) {
5914 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
5916 switch (aPrincipalInfo.type()) {
5917 case PrincipalInfo::TSystemPrincipalInfo: {
5918 return GetInfoForChrome();
5921 case PrincipalInfo::TContentPrincipalInfo: {
5922 const ContentPrincipalInfo& info =
5923 aPrincipalInfo.get_ContentPrincipalInfo();
5925 nsCString suffix;
5926 info.attrs().CreateSuffix(suffix);
5928 nsCString origin = info.originNoSuffix() + suffix;
5930 if (IsUUIDOrigin(origin)) {
5931 QM_TRY_INSPECT(const auto& originalOrigin,
5932 GetOriginFromStorageOrigin(origin));
5934 nsCOMPtr<nsIPrincipal> principal =
5935 BasePrincipal::CreateContentPrincipal(originalOrigin);
5936 QM_TRY(MOZ_TO_RESULT(principal));
5938 PrincipalInfo principalInfo;
5939 QM_TRY(
5940 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
5942 return GetInfoFromValidatedPrincipalInfo(principalInfo);
5945 PrincipalMetadata principalMetadata;
5947 principalMetadata.mSuffix = suffix;
5949 principalMetadata.mGroup = info.baseDomain() + suffix;
5951 principalMetadata.mOrigin = origin;
5953 if (info.attrs().mPrivateBrowsingId != 0) {
5954 QM_TRY_UNWRAP(principalMetadata.mStorageOrigin,
5955 EnsureStorageOriginFromOrigin(origin));
5956 } else {
5957 principalMetadata.mStorageOrigin = origin;
5960 principalMetadata.mIsPrivate = info.attrs().mPrivateBrowsingId != 0;
5962 return principalMetadata;
5965 default: {
5966 MOZ_ASSERT_UNREACHABLE("Should never get here!");
5967 return Err(NS_ERROR_UNEXPECTED);
5972 // static
5973 nsAutoCString QuotaManager::GetOriginFromValidatedPrincipalInfo(
5974 const PrincipalInfo& aPrincipalInfo) {
5975 MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
5977 switch (aPrincipalInfo.type()) {
5978 case PrincipalInfo::TSystemPrincipalInfo: {
5979 return nsAutoCString{GetOriginForChrome()};
5982 case PrincipalInfo::TContentPrincipalInfo: {
5983 const ContentPrincipalInfo& info =
5984 aPrincipalInfo.get_ContentPrincipalInfo();
5986 nsAutoCString suffix;
5988 info.attrs().CreateSuffix(suffix);
5990 return info.originNoSuffix() + suffix;
5993 default: {
5994 MOZ_CRASH("Should never get here!");
5999 // static
6000 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromPrincipal(
6001 nsIPrincipal* aPrincipal) {
6002 MOZ_ASSERT(aPrincipal);
6004 if (aPrincipal->IsSystemPrincipal()) {
6005 return GetInfoForChrome();
6008 if (aPrincipal->GetIsNullPrincipal()) {
6009 NS_WARNING("IndexedDB not supported from this principal!");
6010 return Err(NS_ERROR_FAILURE);
6013 PrincipalMetadata principalMetadata;
6015 QM_TRY(MOZ_TO_RESULT(aPrincipal->GetOrigin(principalMetadata.mOrigin)));
6017 if (principalMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
6018 NS_WARNING("Non-chrome principal can't use chrome origin!");
6019 return Err(NS_ERROR_FAILURE);
6022 aPrincipal->OriginAttributesRef().CreateSuffix(principalMetadata.mSuffix);
6024 nsAutoCString baseDomain;
6025 QM_TRY(MOZ_TO_RESULT(aPrincipal->GetBaseDomain(baseDomain)));
6027 MOZ_ASSERT(!baseDomain.IsEmpty());
6029 principalMetadata.mGroup = baseDomain + principalMetadata.mSuffix;
6031 principalMetadata.mStorageOrigin = principalMetadata.mOrigin;
6033 principalMetadata.mIsPrivate = aPrincipal->GetPrivateBrowsingId() != 0;
6035 return principalMetadata;
6038 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromWindow(
6039 nsPIDOMWindowOuter* aWindow) {
6040 MOZ_ASSERT(NS_IsMainThread());
6041 MOZ_ASSERT(aWindow);
6043 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6044 QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6046 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6047 QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6049 return GetInfoFromPrincipal(principal);
6052 // static
6053 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromPrincipal(
6054 nsIPrincipal* aPrincipal) {
6055 MOZ_ASSERT(NS_IsMainThread());
6056 MOZ_ASSERT(aPrincipal);
6058 if (aPrincipal->IsSystemPrincipal()) {
6059 return nsAutoCString{GetOriginForChrome()};
6062 if (aPrincipal->GetIsNullPrincipal()) {
6063 NS_WARNING("IndexedDB not supported from this principal!");
6064 return Err(NS_ERROR_FAILURE);
6067 QM_TRY_UNWRAP(const auto origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6068 nsAutoCString, aPrincipal, GetOrigin));
6070 if (origin.EqualsLiteral(kChromeOrigin)) {
6071 NS_WARNING("Non-chrome principal can't use chrome origin!");
6072 return Err(NS_ERROR_FAILURE);
6075 return origin;
6078 // static
6079 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromWindow(
6080 nsPIDOMWindowOuter* aWindow) {
6081 MOZ_ASSERT(NS_IsMainThread());
6082 MOZ_ASSERT(aWindow);
6084 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6085 QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6087 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6088 QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6090 QM_TRY_RETURN(GetOriginFromPrincipal(principal));
6093 // static
6094 PrincipalMetadata QuotaManager::GetInfoForChrome() {
6095 return {{},
6096 GetOriginForChrome(),
6097 GetOriginForChrome(),
6098 GetOriginForChrome(),
6099 false};
6102 // static
6103 nsLiteralCString QuotaManager::GetOriginForChrome() {
6104 return nsLiteralCString{kChromeOrigin};
6107 // static
6108 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
6109 MOZ_ASSERT(!aOrigin.IsEmpty());
6111 // The first prompt is not required for these origins.
6112 if (aOrigin.EqualsLiteral(kChromeOrigin) ||
6113 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
6114 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
6115 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
6116 return true;
6119 return false;
6122 // static
6123 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
6124 const nsACString& aOrigin2) {
6125 return MakeSanitizedOriginCString(aOrigin1) ==
6126 MakeSanitizedOriginCString(aOrigin2);
6129 // static
6130 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
6131 const nsACString& aOrigin) {
6132 // An origin string either corresponds to a SystemPrincipalInfo or a
6133 // ContentPrincipalInfo, see
6134 // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6136 nsCString spec;
6137 OriginAttributes attrs;
6138 nsCString originalSuffix;
6139 const OriginParser::ResultType result = OriginParser::ParseOrigin(
6140 MakeSanitizedOriginCString(aOrigin), spec, &attrs, originalSuffix);
6141 QM_TRY(MOZ_TO_RESULT(result == OriginParser::ValidOrigin));
6143 QM_TRY_INSPECT(
6144 const auto& principal,
6145 ([&spec, &attrs]() -> Result<nsCOMPtr<nsIPrincipal>, nsresult> {
6146 if (spec.EqualsLiteral(kChromeOrigin)) {
6147 return nsCOMPtr<nsIPrincipal>(SystemPrincipal::Get());
6150 nsCOMPtr<nsIURI> uri;
6151 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), spec)));
6153 return nsCOMPtr<nsIPrincipal>(
6154 BasePrincipal::CreateContentPrincipal(uri, attrs));
6155 }()));
6156 QM_TRY(MOZ_TO_RESULT(principal));
6158 PrincipalInfo principalInfo;
6159 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
6161 return std::move(principalInfo);
6164 // static
6165 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
6167 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6168 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6169 mQuotaMutex.AssertCurrentThreadOwns();
6171 RefPtr<CollectOriginsHelper> helper =
6172 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
6174 // Unlock while calling out to XPCOM (code behind the dispatch method needs
6175 // to acquire its own lock which can potentially lead to a deadlock and it
6176 // also calls an observer that can do various stuff like IO, so it's better
6177 // to not hold our mutex while that happens).
6179 MutexAutoUnlock autoUnlock(mQuotaMutex);
6181 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
6184 return helper->BlockAndReturnOriginsForEviction(aLocks);
6187 void QuotaManager::LockedRemoveQuotaForRepository(
6188 PersistenceType aPersistenceType) {
6189 mQuotaMutex.AssertCurrentThreadOwns();
6190 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6192 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
6193 auto& pair = iter.Data();
6195 if (RefPtr<GroupInfo> groupInfo =
6196 pair->LockedGetGroupInfo(aPersistenceType)) {
6197 groupInfo->LockedRemoveOriginInfos();
6199 pair->LockedClearGroupInfo(aPersistenceType);
6201 if (!pair->LockedHasGroupInfos()) {
6202 iter.Remove();
6208 void QuotaManager::LockedRemoveQuotaForOrigin(
6209 const OriginMetadata& aOriginMetadata) {
6210 mQuotaMutex.AssertCurrentThreadOwns();
6211 MOZ_ASSERT(aOriginMetadata.mPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6213 GroupInfoPair* pair;
6214 if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6215 return;
6218 MOZ_ASSERT(pair);
6220 if (RefPtr<GroupInfo> groupInfo =
6221 pair->LockedGetGroupInfo(aOriginMetadata.mPersistenceType)) {
6222 groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
6224 if (!groupInfo->LockedHasOriginInfos()) {
6225 pair->LockedClearGroupInfo(aOriginMetadata.mPersistenceType);
6227 if (!pair->LockedHasGroupInfos()) {
6228 mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
6234 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
6235 PersistenceType aPersistenceType, const nsACString& aSuffix,
6236 const nsACString& aGroup) {
6237 mQuotaMutex.AssertCurrentThreadOwns();
6238 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6240 GroupInfoPair* const pair =
6241 mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
6243 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6244 if (!groupInfo) {
6245 groupInfo = new GroupInfo(pair, aPersistenceType);
6246 pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
6249 return groupInfo.forget();
6252 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
6253 PersistenceType aPersistenceType,
6254 const OriginMetadata& aOriginMetadata) const {
6255 mQuotaMutex.AssertCurrentThreadOwns();
6256 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6258 GroupInfoPair* pair;
6259 if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6260 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6261 if (groupInfo) {
6262 return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6266 return nullptr;
6269 template <typename Iterator>
6270 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6271 Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
6272 const RefPtr<GroupInfo>& aDefaultGroupInfo,
6273 const RefPtr<GroupInfo>& aPrivateGroupInfo) {
6274 const auto copy = [&aDest](const GroupInfo& groupInfo) {
6275 std::copy_if(
6276 groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
6277 [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
6280 if (aTemporaryGroupInfo) {
6281 MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
6282 aTemporaryGroupInfo->GetPersistenceType());
6284 copy(*aTemporaryGroupInfo);
6286 if (aDefaultGroupInfo) {
6287 MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
6288 aDefaultGroupInfo->GetPersistenceType());
6290 copy(*aDefaultGroupInfo);
6292 if (aPrivateGroupInfo) {
6293 MOZ_ASSERT(PERSISTENCE_TYPE_PRIVATE ==
6294 aPrivateGroupInfo->GetPersistenceType());
6295 copy(*aPrivateGroupInfo);
6299 template <typename Collect, typename Pred>
6300 QuotaManager::OriginInfosFlatTraversable
6301 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
6302 OriginInfosFlatTraversable originInfos;
6304 std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
6306 originInfos.Sort(OriginInfoAccessTimeComparator());
6308 const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
6309 std::forward<Pred>(aPred));
6311 originInfos.TruncateLength(foundIt - originInfos.cbegin());
6313 return originInfos;
6316 QuotaManager::OriginInfosNestedTraversable
6317 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
6318 MutexAutoLock lock(mQuotaMutex);
6320 OriginInfosNestedTraversable originInfos;
6322 for (const auto& entry : mGroupInfoPairs) {
6323 const auto& pair = entry.GetData();
6325 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6326 MOZ_ASSERT(pair);
6328 uint64_t groupUsage = 0;
6330 const RefPtr<GroupInfo> temporaryGroupInfo =
6331 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
6332 if (temporaryGroupInfo) {
6333 groupUsage += temporaryGroupInfo->mUsage;
6336 const RefPtr<GroupInfo> defaultGroupInfo =
6337 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
6338 if (defaultGroupInfo) {
6339 groupUsage += defaultGroupInfo->mUsage;
6342 const RefPtr<GroupInfo> privateGroupInfo =
6343 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE);
6344 if (privateGroupInfo) {
6345 groupUsage += privateGroupInfo->mUsage;
6348 if (groupUsage > 0) {
6349 QuotaManager* quotaManager = QuotaManager::Get();
6350 MOZ_ASSERT(quotaManager, "Shouldn't be null!");
6352 if (groupUsage > quotaManager->GetGroupLimit()) {
6353 originInfos.AppendElement(CollectLRUOriginInfosUntil(
6354 [&temporaryGroupInfo, &defaultGroupInfo,
6355 &privateGroupInfo](auto inserter) {
6356 MaybeInsertNonPersistedOriginInfos(
6357 std::move(inserter), temporaryGroupInfo, defaultGroupInfo,
6358 privateGroupInfo);
6360 [&groupUsage, quotaManager](const auto& originInfo) {
6361 groupUsage -= originInfo->LockedUsage();
6363 return groupUsage <= quotaManager->GetGroupLimit();
6364 }));
6369 return originInfos;
6372 QuotaManager::OriginInfosNestedTraversable
6373 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
6374 MutexAutoLock lock(mQuotaMutex);
6376 QuotaManager::OriginInfosNestedTraversable res;
6377 res.AppendElement(CollectLRUOriginInfosUntil(
6378 // XXX The lambda only needs to capture this, but due to Bug 1421435 it
6379 // can't.
6380 [&](auto inserter) {
6381 for (const auto& entry : mGroupInfoPairs) {
6382 const auto& pair = entry.GetData();
6384 MOZ_ASSERT(!entry.GetKey().IsEmpty());
6385 MOZ_ASSERT(pair);
6387 MaybeInsertNonPersistedOriginInfos(
6388 inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
6389 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT),
6390 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_PRIVATE));
6393 [temporaryStorageUsage = mTemporaryStorageUsage,
6394 temporaryStorageLimit = mTemporaryStorageLimit,
6395 doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
6396 if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
6397 return true;
6400 doomedUsage += originInfo->LockedUsage();
6401 return false;
6402 }));
6404 return res;
6407 void QuotaManager::ClearOrigins(
6408 const OriginInfosNestedTraversable& aDoomedOriginInfos) {
6409 AssertIsOnIOThread();
6411 // If we are in shutdown, we could break off early from clearing origins.
6412 // In such cases, we would like to track the ones that were already cleared
6413 // up, such that other essential cleanup could be performed on clearedOrigins.
6414 // clearedOrigins is used in calls to LockedRemoveQuotaForOrigin and
6415 // OriginClearCompleted below. We could have used a collection of OriginInfos
6416 // rather than flattening them to OriginMetadata but groupInfo in OriginInfo
6417 // is just a raw ptr and LockedRemoveQuotaForOrigin might delete groupInfo and
6418 // as a result, we would not be able to get origin persistence type required
6419 // in OriginClearCompleted call after lockedRemoveQuotaForOrigin call.
6420 nsTArray<OriginMetadata> clearedOrigins;
6422 // XXX Does this need to be done a) in order and/or b) sequentially?
6423 for (const auto& doomedOriginInfo :
6424 Flatten<OriginInfosFlatTraversable::value_type>(aDoomedOriginInfos)) {
6425 #ifdef DEBUG
6427 MutexAutoLock lock(mQuotaMutex);
6428 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
6430 #endif
6432 // TODO: We are currently only checking for this flag here which
6433 // means that we cannot break off once we start cleaning an origin. It
6434 // could be better if we could check for shutdown flag while cleaning an
6435 // origin such that we could break off early from the cleaning process if
6436 // we are stuck cleaning on one huge origin. Bug1797098 has been filed to
6437 // track this.
6438 if (QuotaManager::IsShuttingDown()) {
6439 break;
6442 auto originMetadata = doomedOriginInfo->FlattenToOriginMetadata();
6444 DeleteOriginDirectory(originMetadata);
6446 clearedOrigins.AppendElement(std::move(originMetadata));
6450 MutexAutoLock lock(mQuotaMutex);
6452 for (const auto& clearedOrigin : clearedOrigins) {
6453 LockedRemoveQuotaForOrigin(clearedOrigin);
6457 for (const auto& clearedOrigin : clearedOrigins) {
6458 OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin,
6459 Nullable<Client::Type>());
6463 void QuotaManager::CleanupTemporaryStorage() {
6464 AssertIsOnIOThread();
6466 // Evicting origins that exceed their group limit also affects the global
6467 // temporary storage usage, so these steps have to be taken sequentially.
6468 // Combining them doesn't seem worth the added complexity.
6469 ClearOrigins(GetOriginInfosExceedingGroupLimit());
6470 ClearOrigins(GetOriginInfosExceedingGlobalLimit());
6472 if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
6473 // If disk space is still low after origin clear, notify storage pressure.
6474 NotifyStoragePressure(mTemporaryStorageUsage);
6478 void QuotaManager::DeleteOriginDirectory(
6479 const OriginMetadata& aOriginMetadata) {
6480 QM_TRY_INSPECT(const auto& directory, GetOriginDirectory(aOriginMetadata),
6481 QM_VOID);
6483 nsresult rv = directory->Remove(true);
6484 if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
6485 // This should never fail if we've closed all storage connections
6486 // correctly...
6487 NS_ERROR("Failed to remove directory!");
6491 void QuotaManager::FinalizeOriginEviction(
6492 nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
6493 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
6495 auto finalizeOriginEviction = [locks = std::move(aLocks)]() mutable {
6496 QuotaManager* quotaManager = QuotaManager::Get();
6497 MOZ_ASSERT(quotaManager);
6499 RefPtr<OriginOperationBase> op = CreateFinalizeOriginEvictionOp(
6500 WrapMovingNotNull(quotaManager), std::move(locks));
6502 op->RunImmediately();
6505 if (IsOnBackgroundThread()) {
6506 finalizeOriginEviction();
6507 } else {
6508 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(
6509 NS_NewRunnableFunction(
6510 "dom::quota::QuotaManager::FinalizeOriginEviction",
6511 std::move(finalizeOriginEviction)),
6512 NS_DISPATCH_NORMAL));
6516 Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
6517 const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
6518 AssertIsOnIOThread();
6519 MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
6521 QM_TRY_INSPECT(const auto& storageArchivesDir,
6522 QM_NewLocalFile(*mStorageArchivesPath));
6524 // Create another subdir, so once we decide to remove all temporary archives,
6525 // we can remove only the subdir and the parent directory can still be used
6526 // for something else or similar in future. Otherwise, we would have to
6527 // figure out a new name for it.
6528 QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
6530 PRExplodedTime now;
6531 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
6533 const auto dateStr =
6534 nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
6535 now.tm_month + 1, now.tm_mday);
6537 QM_TRY_INSPECT(
6538 const auto& storageArchiveDir,
6539 CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
6541 QM_TRY(MOZ_TO_RESULT(
6542 storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
6544 QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
6545 CloneFileAndAppend(*storageArchiveDir,
6546 nsLiteralString(DEFAULT_DIRECTORY_NAME)));
6548 QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
6549 CloneFileAndAppend(*storageArchiveDir,
6550 nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
6552 for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
6553 MOZ_ASSERT(
6554 IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
6556 QM_TRY_INSPECT(const auto& directory,
6557 GetOriginDirectory(fullOriginMetadata));
6559 // The origin could have been removed, for example due to corruption.
6560 QM_TRY_INSPECT(
6561 const auto& moved,
6562 QM_OR_ELSE_WARN_IF(
6563 // Expression.
6564 MOZ_TO_RESULT(
6565 directory->MoveTo(fullOriginMetadata.mPersistenceType ==
6566 PERSISTENCE_TYPE_DEFAULT
6567 ? defaultStorageArchiveDir
6568 : temporaryStorageArchiveDir,
6569 u""_ns))
6570 .map([](Ok) { return true; }),
6571 // Predicate.
6572 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
6573 // Fallback.
6574 ErrToOk<false>));
6576 if (moved) {
6577 RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
6578 fullOriginMetadata);
6582 return Ok{};
6585 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
6586 -> DirectoryLockTable& {
6587 switch (aPersistenceType) {
6588 case PERSISTENCE_TYPE_TEMPORARY:
6589 return mTemporaryDirectoryLockTable;
6590 case PERSISTENCE_TYPE_DEFAULT:
6591 return mDefaultDirectoryLockTable;
6592 case PERSISTENCE_TYPE_PRIVATE:
6593 return mPrivateDirectoryLockTable;
6595 case PERSISTENCE_TYPE_PERSISTENT:
6596 case PERSISTENCE_TYPE_INVALID:
6597 default:
6598 MOZ_CRASH("Bad persistence type value!");
6602 void QuotaManager::ClearDirectoryLockTables() {
6603 AssertIsOnOwningThread();
6605 for (const PersistenceType type : kBestEffortPersistenceTypes) {
6606 DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(type);
6608 if (!IsShuttingDown()) {
6609 for (const auto& entry : directoryLockTable) {
6610 const auto& array = entry.GetData();
6612 // It doesn't matter which lock is used, they all have the same
6613 // persistence type and origin metadata.
6614 MOZ_ASSERT(!array->IsEmpty());
6615 const auto& lock = array->ElementAt(0);
6617 UpdateOriginAccessTime(lock->GetPersistenceType(),
6618 lock->OriginMetadata());
6622 directoryLockTable.Clear();
6626 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
6627 AssertIsOnIOThread();
6629 // Do not parse this sanitized origin string, if we already parsed it.
6630 return mValidOrigins.LookupOrInsertWith(
6631 aSanitizedOrigin, [&aSanitizedOrigin] {
6632 nsCString spec;
6633 OriginAttributes attrs;
6634 nsCString originalSuffix;
6635 const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
6636 &attrs, originalSuffix);
6638 return result == OriginParser::ValidOrigin;
6642 Result<nsCString, nsresult> QuotaManager::EnsureStorageOriginFromOrigin(
6643 const nsACString& aOrigin) {
6644 MutexAutoLock lock(mQuotaMutex);
6646 QM_TRY_UNWRAP(
6647 auto storageOrigin,
6648 mOriginToStorageOriginMap.TryLookupOrInsertWith(
6649 aOrigin, [this, &aOrigin]() -> Result<nsCString, nsresult> {
6650 OriginAttributes originAttributes;
6652 nsCString originNoSuffix;
6653 QM_TRY(MOZ_TO_RESULT(
6654 originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)));
6656 nsCOMPtr<nsIURI> uri;
6657 QM_TRY(MOZ_TO_RESULT(
6658 NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
6659 .SetSpec(originNoSuffix)
6660 .SetScheme(kUUIDOriginScheme)
6661 .SetHost(NSID_TrimBracketsASCII(nsID::GenerateUUID()))
6662 .SetPort(-1)
6663 .Finalize(uri)));
6665 nsCOMPtr<nsIPrincipal> principal =
6666 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
6667 QM_TRY(MOZ_TO_RESULT(principal));
6669 QM_TRY_UNWRAP(auto origin,
6670 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6671 nsAutoCString, principal, GetOrigin));
6673 mStorageOriginToOriginMap.WithEntryHandle(
6674 origin,
6675 [&aOrigin](auto entryHandle) { entryHandle.Insert(aOrigin); });
6677 return nsCString(std::move(origin));
6678 }));
6680 return nsCString(std::move(storageOrigin));
6683 Result<nsCString, nsresult> QuotaManager::GetOriginFromStorageOrigin(
6684 const nsACString& aStorageOrigin) {
6685 MutexAutoLock lock(mQuotaMutex);
6687 auto maybeOrigin = mStorageOriginToOriginMap.MaybeGet(aStorageOrigin);
6688 if (maybeOrigin.isNothing()) {
6689 return Err(NS_ERROR_FAILURE);
6692 return maybeOrigin.ref();
6695 int64_t QuotaManager::GenerateDirectoryLockId() {
6696 const int64_t directorylockId = mNextDirectoryLockId;
6698 if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
6699 result.isValid()) {
6700 mNextDirectoryLockId = result.value();
6701 } else {
6702 NS_WARNING("Quota manager has run out of ids for directory locks!");
6704 // There's very little chance for this to happen given the max size of
6705 // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
6706 // to zero since such old directory locks shouldn't exist anymore.
6707 mNextDirectoryLockId = 0;
6710 // TODO: Maybe add an assertion here to check that there is no existing
6711 // directory lock with given id.
6713 return directorylockId;
6716 template <typename Func>
6717 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
6718 Func&& aFunc)
6719 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6720 Initialization, StringGenerator>&> {
6721 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
6722 std::forward<Func>(aFunc));
6725 template <typename Func>
6726 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
6727 const nsACString& aContext,
6728 Func&& aFunc)
6729 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6730 Initialization, StringGenerator>&> {
6731 return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
6732 aContext, std::forward<Func>(aFunc));
6735 template <typename Func>
6736 auto QuotaManager::ExecuteOriginInitialization(
6737 const nsACString& aOrigin, const OriginInitialization aInitialization,
6738 const nsACString& aContext, Func&& aFunc)
6739 -> std::invoke_result_t<Func, const FirstInitializationAttempt<
6740 Initialization, StringGenerator>&> {
6741 return quota::ExecuteInitialization(
6742 mInitializationInfo.MutableOriginInitializationInfoRef(
6743 aOrigin, CreateIfNonExistent{}),
6744 aInitialization, aContext, std::forward<Func>(aFunc));
6747 /*******************************************************************************
6748 * Local class implementations
6749 ******************************************************************************/
6751 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
6752 uint64_t aMinSizeToBeFreed)
6753 : Runnable("dom::quota::CollectOriginsHelper"),
6754 mMinSizeToBeFreed(aMinSizeToBeFreed),
6755 mMutex(aMutex),
6756 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
6757 mSizeToBeFreed(0),
6758 mWaiting(true) {
6759 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6760 mMutex.AssertCurrentThreadOwns();
6763 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
6764 nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6765 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
6766 mMutex.AssertCurrentThreadOwns();
6768 while (mWaiting) {
6769 mCondVar.Wait();
6772 mLocks.SwapElements(aLocks);
6773 return mSizeToBeFreed;
6776 NS_IMETHODIMP
6777 CollectOriginsHelper::Run() {
6778 AssertIsOnBackgroundThread();
6780 QuotaManager* quotaManager = QuotaManager::Get();
6781 NS_ASSERTION(quotaManager, "Shouldn't be null!");
6783 // We use extra stack vars here to avoid race detector warnings (the same
6784 // memory accessed with and without the lock held).
6785 nsTArray<RefPtr<OriginDirectoryLock>> locks;
6786 uint64_t sizeToBeFreed =
6787 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
6789 MutexAutoLock lock(mMutex);
6791 NS_ASSERTION(mWaiting, "Huh?!");
6793 mLocks.SwapElements(locks);
6794 mSizeToBeFreed = sizeToBeFreed;
6795 mWaiting = false;
6796 mCondVar.Notify();
6798 return NS_OK;
6801 NS_IMETHODIMP
6802 StoragePressureRunnable::Run() {
6803 MOZ_ASSERT(NS_IsMainThread());
6805 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
6806 if (NS_WARN_IF(!obsSvc)) {
6807 return NS_ERROR_FAILURE;
6810 nsCOMPtr<nsISupportsPRUint64> wrapper =
6811 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
6812 if (NS_WARN_IF(!wrapper)) {
6813 return NS_ERROR_FAILURE;
6816 wrapper->SetData(mUsage);
6818 obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
6820 return NS_OK;
6823 TimeStamp RecordTimeDeltaHelper::Start() {
6824 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6826 // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
6827 // gLastOSWake is set, then this time duration would still be recorded with
6828 // key "Normal". We are assumming this is rather rare to happen.
6829 mStartTime.init(TimeStamp::Now());
6830 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6832 return *mStartTime;
6835 TimeStamp RecordTimeDeltaHelper::End() {
6836 MOZ_ASSERT(IsOnIOThread() || IsOnBackgroundThread());
6838 mEndTime.init(TimeStamp::Now());
6839 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6841 return *mEndTime;
6844 NS_IMETHODIMP
6845 RecordTimeDeltaHelper::Run() {
6846 MOZ_ASSERT(NS_IsMainThread());
6848 if (mInitializedTime.isSome()) {
6849 // Keys for QM_QUOTA_INFO_LOAD_TIME_V0 and QM_SHUTDOWN_TIME_V0:
6850 // Normal: Normal conditions.
6851 // WasSuspended: There was a OS sleep so that it was suspended.
6852 // TimeStampErr1: The recorded start time is unexpectedly greater than the
6853 // end time.
6854 // TimeStampErr2: The initialized time for the recording class is unexpectly
6855 // greater than the last OS wake time.
6856 const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
6857 if (wasSuspended) {
6858 return "WasSuspended"_ns;
6861 // XXX File a bug if we have data for this key.
6862 // We found negative values in our query in STMO for
6863 // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
6864 // because the documentation for TimeStamp::Now() says it returns a
6865 // monotonically increasing number.
6866 if (*mStartTime > *mEndTime) {
6867 return "TimeStampErr1"_ns;
6870 if (*mInitializedTime > gLastOSWake) {
6871 return "TimeStampErr2"_ns;
6874 return "Normal"_ns;
6875 }();
6877 Telemetry::AccumulateTimeDelta(mHistogram, key, *mStartTime, *mEndTime);
6879 return NS_OK;
6882 gLastOSWake = TimeStamp::Now();
6883 mInitializedTime.init(gLastOSWake);
6885 return NS_OK;
6888 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
6889 int64_t& aTimestamp,
6890 nsACString& aGroup,
6891 nsACString& aOrigin,
6892 Nullable<bool>& aIsApp) {
6893 AssertIsOnIOThread();
6894 MOZ_ASSERT(aDirectory);
6896 QM_TRY_INSPECT(
6897 const auto& binaryStream,
6898 GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
6900 QM_TRY_INSPECT(const uint64_t& timestamp,
6901 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
6903 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6904 nsCString, binaryStream, ReadCString));
6906 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6907 nsCString, binaryStream, ReadCString));
6909 Nullable<bool> isApp;
6910 bool value;
6911 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
6912 isApp.SetValue(value);
6915 aTimestamp = timestamp;
6916 aGroup = group;
6917 aOrigin = origin;
6918 aIsApp = std::move(isApp);
6919 return NS_OK;
6922 nsresult StorageOperationBase::GetDirectoryMetadata2(
6923 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
6924 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
6925 AssertIsOnIOThread();
6926 MOZ_ASSERT(aDirectory);
6928 QM_TRY_INSPECT(const auto& binaryStream,
6929 GetBinaryInputStream(*aDirectory,
6930 nsLiteralString(METADATA_V2_FILE_NAME)));
6932 QM_TRY_INSPECT(const uint64_t& timestamp,
6933 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
6935 QM_TRY_INSPECT(const bool& persisted,
6936 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
6937 Unused << persisted;
6939 QM_TRY_INSPECT(const bool& reservedData1,
6940 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
6941 Unused << reservedData1;
6943 QM_TRY_INSPECT(const bool& reservedData2,
6944 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
6945 Unused << reservedData2;
6947 QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6948 nsCString, binaryStream, ReadCString));
6950 QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6951 nsCString, binaryStream, ReadCString));
6953 QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6954 nsCString, binaryStream, ReadCString));
6956 QM_TRY_INSPECT(const bool& isApp,
6957 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
6959 aTimestamp = timestamp;
6960 aSuffix = suffix;
6961 aGroup = group;
6962 aOrigin = origin;
6963 aIsApp = isApp;
6964 return NS_OK;
6967 int64_t StorageOperationBase::GetOriginLastModifiedTime(
6968 const OriginProps& aOriginProps) {
6969 return GetLastModifiedTime(*aOriginProps.mPersistenceType,
6970 *aOriginProps.mDirectory);
6973 nsresult StorageOperationBase::RemoveObsoleteOrigin(
6974 const OriginProps& aOriginProps) {
6975 AssertIsOnIOThread();
6977 QM_WARNING(
6978 "Deleting obsolete %s directory that is no longer a legal "
6979 "origin!",
6980 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
6982 QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
6984 return NS_OK;
6987 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
6988 const OriginProps& aOriginProps) {
6989 AssertIsOnIOThread();
6991 const nsAString& oldLeafName = aOriginProps.mLeafName;
6993 const auto newLeafName =
6994 MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
6996 if (oldLeafName == newLeafName) {
6997 return false;
7000 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7001 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7002 aOriginProps.mOriginMetadata)));
7004 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7005 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7006 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7008 QM_TRY_INSPECT(const auto& newFile,
7009 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7010 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
7012 QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
7014 QM_TRY_INSPECT(const bool& exists,
7015 MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
7017 if (exists) {
7018 QM_WARNING(
7019 "Can't rename %s directory to %s, the target already exists, removing "
7020 "instead of renaming!",
7021 NS_ConvertUTF16toUTF8(oldLeafName).get(),
7022 NS_ConvertUTF16toUTF8(newLeafName).get());
7025 QM_TRY(CallWithDelayedRetriesIfAccessDenied(
7026 [&exists, &aOriginProps, &newLeafName] {
7027 if (exists) {
7028 QM_TRY_RETURN(MOZ_TO_RESULT(
7029 aOriginProps.mDirectory->Remove(/* recursive */ true)));
7031 QM_TRY_RETURN(MOZ_TO_RESULT(
7032 aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
7034 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
7035 StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
7037 return true;
7040 nsresult StorageOperationBase::ProcessOriginDirectories() {
7041 AssertIsOnIOThread();
7042 MOZ_ASSERT(!mOriginProps.IsEmpty());
7044 QuotaManager* quotaManager = QuotaManager::Get();
7045 MOZ_ASSERT(quotaManager);
7047 for (auto& originProps : mOriginProps) {
7048 switch (originProps.mType) {
7049 case OriginProps::eChrome: {
7050 originProps.mOriginMetadata = {QuotaManager::GetInfoForChrome(),
7051 *originProps.mPersistenceType};
7052 break;
7055 case OriginProps::eContent: {
7056 nsCOMPtr<nsIURI> uri;
7057 QM_TRY(
7058 MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(uri), originProps.mSpec)));
7060 nsCOMPtr<nsIPrincipal> principal =
7061 BasePrincipal::CreateContentPrincipal(uri, originProps.mAttrs);
7062 QM_TRY(MOZ_TO_RESULT(principal));
7064 PrincipalInfo principalInfo;
7065 QM_TRY(
7066 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
7068 QM_WARNONLY_TRY_UNWRAP(
7069 auto valid,
7070 MOZ_TO_RESULT(quotaManager->IsPrincipalInfoValid(principalInfo)));
7072 if (!valid) {
7073 // Unknown directories during upgrade are allowed. Just warn if we
7074 // find them.
7075 UNKNOWN_FILE_WARNING(originProps.mLeafName);
7076 originProps.mIgnore = true;
7077 break;
7080 QM_TRY_UNWRAP(
7081 auto principalMetadata,
7082 quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo));
7084 originProps.mOriginMetadata = {std::move(principalMetadata),
7085 *originProps.mPersistenceType};
7087 break;
7090 case OriginProps::eObsolete: {
7091 // There's no way to get info for obsolete origins.
7092 break;
7095 default:
7096 MOZ_CRASH("Bad type!");
7100 // Don't try to upgrade obsolete origins, remove them right after we detect
7101 // them.
7102 for (const auto& originProps : mOriginProps) {
7103 if (originProps.mType == OriginProps::eObsolete) {
7104 MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
7105 MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
7106 MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
7108 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
7109 } else if (!originProps.mIgnore) {
7110 MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
7111 MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
7113 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
7117 return NS_OK;
7120 // XXX Do the fallible initialization in a separate non-static member function
7121 // of StorageOperationBase and eventually get rid of this method and use a
7122 // normal constructor instead.
7123 template <typename PersistenceTypeFunc>
7124 nsresult StorageOperationBase::OriginProps::Init(
7125 PersistenceTypeFunc&& aPersistenceTypeFunc) {
7126 AssertIsOnIOThread();
7128 QM_TRY_INSPECT(const auto& leafName,
7129 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
7130 GetLeafName));
7132 // XXX Consider using QuotaManager::ParseOrigin here.
7133 nsCString spec;
7134 OriginAttributes attrs;
7135 nsCString originalSuffix;
7136 OriginParser::ResultType result = OriginParser::ParseOrigin(
7137 NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
7138 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
7139 mType = OriginProps::eInvalid;
7140 return NS_OK;
7143 const auto persistenceType = [&]() -> PersistenceType {
7144 // XXX We shouldn't continue with initialization if OriginParser returned
7145 // anything else but ValidOrigin. Otherwise, we have to deal with empty
7146 // spec when the origin is obsolete, like here. The caller should handle
7147 // the errors. Until it's fixed, we have to treat obsolete origins as
7148 // origins with unknown/invalid persistence type.
7149 if (result != OriginParser::ValidOrigin) {
7150 return PERSISTENCE_TYPE_INVALID;
7152 return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
7153 }();
7155 mLeafName = leafName;
7156 mSpec = spec;
7157 mAttrs = attrs;
7158 mOriginalSuffix = originalSuffix;
7159 mPersistenceType.init(persistenceType);
7160 if (result == OriginParser::ObsoleteOrigin) {
7161 mType = eObsolete;
7162 } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
7163 mType = eChrome;
7164 } else {
7165 mType = eContent;
7168 return NS_OK;
7171 nsresult RepositoryOperationBase::ProcessRepository() {
7172 AssertIsOnIOThread();
7174 #ifdef DEBUG
7176 QM_TRY_INSPECT(const bool& exists,
7177 MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
7178 QM_ASSERT_UNREACHABLE);
7179 MOZ_ASSERT(exists);
7181 #endif
7183 QM_TRY(CollectEachFileEntry(
7184 *mDirectory,
7185 [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
7186 QM_TRY_INSPECT(const auto& leafName,
7187 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7188 nsAutoString, originFile, GetLeafName));
7190 // Unknown files during upgrade are allowed. Just warn if we find
7191 // them.
7192 if (!IsOSMetadata(leafName)) {
7193 UNKNOWN_FILE_WARNING(leafName);
7196 return mozilla::Ok{};
7198 [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
7199 OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
7200 QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
7201 return self.PersistenceTypeFromSpec(aSpec);
7202 })));
7203 // Bypass invalid origins while upgrading
7204 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
7206 if (originProps.mType != OriginProps::eObsolete) {
7207 QM_TRY_INSPECT(const bool& removed,
7208 MOZ_TO_RESULT_INVOKE_MEMBER(
7209 self, PrepareOriginDirectory, originProps));
7210 if (removed) {
7211 return mozilla::Ok{};
7215 self.mOriginProps.AppendElement(std::move(originProps));
7217 return mozilla::Ok{};
7218 }));
7220 if (mOriginProps.IsEmpty()) {
7221 return NS_OK;
7224 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7226 return NS_OK;
7229 template <typename UpgradeMethod>
7230 nsresult RepositoryOperationBase::MaybeUpgradeClients(
7231 const OriginProps& aOriginProps, UpgradeMethod aMethod) {
7232 AssertIsOnIOThread();
7233 MOZ_ASSERT(aMethod);
7235 QuotaManager* quotaManager = QuotaManager::Get();
7236 MOZ_ASSERT(quotaManager);
7238 QM_TRY(CollectEachFileEntry(
7239 *aOriginProps.mDirectory,
7240 [](const auto& file) -> Result<mozilla::Ok, nsresult> {
7241 QM_TRY_INSPECT(
7242 const auto& leafName,
7243 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
7245 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
7246 UNKNOWN_FILE_WARNING(leafName);
7249 return mozilla::Ok{};
7251 [quotaManager, &aMethod,
7252 &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
7253 QM_TRY_INSPECT(
7254 const auto& leafName,
7255 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
7257 QM_TRY_INSPECT(const bool& removed,
7258 MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
7259 dir, leafName));
7260 if (removed) {
7261 return mozilla::Ok{};
7264 Client::Type clientType;
7265 bool ok = Client::TypeFromText(leafName, clientType, fallible);
7266 if (!ok) {
7267 UNKNOWN_FILE_WARNING(leafName);
7268 return mozilla::Ok{};
7271 Client* client = quotaManager->GetClient(clientType);
7272 MOZ_ASSERT(client);
7274 QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
7276 return mozilla::Ok{};
7277 }));
7279 return NS_OK;
7282 nsresult RepositoryOperationBase::PrepareClientDirectory(
7283 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
7284 AssertIsOnIOThread();
7286 aRemoved = false;
7287 return NS_OK;
7290 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
7291 AssertIsOnIOThread();
7292 MOZ_ASSERT(mDirectory);
7294 const auto maybeLegacyPersistenceType =
7295 LegacyPersistenceTypeFromFile(*mDirectory, fallible);
7296 QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7298 mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
7300 return NS_OK;
7303 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
7304 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
7305 nsIFile& aFile, const fallible_t&) {
7306 nsAutoString leafName;
7307 MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
7309 if (leafName.Equals(u"persistent"_ns)) {
7310 return Some(LegacyPersistenceType::Persistent);
7313 if (leafName.Equals(u"temporary"_ns)) {
7314 return Some(LegacyPersistenceType::Temporary);
7317 return Nothing();
7320 PersistenceType
7321 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
7322 const nsCString& aSpec) {
7323 if (QuotaManager::IsOriginInternal(aSpec)) {
7324 return PERSISTENCE_TYPE_PERSISTENT;
7327 return PERSISTENCE_TYPE_DEFAULT;
7330 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
7331 const nsCString& aSpec) {
7332 switch (*mLegacyPersistenceType) {
7333 case LegacyPersistenceType::Persistent:
7334 return PersistenceTypeFromLegacyPersistentSpec(aSpec);
7335 case LegacyPersistenceType::Temporary:
7336 return PERSISTENCE_TYPE_TEMPORARY;
7338 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
7341 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
7342 nsIFile* aDirectory) {
7343 AssertIsOnIOThread();
7344 MOZ_ASSERT(aDirectory);
7346 QM_TRY_INSPECT(
7347 const auto& metadataFile,
7348 CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
7350 QM_TRY_INSPECT(const bool& exists,
7351 MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
7353 if (!exists) {
7354 // Directory structure upgrade needed.
7355 // Move all files to IDB specific directory.
7357 nsString idbDirectoryName;
7358 QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
7359 NS_ERROR_FAILURE);
7361 QM_TRY_INSPECT(const auto& idbDirectory,
7362 CloneFileAndAppend(*aDirectory, idbDirectoryName));
7364 // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
7365 // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
7366 // idb directory shouldn't exist during the upgrade and the upgrade runs
7367 // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
7368 // here.
7369 QM_TRY(QM_OR_ELSE_WARN_IF(
7370 // Expression.
7371 MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
7372 // Predicate.
7373 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
7374 // Fallback.
7375 ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
7376 QM_TRY_INSPECT(
7377 const bool& isDirectory,
7378 MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
7380 QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
7382 return Ok{};
7383 })));
7385 QM_TRY(CollectEachFile(
7386 *aDirectory,
7387 [&idbDirectory, &idbDirectoryName](
7388 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
7389 QM_TRY_INSPECT(const auto& leafName,
7390 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
7391 GetLeafName));
7393 if (!leafName.Equals(idbDirectoryName)) {
7394 QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
7397 return Ok{};
7398 }));
7400 QM_TRY(
7401 MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
7404 return NS_OK;
7407 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
7408 OriginProps& aOriginProps, bool* aRemoved) {
7409 AssertIsOnIOThread();
7410 MOZ_ASSERT(aRemoved);
7412 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
7413 QM_TRY(MOZ_TO_RESULT(
7414 MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
7416 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7417 } else {
7418 int64_t timestamp;
7419 nsCString group;
7420 nsCString origin;
7421 Nullable<bool> isApp;
7423 QM_WARNONLY_TRY_UNWRAP(
7424 const auto maybeDirectoryMetadata,
7425 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7426 timestamp, group, origin, isApp)));
7427 if (!maybeDirectoryMetadata) {
7428 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7429 aOriginProps.mNeedsRestore = true;
7430 } else if (!isApp.IsNull()) {
7431 aOriginProps.mIgnore = true;
7435 *aRemoved = false;
7436 return NS_OK;
7439 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
7440 const OriginProps& aOriginProps) {
7441 AssertIsOnIOThread();
7443 if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
7444 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7445 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7446 aOriginProps.mOriginMetadata)));
7448 // Move internal origins to new persistent storage.
7449 if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
7450 PERSISTENCE_TYPE_PERSISTENT) {
7451 if (!mPermanentStorageDir) {
7452 QuotaManager* quotaManager = QuotaManager::Get();
7453 MOZ_ASSERT(quotaManager);
7455 const nsString& permanentStoragePath =
7456 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
7458 QM_TRY_UNWRAP(mPermanentStorageDir,
7459 QM_NewLocalFile(permanentStoragePath));
7462 const nsAString& leafName = aOriginProps.mLeafName;
7464 QM_TRY_INSPECT(const auto& newDirectory,
7465 CloneFileAndAppend(*mPermanentStorageDir, leafName));
7467 QM_TRY_INSPECT(const bool& exists,
7468 MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
7470 if (exists) {
7471 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
7472 NS_ConvertUTF16toUTF8(leafName).get());
7474 QM_TRY(MOZ_TO_RESULT(
7475 aOriginProps.mDirectory->Remove(/* recursive */ true)));
7476 } else {
7477 QM_TRY(MOZ_TO_RESULT(
7478 aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
7481 } else if (aOriginProps.mNeedsRestore) {
7482 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7483 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7484 aOriginProps.mOriginMetadata)));
7485 } else if (!aOriginProps.mIgnore) {
7486 QM_TRY_INSPECT(const auto& file,
7487 CloneFileAndAppend(*aOriginProps.mDirectory,
7488 nsLiteralString(METADATA_FILE_NAME)));
7490 QM_TRY_INSPECT(const auto& stream,
7491 GetBinaryOutputStream(*file, FileFlag::Append));
7493 MOZ_ASSERT(stream);
7495 // Currently unused (used to be isApp).
7496 QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
7499 return NS_OK;
7502 nsresult UpgradeStorageHelperBase::Init() {
7503 AssertIsOnIOThread();
7504 MOZ_ASSERT(mDirectory);
7506 const auto maybePersistenceType =
7507 PersistenceTypeFromFile(*mDirectory, fallible);
7508 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7510 mPersistenceType.init(maybePersistenceType.value());
7512 return NS_OK;
7515 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
7516 const nsCString& aSpec) {
7517 // There's no moving of origin directories between repositories like in the
7518 // CreateOrUpgradeDirectoryMetadataHelper
7519 return *mPersistenceType;
7522 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
7523 OriginProps& aOriginProps, bool* aRemoved) {
7524 AssertIsOnIOThread();
7525 MOZ_ASSERT(aRemoved);
7527 int64_t timestamp;
7528 nsCString group;
7529 nsCString origin;
7530 Nullable<bool> isApp;
7532 QM_WARNONLY_TRY_UNWRAP(
7533 const auto maybeDirectoryMetadata,
7534 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7535 timestamp, group, origin, isApp)));
7536 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7537 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7538 aOriginProps.mNeedsRestore = true;
7539 } else {
7540 aOriginProps.mTimestamp = timestamp;
7543 *aRemoved = false;
7544 return NS_OK;
7547 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
7548 const OriginProps& aOriginProps) {
7549 AssertIsOnIOThread();
7551 // This handles changes in origin string generation from nsIPrincipal,
7552 // especially the change from: appId+inMozBrowser+originNoSuffix
7553 // to: origin (with origin suffix).
7554 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
7555 if (renamed) {
7556 return NS_OK;
7559 if (aOriginProps.mNeedsRestore) {
7560 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7561 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7562 aOriginProps.mOriginMetadata)));
7565 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7566 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7567 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7569 return NS_OK;
7572 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
7573 const OriginProps& aOriginProps) {
7574 AssertIsOnIOThread();
7576 // The Cache API was creating top level morgue directories by accident for
7577 // a short time in nightly. This unfortunately prevents all storage from
7578 // working. So recover these profiles permanently by removing these corrupt
7579 // directories as part of this upgrade.
7581 QM_TRY_INSPECT(const auto& morgueDir,
7582 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
7583 nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
7585 QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
7587 QM_TRY_INSPECT(const bool& exists,
7588 MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
7590 if (exists) {
7591 QM_WARNING("Deleting accidental morgue directory!");
7593 QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
7596 return NS_OK;
7599 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
7600 const OriginProps& aOriginProps) {
7601 AssertIsOnIOThread();
7603 // TODO: This method was empty for some time due to accidental changes done
7604 // in bug 1320404. This led to renaming of origin directories like:
7605 // https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
7606 // to:
7607 // https+++developer.cdn.mozilla.net^inBrowser=1
7608 // instead of just removing them.
7610 const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
7611 if (!originalSuffix.IsEmpty()) {
7612 MOZ_ASSERT(originalSuffix[0] == '^');
7614 if (!URLParams::Parse(
7615 Substring(originalSuffix, 1, originalSuffix.Length() - 1),
7616 [](const nsAString& aName, const nsAString& aValue) {
7617 if (aName.EqualsLiteral("appId")) {
7618 return false;
7621 return true;
7622 })) {
7623 QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
7625 return true;
7629 return false;
7632 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
7633 OriginProps& aOriginProps, bool* aRemoved) {
7634 AssertIsOnIOThread();
7635 MOZ_ASSERT(aRemoved);
7637 QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
7639 QM_TRY(MOZ_TO_RESULT(
7640 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
7642 QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
7643 if (removed) {
7644 *aRemoved = true;
7645 return NS_OK;
7648 int64_t timestamp;
7649 nsCString group;
7650 nsCString origin;
7651 Nullable<bool> isApp;
7652 QM_WARNONLY_TRY_UNWRAP(
7653 const auto maybeDirectoryMetadata,
7654 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7655 timestamp, group, origin, isApp)));
7656 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7657 aOriginProps.mNeedsRestore = true;
7660 nsCString suffix;
7661 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7662 MOZ_TO_RESULT(GetDirectoryMetadata2(
7663 aOriginProps.mDirectory.get(), timestamp, suffix,
7664 group, origin, isApp.SetValue())));
7665 if (!maybeDirectoryMetadata2) {
7666 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7667 aOriginProps.mNeedsRestore2 = true;
7668 } else {
7669 aOriginProps.mTimestamp = timestamp;
7672 *aRemoved = false;
7673 return NS_OK;
7676 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
7677 const OriginProps& aOriginProps) {
7678 AssertIsOnIOThread();
7680 // This handles changes in origin string generation from nsIPrincipal,
7681 // especially the stripping of obsolete origin attributes like addonId.
7682 QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
7683 if (renamed) {
7684 return NS_OK;
7687 if (aOriginProps.mNeedsRestore) {
7688 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7689 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7690 aOriginProps.mOriginMetadata)));
7693 if (aOriginProps.mNeedsRestore2) {
7694 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7695 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7696 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7699 return NS_OK;
7702 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
7703 OriginProps& aOriginProps, bool* aRemoved) {
7704 AssertIsOnIOThread();
7705 MOZ_ASSERT(aRemoved);
7707 QM_TRY(MOZ_TO_RESULT(
7708 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
7710 int64_t timestamp;
7711 nsCString group;
7712 nsCString origin;
7713 Nullable<bool> isApp;
7714 QM_WARNONLY_TRY_UNWRAP(
7715 const auto maybeDirectoryMetadata,
7716 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7717 timestamp, group, origin, isApp)));
7718 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7719 aOriginProps.mNeedsRestore = true;
7722 nsCString suffix;
7723 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7724 MOZ_TO_RESULT(GetDirectoryMetadata2(
7725 aOriginProps.mDirectory.get(), timestamp, suffix,
7726 group, origin, isApp.SetValue())));
7727 if (!maybeDirectoryMetadata2) {
7728 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7729 aOriginProps.mNeedsRestore2 = true;
7730 } else {
7731 aOriginProps.mTimestamp = timestamp;
7734 *aRemoved = false;
7735 return NS_OK;
7738 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
7739 const OriginProps& aOriginProps) {
7740 AssertIsOnIOThread();
7742 if (aOriginProps.mNeedsRestore) {
7743 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7744 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7745 aOriginProps.mOriginMetadata)));
7748 if (aOriginProps.mNeedsRestore2) {
7749 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7750 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7751 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7754 return NS_OK;
7757 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
7758 OriginProps& aOriginProps, bool* aRemoved) {
7759 AssertIsOnIOThread();
7760 MOZ_ASSERT(aRemoved);
7762 QM_TRY(MOZ_TO_RESULT(
7763 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
7765 int64_t timestamp;
7766 nsCString group;
7767 nsCString origin;
7768 Nullable<bool> isApp;
7769 QM_WARNONLY_TRY_UNWRAP(
7770 const auto maybeDirectoryMetadata,
7771 MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
7772 timestamp, group, origin, isApp)));
7773 if (!maybeDirectoryMetadata || isApp.IsNull()) {
7774 aOriginProps.mNeedsRestore = true;
7777 nsCString suffix;
7778 QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
7779 MOZ_TO_RESULT(GetDirectoryMetadata2(
7780 aOriginProps.mDirectory.get(), timestamp, suffix,
7781 group, origin, isApp.SetValue())));
7782 if (!maybeDirectoryMetadata2) {
7783 aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
7784 aOriginProps.mNeedsRestore2 = true;
7785 } else {
7786 aOriginProps.mTimestamp = timestamp;
7789 *aRemoved = false;
7790 return NS_OK;
7793 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
7794 const OriginProps& aOriginProps) {
7795 AssertIsOnIOThread();
7797 if (aOriginProps.mNeedsRestore) {
7798 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata(
7799 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7800 aOriginProps.mOriginMetadata)));
7803 if (aOriginProps.mNeedsRestore2) {
7804 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7805 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7806 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7809 return NS_OK;
7812 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
7813 nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
7814 AssertIsOnIOThread();
7816 if (Client::IsDeprecatedClient(aLeafName)) {
7817 QM_WARNING("Deleting deprecated %s client!",
7818 NS_ConvertUTF16toUTF8(aLeafName).get());
7820 QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
7822 aRemoved = true;
7823 } else {
7824 aRemoved = false;
7827 return NS_OK;
7830 nsresult RestoreDirectoryMetadata2Helper::Init() {
7831 AssertIsOnIOThread();
7832 MOZ_ASSERT(mDirectory);
7834 nsCOMPtr<nsIFile> parentDir;
7835 QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
7837 const auto maybePersistenceType =
7838 PersistenceTypeFromFile(*parentDir, fallible);
7839 QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
7841 mPersistenceType.init(maybePersistenceType.value());
7843 return NS_OK;
7846 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
7847 OriginProps originProps(WrapMovingNotNull(mDirectory));
7848 QM_TRY(MOZ_TO_RESULT(originProps.Init(
7849 [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
7851 QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
7853 originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
7855 mOriginProps.AppendElement(std::move(originProps));
7857 QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
7859 return NS_OK;
7862 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
7863 const OriginProps& aOriginProps) {
7864 AssertIsOnIOThread();
7866 // We don't have any approach to restore aPersisted, so reset it to false.
7867 QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
7868 *aOriginProps.mDirectory, aOriginProps.mTimestamp,
7869 /* aPersisted */ false, aOriginProps.mOriginMetadata)));
7871 return NS_OK;
7874 } // namespace mozilla::dom::quota