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