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