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(&current)));
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(&currentThread)) &&
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, &timestamp);
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