Bug 1526591 - Remove devtools.inspector.shapesHighlighter.enabled pref. r=rcaliman
[gecko.git] / dom / quota / ActorsParent.cpp
blob5447c4509af95abd0faa284ae19516354a129d09
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ActorsParent.h"
9 #include "mozIStorageConnection.h"
10 #include "mozIStorageService.h"
11 #include "nsIObjectInputStream.h"
12 #include "nsIObjectOutputStream.h"
13 #include "nsIFile.h"
14 #include "nsIFileStreams.h"
15 #include "nsIObserverService.h"
16 #include "nsIPermissionManager.h"
17 #include "nsIPrincipal.h"
18 #include "nsIRunnable.h"
19 #include "nsISimpleEnumerator.h"
20 #include "nsIScriptObjectPrincipal.h"
21 #include "nsIScriptSecurityManager.h"
22 #include "nsISupportsPrimitives.h"
23 #include "nsITimer.h"
24 #include "nsIURI.h"
25 #include "nsPIDOMWindow.h"
27 #include <algorithm>
28 #include "GeckoProfiler.h"
29 #include "mozilla/Atomics.h"
30 #include "mozilla/BasePrincipal.h"
31 #include "mozilla/CondVar.h"
32 #include "mozilla/Telemetry.h"
33 #include "mozilla/dom/PContent.h"
34 #include "mozilla/dom/asmjscache/AsmJSCache.h"
35 #include "mozilla/dom/cache/QuotaClient.h"
36 #include "mozilla/dom/indexedDB/ActorsParent.h"
37 #include "mozilla/dom/localstorage/ActorsParent.h"
38 #include "mozilla/dom/quota/PQuotaParent.h"
39 #include "mozilla/dom/quota/PQuotaRequestParent.h"
40 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
41 #include "mozilla/dom/simpledb/ActorsParent.h"
42 #include "mozilla/dom/StorageActivityService.h"
43 #include "mozilla/dom/StorageDBUpdater.h"
44 #include "mozilla/ipc/BackgroundParent.h"
45 #include "mozilla/ipc/BackgroundUtils.h"
46 #include "mozilla/IntegerRange.h"
47 #include "mozilla/Mutex.h"
48 #include "mozilla/Preferences.h"
49 #include "mozilla/Services.h"
50 #include "mozilla/StaticPtr.h"
51 #include "mozilla/TextUtils.h"
52 #include "mozilla/Telemetry.h"
53 #include "mozilla/TypeTraits.h"
54 #include "mozilla/Unused.h"
55 #include "mozStorageCID.h"
56 #include "mozStorageHelper.h"
57 #include "nsAppDirectoryServiceDefs.h"
58 #include "nsComponentManagerUtils.h"
59 #include "nsAboutProtocolUtils.h"
60 #include "nsCharSeparatedTokenizer.h"
61 #include "nsContentUtils.h"
62 #include "nsCRTGlue.h"
63 #include "nsDirectoryServiceUtils.h"
64 #include "nsEscape.h"
65 #include "nsNetUtil.h"
66 #include "nsPrintfCString.h"
67 #include "nsScriptSecurityManager.h"
68 #include "nsThreadUtils.h"
69 #include "nsXULAppAPI.h"
70 #include "prio.h"
71 #include "xpcpublic.h"
73 #include "OriginScope.h"
74 #include "QuotaManager.h"
75 #include "QuotaManagerService.h"
76 #include "QuotaObject.h"
77 #include "UsageInfo.h"
79 #define DISABLE_ASSERTS_FOR_FUZZING 0
81 #if DISABLE_ASSERTS_FOR_FUZZING
82 # define ASSERT_UNLESS_FUZZING(...) \
83 do { \
84 } while (0)
85 #else
86 # define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
87 #endif
89 #define UNKNOWN_FILE_WARNING(_leafName) \
90 QM_WARNING("Something (%s) in the directory that doesn't belong!", \
91 NS_ConvertUTF16toUTF8(_leafName).get())
93 // The amount of time, in milliseconds, that our IO thread will stay alive
94 // after the last event it processes.
95 #define DEFAULT_THREAD_TIMEOUT_MS 30000
97 // The amount of time, in milliseconds, that we will wait for active storage
98 // transactions on shutdown before aborting them.
99 #define DEFAULT_SHUTDOWN_TIMER_MS 30000
101 // Preference that users can set to override temporary storage smart limit
102 // calculation.
103 #define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit"
104 #define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize"
106 // Preference that is used to enable testing features
107 #define PREF_TESTING_FEATURES "dom.quotaManager.testing"
109 // profile-before-change, when we need to shut down quota manager
110 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
112 #define KB *1024ULL
113 #define MB *1024ULL KB
114 #define GB *1024ULL MB
116 namespace mozilla {
117 namespace dom {
118 namespace quota {
120 using namespace mozilla::ipc;
122 // We want profiles to be platform-independent so we always need to replace
123 // the same characters on every platform. Windows has the most extensive set
124 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
125 // FILE_PATH_SEPARATOR.
126 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
128 namespace {
130 /*******************************************************************************
131 * Constants
132 ******************************************************************************/
134 const uint32_t kSQLitePageSizeOverride = 512;
136 const uint32_t kHackyDowngradeMajorStorageVersion = 2;
137 const uint32_t kHackyDowngradeMinorStorageVersion = 1;
139 // Important version history:
140 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
141 // which caused Firefox 57 release concerns because the major schema upgrade
142 // means anyone downgrading to Firefox 56 will experience a non-operational
143 // QuotaManager and all of its clients.
144 // - Bug 1404344 got very concerned about that and so we decided to effectively
145 // rename 3.0 to 2.1, effective in Firefox 57. This works because post
146 // storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
147 // increases. It also works because all the upgrade did was give the DOM
148 // Cache API QuotaClient an opportunity to create its newly added .padding
149 // files during initialization/upgrade, which isn't functionally necessary as
150 // that can be done on demand.
152 // Major storage version. Bump for backwards-incompatible changes.
153 // (The next major version should be 4 to distinguish from the Bug 1290481
154 // downgrade snafu.)
155 const uint32_t kMajorStorageVersion = 2;
157 // Minor storage version. Bump for backwards-compatible changes.
158 const uint32_t kMinorStorageVersion = 1;
160 // The storage version we store in the SQLite database is a (signed) 32-bit
161 // integer. The major version is left-shifted 16 bits so the max value is
162 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
163 static_assert(kMajorStorageVersion <= 0xFFFF,
164 "Major version needs to fit in 16 bits.");
165 static_assert(kMinorStorageVersion <= 0xFFFF,
166 "Minor version needs to fit in 16 bits.");
168 const int32_t kStorageVersion =
169 int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
171 // See comments above about why these are a thing.
172 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
173 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
175 static_assert(static_cast<uint32_t>(StorageType::Persistent) ==
176 static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
177 "Enum values should match.");
179 static_assert(static_cast<uint32_t>(StorageType::Temporary) ==
180 static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
181 "Enum values should match.");
183 static_assert(static_cast<uint32_t>(StorageType::Default) ==
184 static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
185 "Enum values should match.");
187 const char kChromeOrigin[] = "chrome";
188 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
189 const char kIndexedDBOriginPrefix[] = "indexeddb://";
190 const char kResourceOriginPrefix[] = "resource://";
192 #define INDEXEDDB_DIRECTORY_NAME "indexedDB"
193 #define STORAGE_DIRECTORY_NAME "storage"
194 #define PERSISTENT_DIRECTORY_NAME "persistent"
195 #define PERMANENT_DIRECTORY_NAME "permanent"
196 #define TEMPORARY_DIRECTORY_NAME "temporary"
197 #define DEFAULT_DIRECTORY_NAME "default"
199 enum AppId {
200 kNoAppId = nsIScriptSecurityManager::NO_APP_ID,
201 kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID
204 #define STORAGE_FILE_NAME "storage.sqlite"
206 // The name of the file that we use to load/save the last access time of an
207 // origin.
208 // XXX We should get rid of old metadata files at some point, bug 1343576.
209 #define METADATA_FILE_NAME ".metadata"
210 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
211 #define METADATA_V2_FILE_NAME ".metadata-v2"
212 #define METADATA_V2_TMP_FILE_NAME ".metadata-v2-tmp"
214 #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite"
215 #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite"
216 #define LS_ARCHIVE_TMP_FILE_NAME "ls-archive-tmp.sqlite"
218 /******************************************************************************
219 * SQLite functions
220 ******************************************************************************/
222 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
223 uint32_t aMinorStorageVersion) {
224 return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
227 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
228 return uint32_t(aStorageVersion >> 16);
231 nsresult CreateTables(mozIStorageConnection* aConnection) {
232 AssertIsOnIOThread();
233 MOZ_ASSERT(aConnection);
235 // The database doesn't have any tables for now. It's only used for storage
236 // version checking.
237 // However, this is the place where any future tables should be created.
239 nsresult rv;
241 #ifdef DEBUG
243 int32_t storageVersion;
244 rv = aConnection->GetSchemaVersion(&storageVersion);
245 if (NS_WARN_IF(NS_FAILED(rv))) {
246 return rv;
249 MOZ_ASSERT(storageVersion == 0);
251 #endif
253 rv = aConnection->SetSchemaVersion(kStorageVersion);
254 if (NS_WARN_IF(NS_FAILED(rv))) {
255 return rv;
258 return NS_OK;
261 nsresult CreateWebAppsStoreConnection(nsIFile* aWebAppsStoreFile,
262 mozIStorageService* aStorageService,
263 mozIStorageConnection** aConnection) {
264 AssertIsOnIOThread();
265 MOZ_ASSERT(aWebAppsStoreFile);
266 MOZ_ASSERT(aStorageService);
267 MOZ_ASSERT(aConnection);
269 // Check if the old database exists at all.
270 bool exists;
271 nsresult rv = aWebAppsStoreFile->Exists(&exists);
272 if (NS_WARN_IF(NS_FAILED(rv))) {
273 return rv;
276 if (!exists) {
277 // webappsstore.sqlite doesn't exist, return a null connection.
278 *aConnection = nullptr;
279 return NS_OK;
282 bool isDirectory;
283 rv = aWebAppsStoreFile->IsDirectory(&isDirectory);
284 if (NS_WARN_IF(NS_FAILED(rv))) {
285 return rv;
288 if (isDirectory) {
289 QM_WARNING("webappsstore.sqlite is not a file!");
290 *aConnection = nullptr;
291 return NS_OK;
294 nsCOMPtr<mozIStorageConnection> connection;
295 rv = aStorageService->OpenUnsharedDatabase(aWebAppsStoreFile,
296 getter_AddRefs(connection));
297 if (rv == NS_ERROR_FILE_CORRUPTED) {
298 // Don't throw an error, leave a corrupted webappsstore database as it is.
299 *aConnection = nullptr;
300 return NS_OK;
302 if (NS_WARN_IF(NS_FAILED(rv))) {
303 return rv;
306 rv = StorageDBUpdater::Update(connection);
307 if (NS_FAILED(rv)) {
308 // Don't throw an error, leave a non-updateable webappsstore database as
309 // it is.
310 *aConnection = nullptr;
311 return NS_OK;
314 connection.forget(aConnection);
315 return NS_OK;
318 /******************************************************************************
319 * Quota manager class declarations
320 ******************************************************************************/
322 } // namespace
324 class DirectoryLockImpl final : public DirectoryLock {
325 RefPtr<QuotaManager> mQuotaManager;
327 const Nullable<PersistenceType> mPersistenceType;
328 const nsCString mGroup;
329 const OriginScope mOriginScope;
330 const Nullable<Client::Type> mClientType;
331 RefPtr<OpenDirectoryListener> mOpenListener;
333 nsTArray<DirectoryLockImpl*> mBlocking;
334 nsTArray<DirectoryLockImpl*> mBlockedOn;
336 const bool mExclusive;
338 // Internal quota manager operations use this flag to prevent directory lock
339 // registraction/unregistration from updating origin access time, etc.
340 const bool mInternal;
342 bool mInvalidated;
344 public:
345 DirectoryLockImpl(QuotaManager* aQuotaManager,
346 const Nullable<PersistenceType>& aPersistenceType,
347 const nsACString& aGroup, const OriginScope& aOriginScope,
348 const Nullable<Client::Type>& aClientType, bool aExclusive,
349 bool aInternal, OpenDirectoryListener* aOpenListener);
351 void AssertIsOnOwningThread() const
352 #ifdef DEBUG
354 #else
357 #endif
359 const Nullable<PersistenceType>& GetPersistenceType() const {
360 return mPersistenceType;
363 const nsACString& GetGroup() const { return mGroup; }
365 const OriginScope& GetOriginScope() const { return mOriginScope; }
367 const Nullable<Client::Type>& GetClientType() const { return mClientType; }
369 bool IsInternal() const { return mInternal; }
371 bool ShouldUpdateLockTable() {
372 return !mInternal &&
373 mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT;
376 // Test whether this DirectoryLock needs to wait for the given lock.
377 bool MustWaitFor(const DirectoryLockImpl& aLock);
379 void AddBlockingLock(DirectoryLockImpl* aLock) {
380 AssertIsOnOwningThread();
382 mBlocking.AppendElement(aLock);
385 const nsTArray<DirectoryLockImpl*>& GetBlockedOnLocks() { return mBlockedOn; }
387 void AddBlockedOnLock(DirectoryLockImpl* aLock) {
388 AssertIsOnOwningThread();
390 mBlockedOn.AppendElement(aLock);
393 void MaybeUnblock(DirectoryLockImpl* aLock) {
394 AssertIsOnOwningThread();
396 mBlockedOn.RemoveElement(aLock);
397 if (mBlockedOn.IsEmpty()) {
398 NotifyOpenListener();
402 void NotifyOpenListener();
404 void Invalidate() {
405 AssertIsOnOwningThread();
407 mInvalidated = true;
410 NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl, override)
412 private:
413 ~DirectoryLockImpl();
416 class QuotaManager::CreateRunnable final : public BackgroundThreadObject,
417 public Runnable {
418 nsCOMPtr<nsIEventTarget> mMainEventTarget;
419 nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
420 nsString mBaseDirPath;
421 RefPtr<QuotaManager> mManager;
422 nsresult mResultCode;
424 enum class State {
425 Initial,
426 CreatingManager,
427 RegisteringObserver,
428 CallingCallbacks,
429 Completed
432 State mState;
434 public:
435 explicit CreateRunnable(nsIEventTarget* aMainEventTarget)
436 : Runnable("dom::quota::QuotaManager::CreateRunnable"),
437 mMainEventTarget(aMainEventTarget),
438 mResultCode(NS_OK),
439 mState(State::Initial) {
440 AssertIsOnBackgroundThread();
443 void AddCallback(nsIRunnable* aCallback) {
444 AssertIsOnOwningThread();
445 MOZ_ASSERT(aCallback);
447 mCallbacks.AppendElement(aCallback);
450 private:
451 ~CreateRunnable() {}
453 nsresult Init();
455 nsresult CreateManager();
457 nsresult RegisterObserver();
459 void CallCallbacks();
461 State GetNextState(nsCOMPtr<nsIEventTarget>& aThread);
463 NS_DECL_NSIRUNNABLE
466 class QuotaManager::ShutdownRunnable final : public Runnable {
467 // Only touched on the main thread.
468 bool& mDone;
470 public:
471 explicit ShutdownRunnable(bool& aDone)
472 : Runnable("dom::quota::QuotaManager::ShutdownRunnable"), mDone(aDone) {
473 MOZ_ASSERT(NS_IsMainThread());
476 private:
477 ~ShutdownRunnable() {}
479 NS_DECL_NSIRUNNABLE
482 class QuotaManager::ShutdownObserver final : public nsIObserver {
483 nsCOMPtr<nsIEventTarget> mBackgroundThread;
485 public:
486 explicit ShutdownObserver(nsIEventTarget* aBackgroundThread)
487 : mBackgroundThread(aBackgroundThread) {
488 MOZ_ASSERT(NS_IsMainThread());
491 NS_DECL_ISUPPORTS
492 NS_DECL_NSIOBSERVER
494 private:
495 ~ShutdownObserver() { MOZ_ASSERT(NS_IsMainThread()); }
498 namespace {
500 /*******************************************************************************
501 * Local class declarations
502 ******************************************************************************/
504 } // namespace
506 class OriginInfo final {
507 friend class GroupInfo;
508 friend class QuotaManager;
509 friend class QuotaObject;
511 public:
512 OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, uint64_t aUsage,
513 int64_t aAccessTime, bool aPersisted);
515 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
517 int64_t LockedAccessTime() const {
518 AssertCurrentThreadOwnsQuotaMutex();
520 return mAccessTime;
523 bool LockedPersisted() const {
524 AssertCurrentThreadOwnsQuotaMutex();
526 return mPersisted;
529 private:
530 // Private destructor, to discourage deletion outside of Release():
531 ~OriginInfo() {
532 MOZ_COUNT_DTOR(OriginInfo);
534 MOZ_ASSERT(!mQuotaObjects.Count());
537 void LockedDecreaseUsage(int64_t aSize);
539 void LockedUpdateAccessTime(int64_t aAccessTime) {
540 AssertCurrentThreadOwnsQuotaMutex();
542 mAccessTime = aAccessTime;
545 void LockedPersist();
547 nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
549 GroupInfo* mGroupInfo;
550 const nsCString mOrigin;
551 uint64_t mUsage;
552 int64_t mAccessTime;
553 bool mPersisted;
556 class OriginInfoLRUComparator {
557 public:
558 bool Equals(const OriginInfo* a, const OriginInfo* b) const {
559 return a && b ? a->LockedAccessTime() == b->LockedAccessTime()
560 : !a && !b ? true : false;
563 bool LessThan(const OriginInfo* a, const OriginInfo* b) const {
564 return a && b ? a->LockedAccessTime() < b->LockedAccessTime()
565 : b ? true : false;
569 class GroupInfo final {
570 friend class GroupInfoPair;
571 friend class OriginInfo;
572 friend class QuotaManager;
573 friend class QuotaObject;
575 public:
576 GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType,
577 const nsACString& aGroup)
578 : mGroupInfoPair(aGroupInfoPair),
579 mPersistenceType(aPersistenceType),
580 mGroup(aGroup),
581 mUsage(0) {
582 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
584 MOZ_COUNT_CTOR(GroupInfo);
587 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
589 private:
590 // Private destructor, to discourage deletion outside of Release():
591 ~GroupInfo() { MOZ_COUNT_DTOR(GroupInfo); }
593 already_AddRefed<OriginInfo> LockedGetOriginInfo(const nsACString& aOrigin);
595 void LockedAddOriginInfo(OriginInfo* aOriginInfo);
597 void LockedRemoveOriginInfo(const nsACString& aOrigin);
599 void LockedRemoveOriginInfos();
601 bool LockedHasOriginInfos() {
602 AssertCurrentThreadOwnsQuotaMutex();
604 return !mOriginInfos.IsEmpty();
607 nsTArray<RefPtr<OriginInfo>> mOriginInfos;
609 GroupInfoPair* mGroupInfoPair;
610 PersistenceType mPersistenceType;
611 nsCString mGroup;
612 uint64_t mUsage;
615 class GroupInfoPair {
616 friend class QuotaManager;
617 friend class QuotaObject;
619 public:
620 GroupInfoPair() { MOZ_COUNT_CTOR(GroupInfoPair); }
622 ~GroupInfoPair() { MOZ_COUNT_DTOR(GroupInfoPair); }
624 private:
625 already_AddRefed<GroupInfo> LockedGetGroupInfo(
626 PersistenceType aPersistenceType) {
627 AssertCurrentThreadOwnsQuotaMutex();
628 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
630 RefPtr<GroupInfo> groupInfo =
631 GetGroupInfoForPersistenceType(aPersistenceType);
632 return groupInfo.forget();
635 void LockedSetGroupInfo(PersistenceType aPersistenceType,
636 GroupInfo* aGroupInfo) {
637 AssertCurrentThreadOwnsQuotaMutex();
638 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
640 RefPtr<GroupInfo>& groupInfo =
641 GetGroupInfoForPersistenceType(aPersistenceType);
642 groupInfo = aGroupInfo;
645 void LockedClearGroupInfo(PersistenceType aPersistenceType) {
646 AssertCurrentThreadOwnsQuotaMutex();
647 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
649 RefPtr<GroupInfo>& groupInfo =
650 GetGroupInfoForPersistenceType(aPersistenceType);
651 groupInfo = nullptr;
654 bool LockedHasGroupInfos() {
655 AssertCurrentThreadOwnsQuotaMutex();
657 return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo;
660 RefPtr<GroupInfo>& GetGroupInfoForPersistenceType(
661 PersistenceType aPersistenceType);
663 RefPtr<GroupInfo> mTemporaryStorageGroupInfo;
664 RefPtr<GroupInfo> mDefaultStorageGroupInfo;
667 namespace {
669 class CollectOriginsHelper final : public Runnable {
670 uint64_t mMinSizeToBeFreed;
672 Mutex& mMutex;
673 CondVar mCondVar;
675 // The members below are protected by mMutex.
676 nsTArray<RefPtr<DirectoryLockImpl>> mLocks;
677 uint64_t mSizeToBeFreed;
678 bool mWaiting;
680 public:
681 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
683 // Blocks the current thread until origins are collected on the main thread.
684 // The returned value contains an aggregate size of those origins.
685 int64_t BlockAndReturnOriginsForEviction(
686 nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
688 private:
689 ~CollectOriginsHelper() {}
691 NS_IMETHOD
692 Run() override;
695 class OriginOperationBase : public BackgroundThreadObject, public Runnable {
696 protected:
697 nsresult mResultCode;
699 enum State {
700 // Not yet run.
701 State_Initial,
703 // Running initialization on the main thread.
704 State_Initializing,
706 // Running initialization on the owning thread.
707 State_FinishingInit,
709 // Running quota manager initialization on the owning thread.
710 State_CreatingQuotaManager,
712 // Running on the owning thread in the listener for OpenDirectory.
713 State_DirectoryOpenPending,
715 // Running on the IO thread.
716 State_DirectoryWorkOpen,
718 // Running on the owning thread after all work is done.
719 State_UnblockingOpen,
721 // All done.
722 State_Complete
725 private:
726 State mState;
727 bool mActorDestroyed;
729 protected:
730 bool mNeedsMainThreadInit;
731 bool mNeedsQuotaManagerInit;
733 public:
734 void NoteActorDestroyed() {
735 AssertIsOnOwningThread();
737 mActorDestroyed = true;
740 bool IsActorDestroyed() const {
741 AssertIsOnOwningThread();
743 return mActorDestroyed;
746 protected:
747 explicit OriginOperationBase(
748 nsIEventTarget* aOwningThread = GetCurrentThreadEventTarget())
749 : BackgroundThreadObject(aOwningThread),
750 Runnable("dom::quota::OriginOperationBase"),
751 mResultCode(NS_OK),
752 mState(State_Initial),
753 mActorDestroyed(false),
754 mNeedsMainThreadInit(false),
755 mNeedsQuotaManagerInit(false) {}
757 // Reference counted.
758 virtual ~OriginOperationBase() {
759 MOZ_ASSERT(mState == State_Complete);
760 MOZ_ASSERT(mActorDestroyed);
763 #ifdef DEBUG
764 State GetState() const { return mState; }
765 #endif
767 void SetState(State aState) {
768 MOZ_ASSERT(mState == State_Initial);
769 mState = aState;
772 void AdvanceState() {
773 switch (mState) {
774 case State_Initial:
775 mState = State_Initializing;
776 return;
777 case State_Initializing:
778 mState = State_FinishingInit;
779 return;
780 case State_FinishingInit:
781 mState = State_CreatingQuotaManager;
782 return;
783 case State_CreatingQuotaManager:
784 mState = State_DirectoryOpenPending;
785 return;
786 case State_DirectoryOpenPending:
787 mState = State_DirectoryWorkOpen;
788 return;
789 case State_DirectoryWorkOpen:
790 mState = State_UnblockingOpen;
791 return;
792 case State_UnblockingOpen:
793 mState = State_Complete;
794 return;
795 default:
796 MOZ_CRASH("Bad state!");
800 NS_IMETHOD
801 Run() override;
803 virtual nsresult DoInitOnMainThread() { return NS_OK; }
805 virtual void Open() = 0;
807 nsresult DirectoryOpen();
809 virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) = 0;
811 void Finish(nsresult aResult);
813 virtual void UnblockOpen() = 0;
815 private:
816 nsresult Init();
818 nsresult InitOnMainThread();
820 nsresult FinishInit();
822 nsresult QuotaManagerOpen();
824 nsresult DirectoryWork();
827 class FinalizeOriginEvictionOp : public OriginOperationBase {
828 nsTArray<RefPtr<DirectoryLockImpl>> mLocks;
830 public:
831 FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread,
832 nsTArray<RefPtr<DirectoryLockImpl>>& aLocks)
833 : OriginOperationBase(aBackgroundThread) {
834 MOZ_ASSERT(!NS_IsMainThread());
836 mLocks.SwapElements(aLocks);
839 void Dispatch();
841 void RunOnIOThreadImmediately();
843 private:
844 ~FinalizeOriginEvictionOp() {}
846 virtual void Open() override;
848 virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
850 virtual void UnblockOpen() override;
853 class NormalOriginOperationBase : public OriginOperationBase,
854 public OpenDirectoryListener {
855 RefPtr<DirectoryLock> mDirectoryLock;
857 protected:
858 Nullable<PersistenceType> mPersistenceType;
859 OriginScope mOriginScope;
860 Nullable<Client::Type> mClientType;
861 mozilla::Atomic<bool> mCanceled;
862 const bool mExclusive;
864 public:
865 void RunImmediately() {
866 MOZ_ASSERT(GetState() == State_Initial);
868 MOZ_ALWAYS_SUCCEEDS(this->Run());
871 protected:
872 NormalOriginOperationBase(const Nullable<PersistenceType>& aPersistenceType,
873 const OriginScope& aOriginScope, bool aExclusive)
874 : mPersistenceType(aPersistenceType),
875 mOriginScope(aOriginScope),
876 mExclusive(aExclusive) {
877 AssertIsOnOwningThread();
880 ~NormalOriginOperationBase() {}
882 private:
883 // Need to declare refcounting unconditionally, because
884 // OpenDirectoryListener has pure-virtual refcounting.
885 NS_DECL_ISUPPORTS_INHERITED
887 virtual void Open() override;
889 virtual void UnblockOpen() override;
891 // OpenDirectoryListener overrides.
892 virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
894 virtual void DirectoryLockFailed() override;
896 // Used to send results before unblocking open.
897 virtual void SendResults() = 0;
900 class SaveOriginAccessTimeOp : public NormalOriginOperationBase {
901 int64_t mTimestamp;
903 public:
904 SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
905 const nsACString& aOrigin, int64_t aTimestamp)
906 : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
907 OriginScope::FromOrigin(aOrigin),
908 /* aExclusive */ false),
909 mTimestamp(aTimestamp) {
910 AssertIsOnOwningThread();
913 private:
914 ~SaveOriginAccessTimeOp() {}
916 virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
918 virtual void SendResults() override;
921 /*******************************************************************************
922 * Actor class declarations
923 ******************************************************************************/
925 class Quota final : public PQuotaParent {
926 #ifdef DEBUG
927 bool mActorDestroyed;
928 #endif
930 public:
931 Quota();
933 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
935 private:
936 ~Quota();
938 void StartIdleMaintenance();
940 // IPDL methods.
941 virtual void ActorDestroy(ActorDestroyReason aWhy) override;
943 virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(
944 const UsageRequestParams& aParams) override;
946 virtual mozilla::ipc::IPCResult RecvPQuotaUsageRequestConstructor(
947 PQuotaUsageRequestParent* aActor,
948 const UsageRequestParams& aParams) override;
950 virtual bool DeallocPQuotaUsageRequestParent(
951 PQuotaUsageRequestParent* aActor) override;
953 virtual PQuotaRequestParent* AllocPQuotaRequestParent(
954 const RequestParams& aParams) override;
956 virtual mozilla::ipc::IPCResult RecvPQuotaRequestConstructor(
957 PQuotaRequestParent* aActor, const RequestParams& aParams) override;
959 virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
961 virtual mozilla::ipc::IPCResult RecvStartIdleMaintenance() override;
963 virtual mozilla::ipc::IPCResult RecvStopIdleMaintenance() override;
966 class QuotaUsageRequestBase : public NormalOriginOperationBase,
967 public PQuotaUsageRequestParent {
968 public:
969 // May be overridden by subclasses if they need to perform work on the
970 // background thread before being run.
971 virtual bool Init(Quota* aQuota);
973 protected:
974 QuotaUsageRequestBase()
975 : NormalOriginOperationBase(Nullable<PersistenceType>(),
976 OriginScope::FromNull(),
977 /* aExclusive */ false) {}
979 nsresult GetUsageForOrigin(QuotaManager* aQuotaManager,
980 PersistenceType aPersistenceType,
981 const nsACString& aGroup,
982 const nsACString& aOrigin, UsageInfo* aUsageInfo);
984 // Subclasses use this override to set the IPDL response value.
985 virtual void GetResponse(UsageRequestResponse& aResponse) = 0;
987 private:
988 void SendResults() override;
990 // IPDL methods.
991 void ActorDestroy(ActorDestroyReason aWhy) override;
993 mozilla::ipc::IPCResult RecvCancel() final;
996 class GetUsageOp final : public QuotaUsageRequestBase {
997 nsTArray<OriginUsage> mOriginUsages;
998 nsDataHashtable<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
1000 bool mGetAll;
1002 public:
1003 explicit GetUsageOp(const UsageRequestParams& aParams);
1005 private:
1006 ~GetUsageOp() {}
1008 nsresult TraverseRepository(QuotaManager* aQuotaManager,
1009 PersistenceType aPersistenceType);
1011 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1013 void GetResponse(UsageRequestResponse& aResponse) override;
1016 class GetOriginUsageOp final : public QuotaUsageRequestBase {
1017 // If mGetGroupUsage is false, we use mUsageInfo to record the origin usage
1018 // and the file usage. Otherwise, we use it to record the group usage and the
1019 // limit.
1020 UsageInfo mUsageInfo;
1022 const OriginUsageParams mParams;
1023 nsCString mSuffix;
1024 nsCString mGroup;
1025 bool mGetGroupUsage;
1027 public:
1028 explicit GetOriginUsageOp(const UsageRequestParams& aParams);
1030 MOZ_IS_CLASS_INIT bool Init(Quota* aQuota) override;
1032 private:
1033 ~GetOriginUsageOp() {}
1035 MOZ_IS_CLASS_INIT virtual nsresult DoInitOnMainThread() override;
1037 virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1039 void GetResponse(UsageRequestResponse& aResponse) override;
1042 class QuotaRequestBase : public NormalOriginOperationBase,
1043 public PQuotaRequestParent {
1044 public:
1045 // May be overridden by subclasses if they need to perform work on the
1046 // background thread before being run.
1047 virtual bool Init(Quota* aQuota);
1049 protected:
1050 explicit QuotaRequestBase(bool aExclusive)
1051 : NormalOriginOperationBase(Nullable<PersistenceType>(),
1052 OriginScope::FromNull(), aExclusive) {}
1054 // Subclasses use this override to set the IPDL response value.
1055 virtual void GetResponse(RequestResponse& aResponse) = 0;
1057 private:
1058 virtual void SendResults() override;
1060 // IPDL methods.
1061 virtual void ActorDestroy(ActorDestroyReason aWhy) override;
1064 class InitOp final : public QuotaRequestBase {
1065 public:
1066 InitOp() : QuotaRequestBase(/* aExclusive */ false) {
1067 AssertIsOnOwningThread();
1070 private:
1071 ~InitOp() {}
1073 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1075 void GetResponse(RequestResponse& aResponse) override;
1078 class InitTemporaryStorageOp final : public QuotaRequestBase {
1079 public:
1080 InitTemporaryStorageOp() : QuotaRequestBase(/* aExclusive */ false) {
1081 AssertIsOnOwningThread();
1084 private:
1085 ~InitTemporaryStorageOp() {}
1087 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1089 void GetResponse(RequestResponse& aResponse) override;
1092 class InitOriginOp final : public QuotaRequestBase {
1093 const InitOriginParams mParams;
1094 nsCString mSuffix;
1095 nsCString mGroup;
1096 bool mCreated;
1098 public:
1099 explicit InitOriginOp(const RequestParams& aParams);
1101 bool Init(Quota* aQuota) override;
1103 private:
1104 ~InitOriginOp() {}
1106 nsresult DoInitOnMainThread() override;
1108 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1110 void GetResponse(RequestResponse& aResponse) override;
1113 class ResetOrClearOp final : public QuotaRequestBase {
1114 const bool mClear;
1116 public:
1117 explicit ResetOrClearOp(bool aClear)
1118 : QuotaRequestBase(/* aExclusive */ true), mClear(aClear) {
1119 AssertIsOnOwningThread();
1122 private:
1123 ~ResetOrClearOp() {}
1125 void DeleteFiles(QuotaManager* aQuotaManager);
1127 virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1129 virtual void GetResponse(RequestResponse& aResponse) override;
1132 class ClearRequestBase : public QuotaRequestBase {
1133 protected:
1134 const bool mClear;
1136 protected:
1137 ClearRequestBase(bool aExclusive, bool aClear)
1138 : QuotaRequestBase(aExclusive), mClear(aClear) {
1139 AssertIsOnOwningThread();
1142 void DeleteFiles(QuotaManager* aQuotaManager,
1143 PersistenceType aPersistenceType);
1145 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1148 class ClearOriginOp final : public ClearRequestBase {
1149 const ClearResetOriginParams mParams;
1151 public:
1152 explicit ClearOriginOp(const RequestParams& aParams);
1154 bool Init(Quota* aQuota) override;
1156 private:
1157 ~ClearOriginOp() {}
1159 nsresult DoInitOnMainThread() override;
1161 void GetResponse(RequestResponse& aResponse) override;
1164 class ClearDataOp final : public ClearRequestBase {
1165 const ClearDataParams mParams;
1167 public:
1168 explicit ClearDataOp(const RequestParams& aParams);
1170 bool Init(Quota* aQuota) override;
1172 private:
1173 ~ClearDataOp() {}
1175 nsresult DoInitOnMainThread() override;
1177 void GetResponse(RequestResponse& aResponse) override;
1180 class PersistRequestBase : public QuotaRequestBase {
1181 const PrincipalInfo mPrincipalInfo;
1183 protected:
1184 nsCString mSuffix;
1185 nsCString mGroup;
1187 public:
1188 bool Init(Quota* aQuota) override;
1190 protected:
1191 explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
1193 private:
1194 nsresult DoInitOnMainThread() override;
1197 class PersistedOp final : public PersistRequestBase {
1198 bool mPersisted;
1200 public:
1201 explicit PersistedOp(const RequestParams& aParams);
1203 private:
1204 ~PersistedOp() {}
1206 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1208 void GetResponse(RequestResponse& aResponse) override;
1211 class PersistOp final : public PersistRequestBase {
1212 public:
1213 explicit PersistOp(const RequestParams& aParams);
1215 private:
1216 ~PersistOp() {}
1218 nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
1220 void GetResponse(RequestResponse& aResponse) override;
1223 class StoragePressureRunnable final : public Runnable {
1224 const uint64_t mUsage;
1226 public:
1227 explicit StoragePressureRunnable(uint64_t aUsage)
1228 : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
1229 mUsage(aUsage) {}
1231 private:
1232 ~StoragePressureRunnable() = default;
1234 NS_DECL_NSIRUNNABLE
1237 /*******************************************************************************
1238 * Helper Functions
1239 ******************************************************************************/
1241 template <typename T, bool = mozilla::IsUnsigned<T>::value>
1242 struct IntChecker {
1243 static void Assert(T aInt) {
1244 static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
1245 MOZ_ASSERT(aInt >= 0);
1249 template <typename T>
1250 struct IntChecker<T, true> {
1251 static void Assert(T aInt) {
1252 static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
1256 template <typename T>
1257 void AssertNoOverflow(uint64_t aDest, T aArg) {
1258 IntChecker<T>::Assert(aDest);
1259 IntChecker<T>::Assert(aArg);
1260 MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
1263 template <typename T, typename U>
1264 void AssertNoUnderflow(T aDest, U aArg) {
1265 IntChecker<T>::Assert(aDest);
1266 IntChecker<T>::Assert(aArg);
1267 MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
1270 inline bool IsDotFile(const nsAString& aFileName) {
1271 return QuotaManager::IsDotFile(aFileName);
1274 inline bool IsOSMetadata(const nsAString& aFileName) {
1275 return QuotaManager::IsOSMetadata(aFileName);
1278 bool IsOriginMetadata(const nsAString& aFileName) {
1279 return aFileName.EqualsLiteral(METADATA_FILE_NAME) ||
1280 aFileName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
1281 IsOSMetadata(aFileName);
1284 bool IsTempMetadata(const nsAString& aFileName) {
1285 return aFileName.EqualsLiteral(METADATA_TMP_FILE_NAME) ||
1286 aFileName.EqualsLiteral(METADATA_V2_TMP_FILE_NAME);
1289 } // namespace
1291 BackgroundThreadObject::BackgroundThreadObject()
1292 : mOwningThread(GetCurrentThreadEventTarget()) {
1293 AssertIsOnOwningThread();
1296 BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread)
1297 : mOwningThread(aOwningThread) {}
1299 #ifdef DEBUG
1301 void BackgroundThreadObject::AssertIsOnOwningThread() const {
1302 AssertIsOnBackgroundThread();
1303 MOZ_ASSERT(mOwningThread);
1304 bool current;
1305 MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
1306 MOZ_ASSERT(current);
1309 #endif // DEBUG
1311 nsIEventTarget* BackgroundThreadObject::OwningThread() const {
1312 MOZ_ASSERT(mOwningThread);
1313 return mOwningThread;
1316 bool IsOnIOThread() {
1317 QuotaManager* quotaManager = QuotaManager::Get();
1318 NS_ASSERTION(quotaManager, "Must have a manager here!");
1320 bool currentThread;
1321 return NS_SUCCEEDED(
1322 quotaManager->IOThread()->IsOnCurrentThread(&currentThread)) &&
1323 currentThread;
1326 void AssertIsOnIOThread() {
1327 NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
1330 void AssertCurrentThreadOwnsQuotaMutex() {
1331 #ifdef DEBUG
1332 QuotaManager* quotaManager = QuotaManager::Get();
1333 NS_ASSERTION(quotaManager, "Must have a manager here!");
1335 quotaManager->AssertCurrentThreadOwnsQuotaMutex();
1336 #endif
1339 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
1340 // Get leaf of file path
1341 for (const char* p = aFile; *p; ++p) {
1342 if (*p == '/' && *(p + 1)) {
1343 aFile = p + 1;
1347 nsContentUtils::LogSimpleConsoleError(
1348 NS_ConvertUTF8toUTF16(
1349 nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
1350 "quota",
1351 false /* Quota Manager is not active in private browsing mode */);
1354 namespace {
1356 StaticRefPtr<QuotaManager> gInstance;
1357 bool gCreateFailed = false;
1358 StaticRefPtr<QuotaManager::CreateRunnable> gCreateRunnable;
1359 mozilla::Atomic<bool> gShutdown(false);
1361 // Constants for temporary storage limit computing.
1362 static const int32_t kDefaultFixedLimitKB = -1;
1363 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
1364 int32_t gFixedLimitKB = kDefaultFixedLimitKB;
1365 uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
1367 bool gTestingEnabled = false;
1369 class StorageOperationBase : public Runnable {
1370 mozilla::Mutex mMutex;
1371 mozilla::CondVar mCondVar;
1372 nsresult mMainThreadResultCode;
1373 bool mWaiting;
1375 protected:
1376 struct OriginProps;
1378 nsTArray<OriginProps> mOriginProps;
1380 nsCOMPtr<nsIFile> mDirectory;
1382 const bool mPersistent;
1384 public:
1385 StorageOperationBase(nsIFile* aDirectory, bool aPersistent)
1386 : Runnable("dom::quota::StorageOperationBase"),
1387 mMutex("StorageOperationBase::mMutex"),
1388 mCondVar(mMutex, "StorageOperationBase::mCondVar"),
1389 mMainThreadResultCode(NS_OK),
1390 mWaiting(true),
1391 mDirectory(aDirectory),
1392 mPersistent(aPersistent) {
1393 AssertIsOnIOThread();
1396 protected:
1397 virtual ~StorageOperationBase() {}
1399 nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
1400 nsACString& aGroup, nsACString& aOrigin,
1401 Nullable<bool>& aIsApp);
1403 // Upgrade helper to load the contents of ".metadata-v2" files from previous
1404 // schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
1405 // method, it is only intended to read current version ".metadata-v2" files.
1406 // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
1407 // because our "storage.sqlite" lets us track the overall version of the
1408 // storage directory.
1409 nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
1410 nsACString& aSuffix, nsACString& aGroup,
1411 nsACString& aOrigin, bool& aIsApp);
1413 nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
1415 nsresult ProcessOriginDirectories();
1417 virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
1419 private:
1420 nsresult RunOnMainThread();
1422 NS_IMETHOD
1423 Run() override;
1426 struct StorageOperationBase::OriginProps {
1427 enum Type { eChrome, eContent, eObsolete };
1429 nsCOMPtr<nsIFile> mDirectory;
1430 nsString mLeafName;
1431 nsCString mSpec;
1432 OriginAttributes mAttrs;
1433 int64_t mTimestamp;
1434 nsCString mSuffix;
1435 nsCString mGroup;
1436 nsCString mOrigin;
1438 Type mType;
1439 bool mNeedsRestore;
1440 bool mNeedsRestore2;
1441 bool mIgnore;
1443 public:
1444 explicit OriginProps()
1445 : mTimestamp(0),
1446 mType(eContent),
1447 mNeedsRestore(false),
1448 mNeedsRestore2(false),
1449 mIgnore(false) {}
1451 nsresult Init(nsIFile* aDirectory);
1454 class MOZ_STACK_CLASS OriginParser final {
1455 public:
1456 enum ResultType { InvalidOrigin, ObsoleteOrigin, ValidOrigin };
1458 private:
1459 static bool IgnoreWhitespace(char16_t /* aChar */) { return false; }
1461 typedef nsCCharSeparatedTokenizerTemplate<IgnoreWhitespace> Tokenizer;
1463 enum SchemeType { eNone, eFile, eAbout };
1465 enum State {
1466 eExpectingAppIdOrScheme,
1467 eExpectingInMozBrowser,
1468 eExpectingScheme,
1469 eExpectingEmptyToken1,
1470 eExpectingEmptyToken2,
1471 eExpectingEmptyTokenOrUniversalFileOrigin,
1472 eExpectingHost,
1473 eExpectingPort,
1474 eExpectingEmptyTokenOrDriveLetterOrPathnameComponent,
1475 eExpectingEmptyTokenOrPathnameComponent,
1477 // We transit from eExpectingHost to this state when we encounter a host
1478 // beginning with "[" which indicates an IPv6 literal. Because we mangle the
1479 // IPv6 ":" delimiter to be a "+", we will receive separate tokens for each
1480 // portion of the IPv6 address, including a final token that ends with "]".
1481 // (Note that we do not mangle "[" or "]".) Note that the URL spec
1482 // explicitly disclaims support for "<zone_id>" and so we don't have to deal
1483 // with that.
1484 eExpectingIPV6Token,
1485 eComplete,
1486 eHandledTrailingSeparator
1489 const nsCString mOrigin;
1490 const OriginAttributes mOriginAttributes;
1491 Tokenizer mTokenizer;
1493 uint32_t mAppId;
1494 nsCString mScheme;
1495 nsCString mHost;
1496 Nullable<uint32_t> mPort;
1497 nsTArray<nsCString> mPathnameComponents;
1498 nsCString mHandledTokens;
1500 SchemeType mSchemeType;
1501 State mState;
1502 bool mInIsolatedMozBrowser;
1503 bool mUniversalFileOrigin;
1504 bool mMaybeDriveLetter;
1505 bool mError;
1507 // Number of group which a IPv6 address has. Should be less than 9.
1508 uint8_t mIPGroup;
1510 public:
1511 OriginParser(const nsACString& aOrigin,
1512 const OriginAttributes& aOriginAttributes)
1513 : mOrigin(aOrigin),
1514 mOriginAttributes(aOriginAttributes),
1515 mTokenizer(aOrigin, '+'),
1516 mAppId(kNoAppId),
1517 mPort(),
1518 mSchemeType(eNone),
1519 mState(eExpectingAppIdOrScheme),
1520 mInIsolatedMozBrowser(false),
1521 mUniversalFileOrigin(false),
1522 mMaybeDriveLetter(false),
1523 mError(false),
1524 mIPGroup(0) {}
1526 static ResultType ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
1527 OriginAttributes* aAttrs);
1529 ResultType Parse(nsACString& aSpec, OriginAttributes* aAttrs);
1531 private:
1532 void HandleScheme(const nsDependentCSubstring& aToken);
1534 void HandlePathnameComponent(const nsDependentCSubstring& aToken);
1536 void HandleToken(const nsDependentCSubstring& aToken);
1538 void HandleTrailingSeparator();
1541 class RepositoryOperationBase : public StorageOperationBase {
1542 public:
1543 RepositoryOperationBase(nsIFile* aDirectory, bool aPersistent)
1544 : StorageOperationBase(aDirectory, aPersistent) {}
1546 nsresult ProcessRepository();
1548 protected:
1549 virtual ~RepositoryOperationBase() {}
1551 template <typename UpgradeMethod>
1552 nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
1553 UpgradeMethod aMethod);
1555 private:
1556 virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1557 bool* aRemoved) = 0;
1560 class CreateOrUpgradeDirectoryMetadataHelper final
1561 : public RepositoryOperationBase {
1562 nsCOMPtr<nsIFile> mPermanentStorageDir;
1564 public:
1565 CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory, bool aPersistent)
1566 : RepositoryOperationBase(aDirectory, aPersistent) {}
1568 private:
1569 nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
1571 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1572 bool* aRemoved) override;
1574 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1577 class UpgradeStorageFrom0_0To1_0Helper final : public RepositoryOperationBase {
1578 public:
1579 UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory, bool aPersistent)
1580 : RepositoryOperationBase(aDirectory, aPersistent) {}
1582 private:
1583 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1584 bool* aRemoved) override;
1586 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1589 class UpgradeStorageFrom1_0To2_0Helper final : public RepositoryOperationBase {
1590 public:
1591 UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory, bool aPersistent)
1592 : RepositoryOperationBase(aDirectory, aPersistent) {}
1594 private:
1595 nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
1597 nsresult MaybeRemoveAppsData(const OriginProps& aOriginProps, bool* aRemoved);
1599 nsresult MaybeStripObsoleteOriginAttributes(const OriginProps& aOriginProps,
1600 bool* aStripped);
1602 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1603 bool* aRemoved) override;
1605 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1608 class UpgradeStorageFrom2_0To2_1Helper final : public RepositoryOperationBase {
1609 public:
1610 UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory, bool aPersistent)
1611 : RepositoryOperationBase(aDirectory, aPersistent) {}
1613 private:
1614 nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
1615 bool* aRemoved) override;
1617 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1620 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
1621 public:
1622 RestoreDirectoryMetadata2Helper(nsIFile* aDirectory, bool aPersistent)
1623 : StorageOperationBase(aDirectory, aPersistent) {}
1625 nsresult RestoreMetadata2File();
1627 private:
1628 nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
1631 void SanitizeOriginString(nsCString& aOrigin) {
1632 #ifdef XP_WIN
1633 NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars,
1634 FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
1635 "Illegal file characters have changed!");
1636 #endif
1638 aOrigin.ReplaceChar(QuotaManager::kReplaceChars, '+');
1641 nsresult CloneStoragePath(nsIFile* aBaseDir, const nsAString& aStorageName,
1642 nsAString& aStoragePath) {
1643 nsresult rv;
1645 nsCOMPtr<nsIFile> storageDir;
1646 rv = aBaseDir->Clone(getter_AddRefs(storageDir));
1647 if (NS_WARN_IF(NS_FAILED(rv))) {
1648 return rv;
1651 rv = storageDir->Append(aStorageName);
1652 if (NS_WARN_IF(NS_FAILED(rv))) {
1653 return rv;
1656 rv = storageDir->GetPath(aStoragePath);
1657 if (NS_WARN_IF(NS_FAILED(rv))) {
1658 return rv;
1661 return NS_OK;
1664 int64_t GetLastModifiedTime(nsIFile* aFile, bool aPersistent) {
1665 AssertIsOnIOThread();
1666 MOZ_ASSERT(aFile);
1668 class MOZ_STACK_CLASS Helper final {
1669 public:
1670 static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
1671 AssertIsOnIOThread();
1672 MOZ_ASSERT(aFile);
1673 MOZ_ASSERT(aTimestamp);
1675 bool isDirectory;
1676 nsresult rv = aFile->IsDirectory(&isDirectory);
1677 if (NS_WARN_IF(NS_FAILED(rv))) {
1678 return rv;
1681 if (!isDirectory) {
1682 nsString leafName;
1683 rv = aFile->GetLeafName(leafName);
1684 if (NS_WARN_IF(NS_FAILED(rv))) {
1685 return rv;
1688 if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
1689 IsDotFile(leafName)) {
1690 return NS_OK;
1693 int64_t timestamp;
1694 rv = aFile->GetLastModifiedTime(&timestamp);
1695 if (NS_WARN_IF(NS_FAILED(rv))) {
1696 return rv;
1699 // Need to convert from milliseconds to microseconds.
1700 MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
1701 timestamp *= int64_t(PR_USEC_PER_MSEC);
1703 if (timestamp > *aTimestamp) {
1704 *aTimestamp = timestamp;
1706 return NS_OK;
1709 nsCOMPtr<nsIDirectoryEnumerator> entries;
1710 rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
1711 if (NS_WARN_IF(NS_FAILED(rv))) {
1712 return rv;
1715 nsCOMPtr<nsIFile> file;
1716 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
1717 file) {
1718 rv = GetLastModifiedTime(file, aTimestamp);
1719 if (NS_WARN_IF(NS_FAILED(rv))) {
1720 return rv;
1723 if (NS_WARN_IF(NS_FAILED(rv))) {
1724 return rv;
1727 return NS_OK;
1731 if (aPersistent) {
1732 return PR_Now();
1735 int64_t timestamp = INT64_MIN;
1736 nsresult rv = Helper::GetLastModifiedTime(aFile, &timestamp);
1737 if (NS_FAILED(rv)) {
1738 timestamp = PR_Now();
1741 return timestamp;
1744 nsresult EnsureDirectory(nsIFile* aDirectory, bool* aCreated) {
1745 AssertIsOnIOThread();
1747 nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
1748 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
1749 bool isDirectory;
1750 rv = aDirectory->IsDirectory(&isDirectory);
1751 NS_ENSURE_SUCCESS(rv, rv);
1752 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
1754 *aCreated = false;
1755 } else {
1756 NS_ENSURE_SUCCESS(rv, rv);
1758 *aCreated = true;
1761 return NS_OK;
1764 enum FileFlag { kTruncateFileFlag, kUpdateFileFlag, kAppendFileFlag };
1766 nsresult GetOutputStream(nsIFile* aFile, FileFlag aFileFlag,
1767 nsIOutputStream** aStream) {
1768 AssertIsOnIOThread();
1770 nsresult rv;
1772 nsCOMPtr<nsIOutputStream> outputStream;
1773 switch (aFileFlag) {
1774 case kTruncateFileFlag: {
1775 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), aFile);
1776 if (NS_WARN_IF(NS_FAILED(rv))) {
1777 return rv;
1780 break;
1783 case kUpdateFileFlag: {
1784 bool exists;
1785 rv = aFile->Exists(&exists);
1786 if (NS_WARN_IF(NS_FAILED(rv))) {
1787 return rv;
1790 if (!exists) {
1791 *aStream = nullptr;
1792 return NS_OK;
1795 nsCOMPtr<nsIFileStream> stream;
1796 rv = NS_NewLocalFileStream(getter_AddRefs(stream), aFile);
1797 if (NS_WARN_IF(NS_FAILED(rv))) {
1798 return rv;
1801 outputStream = do_QueryInterface(stream);
1802 if (NS_WARN_IF(!outputStream)) {
1803 return NS_ERROR_FAILURE;
1806 break;
1809 case kAppendFileFlag: {
1810 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), aFile,
1811 PR_WRONLY | PR_CREATE_FILE | PR_APPEND);
1812 if (NS_WARN_IF(NS_FAILED(rv))) {
1813 return rv;
1816 break;
1819 default:
1820 MOZ_CRASH("Should never get here!");
1823 outputStream.forget(aStream);
1824 return NS_OK;
1827 nsresult GetBinaryOutputStream(nsIFile* aFile, FileFlag aFileFlag,
1828 nsIBinaryOutputStream** aStream) {
1829 nsCOMPtr<nsIOutputStream> outputStream;
1830 nsresult rv = GetOutputStream(aFile, aFileFlag, getter_AddRefs(outputStream));
1831 if (NS_WARN_IF(NS_FAILED(rv))) {
1832 return rv;
1835 if (NS_WARN_IF(!outputStream)) {
1836 return NS_ERROR_UNEXPECTED;
1839 nsCOMPtr<nsIObjectOutputStream> objectOutputStream =
1840 NS_NewObjectOutputStream(outputStream);
1842 objectOutputStream.forget(aStream);
1843 return NS_OK;
1846 void GetJarPrefix(uint32_t aAppId, bool aInIsolatedMozBrowser,
1847 nsACString& aJarPrefix) {
1848 MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
1850 if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
1851 aAppId = nsIScriptSecurityManager::NO_APP_ID;
1854 aJarPrefix.Truncate();
1856 // Fallback.
1857 if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInIsolatedMozBrowser) {
1858 return;
1861 // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
1862 aJarPrefix.AppendInt(aAppId);
1863 aJarPrefix.Append('+');
1864 aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
1865 aJarPrefix.Append('+');
1868 nsresult CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
1869 const nsACString& aSuffix,
1870 const nsACString& aGroup,
1871 const nsACString& aOrigin) {
1872 AssertIsOnIOThread();
1874 OriginAttributes groupAttributes;
1876 nsCString groupNoSuffix;
1877 bool ok = groupAttributes.PopulateFromOrigin(aGroup, groupNoSuffix);
1878 if (!ok) {
1879 return NS_ERROR_FAILURE;
1882 nsCString groupPrefix;
1883 GetJarPrefix(groupAttributes.mAppId, groupAttributes.mInIsolatedMozBrowser,
1884 groupPrefix);
1886 nsCString group = groupPrefix + groupNoSuffix;
1888 OriginAttributes originAttributes;
1890 nsCString originNoSuffix;
1891 ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
1892 if (!ok) {
1893 return NS_ERROR_FAILURE;
1896 nsCString originPrefix;
1897 GetJarPrefix(originAttributes.mAppId, originAttributes.mInIsolatedMozBrowser,
1898 originPrefix);
1900 nsCString origin = originPrefix + originNoSuffix;
1902 MOZ_ASSERT(groupPrefix == originPrefix);
1904 nsCOMPtr<nsIFile> file;
1905 nsresult rv = aDirectory->Clone(getter_AddRefs(file));
1906 if (NS_WARN_IF(NS_FAILED(rv))) {
1907 return rv;
1910 rv = file->Append(NS_LITERAL_STRING(METADATA_TMP_FILE_NAME));
1911 if (NS_WARN_IF(NS_FAILED(rv))) {
1912 return rv;
1915 nsCOMPtr<nsIBinaryOutputStream> stream;
1916 rv = GetBinaryOutputStream(file, kTruncateFileFlag, getter_AddRefs(stream));
1917 if (NS_WARN_IF(NS_FAILED(rv))) {
1918 return rv;
1921 MOZ_ASSERT(stream);
1923 rv = stream->Write64(aTimestamp);
1924 if (NS_WARN_IF(NS_FAILED(rv))) {
1925 return rv;
1928 rv = stream->WriteStringZ(group.get());
1929 if (NS_WARN_IF(NS_FAILED(rv))) {
1930 return rv;
1933 rv = stream->WriteStringZ(origin.get());
1934 if (NS_WARN_IF(NS_FAILED(rv))) {
1935 return rv;
1938 // Currently unused (used to be isApp).
1939 rv = stream->WriteBoolean(false);
1940 if (NS_WARN_IF(NS_FAILED(rv))) {
1941 return rv;
1944 rv = stream->Flush();
1945 if (NS_WARN_IF(NS_FAILED(rv))) {
1946 return rv;
1949 rv = stream->Close();
1950 if (NS_WARN_IF(NS_FAILED(rv))) {
1951 return rv;
1954 rv = file->RenameTo(nullptr, NS_LITERAL_STRING(METADATA_FILE_NAME));
1955 if (NS_WARN_IF(NS_FAILED(rv))) {
1956 return rv;
1959 return NS_OK;
1962 nsresult CreateDirectoryMetadata2(nsIFile* aDirectory, int64_t aTimestamp,
1963 bool aPersisted, const nsACString& aSuffix,
1964 const nsACString& aGroup,
1965 const nsACString& aOrigin) {
1966 AssertIsOnIOThread();
1967 MOZ_ASSERT(aDirectory);
1969 nsCOMPtr<nsIFile> file;
1970 nsresult rv = aDirectory->Clone(getter_AddRefs(file));
1971 if (NS_WARN_IF(NS_FAILED(rv))) {
1972 return rv;
1975 rv = file->Append(NS_LITERAL_STRING(METADATA_V2_TMP_FILE_NAME));
1976 if (NS_WARN_IF(NS_FAILED(rv))) {
1977 return rv;
1980 nsCOMPtr<nsIBinaryOutputStream> stream;
1981 rv = GetBinaryOutputStream(file, kTruncateFileFlag, getter_AddRefs(stream));
1982 if (NS_WARN_IF(NS_FAILED(rv))) {
1983 return rv;
1986 MOZ_ASSERT(stream);
1988 rv = stream->Write64(aTimestamp);
1989 if (NS_WARN_IF(NS_FAILED(rv))) {
1990 return rv;
1993 rv = stream->WriteBoolean(aPersisted);
1994 if (NS_WARN_IF(NS_FAILED(rv))) {
1995 return rv;
1998 // Reserved data 1
1999 rv = stream->Write32(0);
2000 if (NS_WARN_IF(NS_FAILED(rv))) {
2001 return rv;
2004 // Reserved data 2
2005 rv = stream->Write32(0);
2006 if (NS_WARN_IF(NS_FAILED(rv))) {
2007 return rv;
2010 // The suffix isn't used right now, but we might need it in future. It's
2011 // a bit of redundancy we can live with given how painful is to upgrade
2012 // metadata files.
2013 rv = stream->WriteStringZ(PromiseFlatCString(aSuffix).get());
2014 if (NS_WARN_IF(NS_FAILED(rv))) {
2015 return rv;
2018 rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get());
2019 if (NS_WARN_IF(NS_FAILED(rv))) {
2020 return rv;
2023 rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get());
2024 if (NS_WARN_IF(NS_FAILED(rv))) {
2025 return rv;
2028 // Currently unused (used to be isApp).
2029 rv = stream->WriteBoolean(false);
2030 if (NS_WARN_IF(NS_FAILED(rv))) {
2031 return rv;
2034 rv = stream->Flush();
2035 if (NS_WARN_IF(NS_FAILED(rv))) {
2036 return rv;
2039 rv = stream->Close();
2040 if (NS_WARN_IF(NS_FAILED(rv))) {
2041 return rv;
2044 rv = file->RenameTo(nullptr, NS_LITERAL_STRING(METADATA_V2_FILE_NAME));
2045 if (NS_WARN_IF(NS_FAILED(rv))) {
2046 return rv;
2049 return NS_OK;
2052 nsresult CreateDirectoryMetadataFiles(nsIFile* aDirectory, bool aPersisted,
2053 const nsACString& aSuffix,
2054 const nsACString& aGroup,
2055 const nsACString& aOrigin,
2056 int64_t* aTimestamp) {
2057 AssertIsOnIOThread();
2059 int64_t timestamp = PR_Now();
2061 nsresult rv =
2062 CreateDirectoryMetadata(aDirectory, timestamp, aSuffix, aGroup, aOrigin);
2063 if (NS_WARN_IF(NS_FAILED(rv))) {
2064 return rv;
2067 rv = CreateDirectoryMetadata2(aDirectory, timestamp, aPersisted, aSuffix,
2068 aGroup, aOrigin);
2069 if (NS_WARN_IF(NS_FAILED(rv))) {
2070 return rv;
2073 if (aTimestamp) {
2074 *aTimestamp = timestamp;
2076 return NS_OK;
2079 nsresult GetBinaryInputStream(nsIFile* aDirectory, const nsAString& aFilename,
2080 nsIBinaryInputStream** aStream) {
2081 MOZ_ASSERT(!NS_IsMainThread());
2082 MOZ_ASSERT(aDirectory);
2083 MOZ_ASSERT(aStream);
2085 nsCOMPtr<nsIFile> file;
2086 nsresult rv = aDirectory->Clone(getter_AddRefs(file));
2087 if (NS_WARN_IF(NS_FAILED(rv))) {
2088 return rv;
2091 rv = file->Append(aFilename);
2092 if (NS_WARN_IF(NS_FAILED(rv))) {
2093 return rv;
2096 nsCOMPtr<nsIInputStream> stream;
2097 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
2098 if (NS_WARN_IF(NS_FAILED(rv))) {
2099 return rv;
2102 nsCOMPtr<nsIInputStream> bufferedStream;
2103 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2104 stream.forget(), 512);
2105 if (NS_WARN_IF(NS_FAILED(rv))) {
2106 return rv;
2109 nsCOMPtr<nsIBinaryInputStream> binaryStream =
2110 do_CreateInstance("@mozilla.org/binaryinputstream;1");
2111 if (NS_WARN_IF(!binaryStream)) {
2112 return NS_ERROR_FAILURE;
2115 rv = binaryStream->SetInputStream(bufferedStream);
2116 if (NS_WARN_IF(NS_FAILED(rv))) {
2117 return rv;
2120 binaryStream.forget(aStream);
2121 return NS_OK;
2124 // This method computes and returns our best guess for the temporary storage
2125 // limit (in bytes), based on the amount of space users have free on their hard
2126 // drive and on given temporary storage usage (also in bytes).
2127 nsresult GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage,
2128 uint64_t* aLimit) {
2129 // Check for free space on device where temporary storage directory lives.
2130 int64_t bytesAvailable;
2131 nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable);
2132 NS_ENSURE_SUCCESS(rv, rv);
2134 NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!");
2136 uint64_t availableKB =
2137 static_cast<uint64_t>((bytesAvailable + aCurrentUsage) / 1024);
2139 // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case
2140 // we don't shrink temporary storage and evict origin data every time we
2141 // initialize.
2142 availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB;
2144 // Allow temporary storage to consume up to half the available space.
2145 uint64_t resultKB = availableKB * .50;
2147 *aLimit = resultKB * 1024;
2148 return NS_OK;
2151 } // namespace
2153 /*******************************************************************************
2154 * Exported functions
2155 ******************************************************************************/
2157 PQuotaParent* AllocPQuotaParent() {
2158 AssertIsOnBackgroundThread();
2160 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
2161 return nullptr;
2164 RefPtr<Quota> actor = new Quota();
2166 return actor.forget().take();
2169 bool DeallocPQuotaParent(PQuotaParent* aActor) {
2170 AssertIsOnBackgroundThread();
2171 MOZ_ASSERT(aActor);
2173 RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
2174 return true;
2177 /*******************************************************************************
2178 * Directory lock
2179 ******************************************************************************/
2181 DirectoryLockImpl::DirectoryLockImpl(
2182 QuotaManager* aQuotaManager,
2183 const Nullable<PersistenceType>& aPersistenceType, const nsACString& aGroup,
2184 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
2185 bool aExclusive, bool aInternal, OpenDirectoryListener* aOpenListener)
2186 : mQuotaManager(aQuotaManager),
2187 mPersistenceType(aPersistenceType),
2188 mGroup(aGroup),
2189 mOriginScope(aOriginScope),
2190 mClientType(aClientType),
2191 mOpenListener(aOpenListener),
2192 mExclusive(aExclusive),
2193 mInternal(aInternal),
2194 mInvalidated(false) {
2195 AssertIsOnOwningThread();
2196 MOZ_ASSERT(aQuotaManager);
2197 MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
2198 MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
2199 MOZ_ASSERT_IF(!aInternal,
2200 aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
2201 MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
2202 MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
2203 MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
2204 MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
2205 MOZ_ASSERT_IF(!aInternal, aOpenListener);
2208 DirectoryLockImpl::~DirectoryLockImpl() {
2209 AssertIsOnOwningThread();
2210 MOZ_ASSERT(mQuotaManager);
2212 for (DirectoryLockImpl* blockingLock : mBlocking) {
2213 blockingLock->MaybeUnblock(this);
2216 mBlocking.Clear();
2218 mQuotaManager->UnregisterDirectoryLock(this);
2221 #ifdef DEBUG
2223 void DirectoryLockImpl::AssertIsOnOwningThread() const {
2224 MOZ_ASSERT(mQuotaManager);
2225 mQuotaManager->AssertIsOnOwningThread();
2228 #endif // DEBUG
2230 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aExistingLock) {
2231 AssertIsOnOwningThread();
2233 // Waiting is never required if the ops in comparison represent shared locks.
2234 if (!aExistingLock.mExclusive && !mExclusive) {
2235 return false;
2238 // If the persistence types don't overlap, the op can proceed.
2239 if (!aExistingLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
2240 aExistingLock.mPersistenceType.Value() != mPersistenceType.Value()) {
2241 return false;
2244 // If the origin scopes don't overlap, the op can proceed.
2245 bool match = aExistingLock.mOriginScope.Matches(mOriginScope);
2246 if (!match) {
2247 return false;
2250 // If the client types don't overlap, the op can proceed.
2251 if (!aExistingLock.mClientType.IsNull() && !mClientType.IsNull() &&
2252 aExistingLock.mClientType.Value() != mClientType.Value()) {
2253 return false;
2256 // Otherwise, when all attributes overlap (persistence type, origin scope and
2257 // client type) the op must wait.
2258 return true;
2261 void DirectoryLockImpl::NotifyOpenListener() {
2262 AssertIsOnOwningThread();
2263 MOZ_ASSERT(mQuotaManager);
2264 MOZ_ASSERT(mOpenListener);
2266 if (mInvalidated) {
2267 mOpenListener->DirectoryLockFailed();
2268 } else {
2269 mOpenListener->DirectoryLockAcquired(this);
2272 mOpenListener = nullptr;
2274 mQuotaManager->RemovePendingDirectoryLock(this);
2277 nsresult QuotaManager::CreateRunnable::Init() {
2278 MOZ_ASSERT(NS_IsMainThread());
2279 MOZ_ASSERT(mState == State::Initial);
2281 nsresult rv;
2283 nsCOMPtr<nsIFile> baseDir;
2284 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
2285 getter_AddRefs(baseDir));
2286 if (NS_FAILED(rv)) {
2287 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
2288 getter_AddRefs(baseDir));
2290 if (NS_WARN_IF(NS_FAILED(rv))) {
2291 return rv;
2294 rv = baseDir->GetPath(mBaseDirPath);
2295 if (NS_WARN_IF(NS_FAILED(rv))) {
2296 return rv;
2299 Unused << NextGenLocalStorageEnabled();
2301 return NS_OK;
2304 nsresult QuotaManager::CreateRunnable::CreateManager() {
2305 AssertIsOnOwningThread();
2306 MOZ_ASSERT(mState == State::CreatingManager);
2308 mManager = new QuotaManager();
2310 nsresult rv = mManager->Init(mBaseDirPath);
2311 if (NS_WARN_IF(NS_FAILED(rv))) {
2312 return rv;
2315 return NS_OK;
2318 nsresult QuotaManager::CreateRunnable::RegisterObserver() {
2319 MOZ_ASSERT(NS_IsMainThread());
2320 MOZ_ASSERT(mState == State::RegisteringObserver);
2322 if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT,
2323 kDefaultFixedLimitKB)) ||
2324 NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB, PREF_CHUNK_SIZE,
2325 kDefaultChunkSizeKB))) {
2326 NS_WARNING("Unable to respond to temp storage pref changes!");
2329 if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled,
2330 PREF_TESTING_FEATURES, false))) {
2331 NS_WARNING("Unable to respond to testing pref changes!");
2334 nsCOMPtr<nsIObserverService> observerService =
2335 mozilla::services::GetObserverService();
2336 if (NS_WARN_IF(!observerService)) {
2337 return NS_ERROR_FAILURE;
2340 nsCOMPtr<nsIObserver> observer = new ShutdownObserver(mOwningThread);
2342 nsresult rv = observerService->AddObserver(
2343 observer, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
2344 if (NS_WARN_IF(NS_FAILED(rv))) {
2345 return rv;
2348 // This service has to be started on the main thread currently.
2349 nsCOMPtr<mozIStorageService> ss =
2350 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
2351 if (NS_WARN_IF(NS_FAILED(rv))) {
2352 return rv;
2355 QuotaManagerService* qms = QuotaManagerService::GetOrCreate();
2356 if (NS_WARN_IF(!qms)) {
2357 return rv;
2360 qms->NoteLiveManager(mManager);
2362 for (RefPtr<Client>& client : mManager->mClients) {
2363 client->DidInitialize(mManager);
2366 return NS_OK;
2369 void QuotaManager::CreateRunnable::CallCallbacks() {
2370 AssertIsOnOwningThread();
2371 MOZ_ASSERT(mState == State::CallingCallbacks);
2373 gCreateRunnable = nullptr;
2375 if (NS_FAILED(mResultCode)) {
2376 gCreateFailed = true;
2377 } else {
2378 gInstance = mManager;
2381 mManager = nullptr;
2383 nsTArray<nsCOMPtr<nsIRunnable>> callbacks;
2384 mCallbacks.SwapElements(callbacks);
2386 for (nsCOMPtr<nsIRunnable>& callback : callbacks) {
2387 Unused << callback->Run();
2391 auto QuotaManager::CreateRunnable::GetNextState(
2392 nsCOMPtr<nsIEventTarget>& aThread) -> State {
2393 switch (mState) {
2394 case State::Initial:
2395 aThread = mOwningThread;
2396 return State::CreatingManager;
2397 case State::CreatingManager:
2398 if (mMainEventTarget) {
2399 aThread = mMainEventTarget;
2400 } else {
2401 aThread = GetMainThreadEventTarget();
2403 return State::RegisteringObserver;
2404 case State::RegisteringObserver:
2405 aThread = mOwningThread;
2406 return State::CallingCallbacks;
2407 case State::CallingCallbacks:
2408 aThread = nullptr;
2409 return State::Completed;
2410 default:
2411 MOZ_CRASH("Bad state!");
2415 NS_IMETHODIMP
2416 QuotaManager::CreateRunnable::Run() {
2417 nsresult rv;
2419 switch (mState) {
2420 case State::Initial:
2421 rv = Init();
2422 break;
2424 case State::CreatingManager:
2425 rv = CreateManager();
2426 break;
2428 case State::RegisteringObserver:
2429 rv = RegisterObserver();
2430 break;
2432 case State::CallingCallbacks:
2433 CallCallbacks();
2434 rv = NS_OK;
2435 break;
2437 case State::Completed:
2438 default:
2439 MOZ_CRASH("Bad state!");
2442 nsCOMPtr<nsIEventTarget> thread;
2443 if (NS_WARN_IF(NS_FAILED(rv))) {
2444 if (NS_SUCCEEDED(mResultCode)) {
2445 mResultCode = rv;
2448 mState = State::CallingCallbacks;
2449 thread = mOwningThread;
2450 } else {
2451 mState = GetNextState(thread);
2454 if (thread) {
2455 MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(this, NS_DISPATCH_NORMAL));
2458 return NS_OK;
2461 NS_IMETHODIMP
2462 QuotaManager::ShutdownRunnable::Run() {
2463 if (NS_IsMainThread()) {
2464 mDone = true;
2466 return NS_OK;
2469 AssertIsOnBackgroundThread();
2471 RefPtr<QuotaManager> quotaManager = gInstance.get();
2472 if (quotaManager) {
2473 quotaManager->Shutdown();
2475 gInstance = nullptr;
2478 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
2480 return NS_OK;
2483 NS_IMPL_ISUPPORTS(QuotaManager::ShutdownObserver, nsIObserver)
2485 NS_IMETHODIMP
2486 QuotaManager::ShutdownObserver::Observe(nsISupports* aSubject,
2487 const char* aTopic,
2488 const char16_t* aData) {
2489 MOZ_ASSERT(NS_IsMainThread());
2490 MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
2491 MOZ_ASSERT(gInstance);
2493 nsCOMPtr<nsIObserverService> observerService =
2494 mozilla::services::GetObserverService();
2495 if (NS_WARN_IF(!observerService)) {
2496 return NS_ERROR_FAILURE;
2499 // Unregister ourselves from the observer service first to make sure the
2500 // nested event loop below will not cause re-entrancy issues.
2501 Unused << observerService->RemoveObserver(
2502 this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
2504 QuotaManagerService* qms = QuotaManagerService::Get();
2505 MOZ_ASSERT(qms);
2507 qms->NoteShuttingDownManager();
2509 for (RefPtr<Client>& client : gInstance->mClients) {
2510 client->WillShutdown();
2513 bool done = false;
2515 RefPtr<ShutdownRunnable> shutdownRunnable = new ShutdownRunnable(done);
2516 MOZ_ALWAYS_SUCCEEDS(
2517 mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
2519 MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
2521 return NS_OK;
2524 /*******************************************************************************
2525 * Quota object
2526 ******************************************************************************/
2528 void QuotaObject::AddRef() {
2529 QuotaManager* quotaManager = QuotaManager::Get();
2530 if (!quotaManager) {
2531 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2533 ++mRefCnt;
2535 return;
2538 MutexAutoLock lock(quotaManager->mQuotaMutex);
2540 ++mRefCnt;
2543 void QuotaObject::Release() {
2544 QuotaManager* quotaManager = QuotaManager::Get();
2545 if (!quotaManager) {
2546 NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2548 nsrefcnt count = --mRefCnt;
2549 if (count == 0) {
2550 mRefCnt = 1;
2551 delete this;
2554 return;
2558 MutexAutoLock lock(quotaManager->mQuotaMutex);
2560 --mRefCnt;
2562 if (mRefCnt > 0) {
2563 return;
2566 if (mOriginInfo) {
2567 mOriginInfo->mQuotaObjects.Remove(mPath);
2571 delete this;
2574 bool QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) {
2575 QuotaManager* quotaManager = QuotaManager::Get();
2576 MOZ_ASSERT(quotaManager);
2578 MutexAutoLock lock(quotaManager->mQuotaMutex);
2580 return LockedMaybeUpdateSize(aSize, aTruncate);
2583 bool QuotaObject::IncreaseSize(int64_t aDelta) {
2584 MOZ_ASSERT(aDelta >= 0);
2586 QuotaManager* quotaManager = QuotaManager::Get();
2587 MOZ_ASSERT(quotaManager);
2589 MutexAutoLock lock(quotaManager->mQuotaMutex);
2591 AssertNoOverflow(mSize, aDelta);
2592 int64_t size = mSize + aDelta;
2594 return LockedMaybeUpdateSize(size, /* aTruncate */ false);
2597 void QuotaObject::DisableQuotaCheck() {
2598 QuotaManager* quotaManager = QuotaManager::Get();
2599 MOZ_ASSERT(quotaManager);
2601 MutexAutoLock lock(quotaManager->mQuotaMutex);
2603 mQuotaCheckDisabled = true;
2606 void QuotaObject::EnableQuotaCheck() {
2607 QuotaManager* quotaManager = QuotaManager::Get();
2608 MOZ_ASSERT(quotaManager);
2610 MutexAutoLock lock(quotaManager->mQuotaMutex);
2612 mQuotaCheckDisabled = false;
2615 bool QuotaObject::LockedMaybeUpdateSize(int64_t aSize, bool aTruncate) {
2616 QuotaManager* quotaManager = QuotaManager::Get();
2617 MOZ_ASSERT(quotaManager);
2619 quotaManager->mQuotaMutex.AssertCurrentThreadOwns();
2621 if (mWritingDone == false && mOriginInfo) {
2622 mWritingDone = true;
2623 StorageActivityService::SendActivity(mOriginInfo->mOrigin);
2626 if (mQuotaCheckDisabled) {
2627 return true;
2630 if (mSize == aSize) {
2631 return true;
2634 if (!mOriginInfo) {
2635 mSize = aSize;
2636 return true;
2639 GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
2640 MOZ_ASSERT(groupInfo);
2642 if (mSize > aSize) {
2643 if (aTruncate) {
2644 const int64_t delta = mSize - aSize;
2646 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
2647 quotaManager->mTemporaryStorageUsage -= delta;
2649 if (!mOriginInfo->LockedPersisted()) {
2650 AssertNoUnderflow(groupInfo->mUsage, delta);
2651 groupInfo->mUsage -= delta;
2654 AssertNoUnderflow(mOriginInfo->mUsage, delta);
2655 mOriginInfo->mUsage -= delta;
2657 mSize = aSize;
2659 return true;
2662 MOZ_ASSERT(mSize < aSize);
2664 RefPtr<GroupInfo> complementaryGroupInfo =
2665 groupInfo->mGroupInfoPair->LockedGetGroupInfo(
2666 ComplementaryPersistenceType(groupInfo->mPersistenceType));
2668 uint64_t delta = aSize - mSize;
2670 AssertNoOverflow(mOriginInfo->mUsage, delta);
2671 uint64_t newUsage = mOriginInfo->mUsage + delta;
2673 // Temporary storage has no limit for origin usage (there's a group and the
2674 // global limit though).
2676 uint64_t newGroupUsage = groupInfo->mUsage;
2677 if (!mOriginInfo->LockedPersisted()) {
2678 AssertNoOverflow(groupInfo->mUsage, delta);
2679 newGroupUsage += delta;
2681 uint64_t groupUsage = groupInfo->mUsage;
2682 if (complementaryGroupInfo) {
2683 AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
2684 groupUsage += complementaryGroupInfo->mUsage;
2687 // Temporary storage has a hard limit for group usage (20 % of the global
2688 // limit).
2689 AssertNoOverflow(groupUsage, delta);
2690 if (groupUsage + delta > quotaManager->GetGroupLimit()) {
2691 return false;
2695 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
2696 uint64_t newTemporaryStorageUsage =
2697 quotaManager->mTemporaryStorageUsage + delta;
2699 if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
2700 // This will block the thread without holding the lock while waitting.
2702 AutoTArray<RefPtr<DirectoryLockImpl>, 10> locks;
2703 uint64_t sizeToBeFreed;
2705 if (IsOnBackgroundThread()) {
2706 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
2708 sizeToBeFreed = quotaManager->CollectOriginsForEviction(delta, locks);
2709 } else {
2710 sizeToBeFreed =
2711 quotaManager->LockedCollectOriginsForEviction(delta, locks);
2714 if (!sizeToBeFreed) {
2715 uint64_t usage = quotaManager->mTemporaryStorageUsage;
2717 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
2719 quotaManager->NotifyStoragePressure(usage);
2721 return false;
2724 NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
2727 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
2729 for (RefPtr<DirectoryLockImpl>& lock : locks) {
2730 MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
2731 MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
2732 MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty());
2734 quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType().Value(),
2735 lock->GetOriginScope().GetOrigin());
2739 // Relocked.
2741 NS_ASSERTION(mOriginInfo, "How come?!");
2743 for (DirectoryLockImpl* lock : locks) {
2744 MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
2745 MOZ_ASSERT(!lock->GetGroup().IsEmpty());
2746 MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
2747 MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty());
2748 MOZ_ASSERT(
2749 !(lock->GetOriginScope().GetOrigin() == mOriginInfo->mOrigin &&
2750 lock->GetPersistenceType().Value() == groupInfo->mPersistenceType),
2751 "Deleted itself!");
2753 quotaManager->LockedRemoveQuotaForOrigin(
2754 lock->GetPersistenceType().Value(), lock->GetGroup(),
2755 lock->GetOriginScope().GetOrigin());
2758 // We unlocked and relocked several times so we need to recompute all the
2759 // essential variables and recheck the group limit.
2761 AssertNoUnderflow(aSize, mSize);
2762 delta = aSize - mSize;
2764 AssertNoOverflow(mOriginInfo->mUsage, delta);
2765 newUsage = mOriginInfo->mUsage + delta;
2767 newGroupUsage = groupInfo->mUsage;
2768 if (!mOriginInfo->LockedPersisted()) {
2769 AssertNoOverflow(groupInfo->mUsage, delta);
2770 newGroupUsage += delta;
2772 uint64_t groupUsage = groupInfo->mUsage;
2773 if (complementaryGroupInfo) {
2774 AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
2775 groupUsage += complementaryGroupInfo->mUsage;
2778 AssertNoOverflow(groupUsage, delta);
2779 if (groupUsage + delta > quotaManager->GetGroupLimit()) {
2780 // Unfortunately some other thread increased the group usage in the
2781 // meantime and we are not below the group limit anymore.
2783 // However, the origin eviction must be finalized in this case too.
2784 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
2786 quotaManager->FinalizeOriginEviction(locks);
2788 return false;
2792 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
2793 newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
2795 NS_ASSERTION(
2796 newTemporaryStorageUsage <= quotaManager->mTemporaryStorageLimit,
2797 "How come?!");
2799 // Ok, we successfully freed enough space and the operation can continue
2800 // without throwing the quota error.
2801 mOriginInfo->mUsage = newUsage;
2802 if (!mOriginInfo->LockedPersisted()) {
2803 groupInfo->mUsage = newGroupUsage;
2805 quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
2808 // Some other thread could increase the size in the meantime, but no more
2809 // than this one.
2810 MOZ_ASSERT(mSize < aSize);
2811 mSize = aSize;
2813 // Finally, release IO thread only objects and allow next synchronized
2814 // ops for the evicted origins.
2815 MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
2817 quotaManager->FinalizeOriginEviction(locks);
2819 return true;
2822 mOriginInfo->mUsage = newUsage;
2823 if (!mOriginInfo->LockedPersisted()) {
2824 groupInfo->mUsage = newGroupUsage;
2826 quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
2828 mSize = aSize;
2830 return true;
2833 /*******************************************************************************
2834 * Quota manager
2835 ******************************************************************************/
2837 QuotaManager::QuotaManager()
2838 : mQuotaMutex("QuotaManager.mQuotaMutex"),
2839 mTemporaryStorageLimit(0),
2840 mTemporaryStorageUsage(0),
2841 mTemporaryStorageInitialized(false),
2842 mStorageInitialized(false) {
2843 AssertIsOnOwningThread();
2844 MOZ_ASSERT(!gInstance);
2847 QuotaManager::~QuotaManager() {
2848 AssertIsOnOwningThread();
2849 MOZ_ASSERT(!gInstance || gInstance == this);
2852 void QuotaManager::GetOrCreate(nsIRunnable* aCallback,
2853 nsIEventTarget* aMainEventTarget) {
2854 AssertIsOnBackgroundThread();
2856 if (IsShuttingDown()) {
2857 MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
2858 return;
2861 if (gInstance || gCreateFailed) {
2862 MOZ_ASSERT(!gCreateRunnable);
2863 MOZ_ASSERT_IF(gCreateFailed, !gInstance);
2865 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
2866 } else {
2867 if (!gCreateRunnable) {
2868 gCreateRunnable = new CreateRunnable(aMainEventTarget);
2869 if (aMainEventTarget) {
2870 MOZ_ALWAYS_SUCCEEDS(
2871 aMainEventTarget->Dispatch(gCreateRunnable, NS_DISPATCH_NORMAL));
2872 } else {
2873 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable));
2877 gCreateRunnable->AddCallback(aCallback);
2881 // static
2882 QuotaManager* QuotaManager::Get() {
2883 // Does not return an owning reference.
2884 return gInstance;
2887 // static
2888 bool QuotaManager::IsShuttingDown() { return gShutdown; }
2890 // static
2891 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
2892 return aFileName.EqualsLiteral(DSSTORE_FILE_NAME) ||
2893 aFileName.EqualsLiteral(DESKTOP_FILE_NAME) ||
2894 aFileName.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME) ||
2895 aFileName.EqualsLiteral(THUMBS_DB_FILE_NAME);
2898 // static
2899 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
2900 return aFileName.First() == char16_t('.');
2903 auto QuotaManager::CreateDirectoryLock(
2904 const Nullable<PersistenceType>& aPersistenceType, const nsACString& aGroup,
2905 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
2906 bool aExclusive, bool aInternal, OpenDirectoryListener* aOpenListener)
2907 -> already_AddRefed<DirectoryLockImpl> {
2908 AssertIsOnOwningThread();
2909 MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
2910 MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
2911 MOZ_ASSERT_IF(!aInternal,
2912 aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
2913 MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
2914 MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
2915 MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
2916 MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
2917 MOZ_ASSERT_IF(!aInternal, aOpenListener);
2919 RefPtr<DirectoryLockImpl> lock =
2920 new DirectoryLockImpl(this, aPersistenceType, aGroup, aOriginScope,
2921 aClientType, aExclusive, aInternal, aOpenListener);
2923 mPendingDirectoryLocks.AppendElement(lock);
2925 // See if this lock needs to wait.
2926 bool blocked = false;
2927 for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
2928 DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
2929 if (lock->MustWaitFor(*existingLock)) {
2930 existingLock->AddBlockingLock(lock);
2931 lock->AddBlockedOnLock(existingLock);
2932 blocked = true;
2936 RegisterDirectoryLock(lock);
2938 // Otherwise, notify the open listener immediately.
2939 if (!blocked) {
2940 lock->NotifyOpenListener();
2943 return lock.forget();
2946 auto QuotaManager::CreateDirectoryLockForEviction(
2947 PersistenceType aPersistenceType, const nsACString& aGroup,
2948 const nsACString& aOrigin) -> already_AddRefed<DirectoryLockImpl> {
2949 AssertIsOnOwningThread();
2950 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
2951 MOZ_ASSERT(!aOrigin.IsEmpty());
2953 RefPtr<DirectoryLockImpl> lock = new DirectoryLockImpl(
2954 this, Nullable<PersistenceType>(aPersistenceType), aGroup,
2955 OriginScope::FromOrigin(aOrigin), Nullable<Client::Type>(),
2956 /* aExclusive */ true,
2957 /* aInternal */ true, nullptr);
2959 #ifdef DEBUG
2960 for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
2961 DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
2962 MOZ_ASSERT(!lock->MustWaitFor(*existingLock));
2964 #endif
2966 RegisterDirectoryLock(lock);
2968 return lock.forget();
2971 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl* aLock) {
2972 AssertIsOnOwningThread();
2973 MOZ_ASSERT(aLock);
2975 mDirectoryLocks.AppendElement(aLock);
2977 if (aLock->ShouldUpdateLockTable()) {
2978 const Nullable<PersistenceType>& persistenceType =
2979 aLock->GetPersistenceType();
2980 const OriginScope& originScope = aLock->GetOriginScope();
2982 MOZ_ASSERT(!persistenceType.IsNull());
2983 MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
2984 MOZ_ASSERT(originScope.IsOrigin());
2985 MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
2987 DirectoryLockTable& directoryLockTable =
2988 GetDirectoryLockTable(persistenceType.Value());
2990 nsTArray<DirectoryLockImpl*>* array;
2991 if (!directoryLockTable.Get(originScope.GetOrigin(), &array)) {
2992 array = new nsTArray<DirectoryLockImpl*>();
2993 directoryLockTable.Put(originScope.GetOrigin(), array);
2995 if (!IsShuttingDown()) {
2996 UpdateOriginAccessTime(persistenceType.Value(), aLock->GetGroup(),
2997 originScope.GetOrigin());
3000 array->AppendElement(aLock);
3004 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl* aLock) {
3005 AssertIsOnOwningThread();
3007 MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(aLock));
3009 if (aLock->ShouldUpdateLockTable()) {
3010 const Nullable<PersistenceType>& persistenceType =
3011 aLock->GetPersistenceType();
3012 const OriginScope& originScope = aLock->GetOriginScope();
3014 MOZ_ASSERT(!persistenceType.IsNull());
3015 MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
3016 MOZ_ASSERT(originScope.IsOrigin());
3017 MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
3019 DirectoryLockTable& directoryLockTable =
3020 GetDirectoryLockTable(persistenceType.Value());
3022 nsTArray<DirectoryLockImpl*>* array;
3023 MOZ_ALWAYS_TRUE(directoryLockTable.Get(originScope.GetOrigin(), &array));
3025 MOZ_ALWAYS_TRUE(array->RemoveElement(aLock));
3026 if (array->IsEmpty()) {
3027 directoryLockTable.Remove(originScope.GetOrigin());
3029 if (!IsShuttingDown()) {
3030 UpdateOriginAccessTime(persistenceType.Value(), aLock->GetGroup(),
3031 originScope.GetOrigin());
3037 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl* aLock) {
3038 AssertIsOnOwningThread();
3039 MOZ_ASSERT(aLock);
3041 MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(aLock));
3044 uint64_t QuotaManager::CollectOriginsForEviction(
3045 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) {
3046 AssertIsOnOwningThread();
3047 MOZ_ASSERT(aLocks.IsEmpty());
3049 struct MOZ_STACK_CLASS Helper final {
3050 static void GetInactiveOriginInfos(
3051 nsTArray<RefPtr<OriginInfo>>& aOriginInfos,
3052 nsTArray<DirectoryLockImpl*>& aLocks,
3053 nsTArray<OriginInfo*>& aInactiveOriginInfos) {
3054 for (OriginInfo* originInfo : aOriginInfos) {
3055 MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
3056 PERSISTENCE_TYPE_PERSISTENT);
3058 if (originInfo->LockedPersisted()) {
3059 continue;
3062 OriginScope originScope = OriginScope::FromOrigin(originInfo->mOrigin);
3064 bool match = false;
3065 for (uint32_t j = aLocks.Length(); j > 0; j--) {
3066 DirectoryLockImpl* lock = aLocks[j - 1];
3067 if (originScope.Matches(lock->GetOriginScope())) {
3068 match = true;
3069 break;
3073 if (!match) {
3074 MOZ_ASSERT(!originInfo->mQuotaObjects.Count(),
3075 "Inactive origin shouldn't have open files!");
3076 aInactiveOriginInfos.InsertElementSorted(originInfo,
3077 OriginInfoLRUComparator());
3083 // Split locks into separate arrays and filter out locks for persistent
3084 // storage, they can't block us.
3085 nsTArray<DirectoryLockImpl*> temporaryStorageLocks;
3086 nsTArray<DirectoryLockImpl*> defaultStorageLocks;
3087 for (DirectoryLockImpl* lock : mDirectoryLocks) {
3088 const Nullable<PersistenceType>& persistenceType =
3089 lock->GetPersistenceType();
3091 if (persistenceType.IsNull()) {
3092 temporaryStorageLocks.AppendElement(lock);
3093 defaultStorageLocks.AppendElement(lock);
3094 } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3095 temporaryStorageLocks.AppendElement(lock);
3096 } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
3097 defaultStorageLocks.AppendElement(lock);
3098 } else {
3099 MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
3101 // Do nothing here, persistent origins don't need to be collected ever.
3105 nsTArray<OriginInfo*> inactiveOrigins;
3107 // Enumerate and process inactive origins. This must be protected by the
3108 // mutex.
3109 MutexAutoLock lock(mQuotaMutex);
3111 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
3112 GroupInfoPair* pair = iter.UserData();
3114 MOZ_ASSERT(!iter.Key().IsEmpty());
3115 MOZ_ASSERT(pair);
3117 RefPtr<GroupInfo> groupInfo =
3118 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3119 if (groupInfo) {
3120 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
3121 temporaryStorageLocks, inactiveOrigins);
3124 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3125 if (groupInfo) {
3126 Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
3127 defaultStorageLocks, inactiveOrigins);
3131 #ifdef DEBUG
3132 // Make sure the array is sorted correctly.
3133 for (uint32_t index = inactiveOrigins.Length(); index > 1; index--) {
3134 MOZ_ASSERT(inactiveOrigins[index - 1]->mAccessTime >=
3135 inactiveOrigins[index - 2]->mAccessTime);
3137 #endif
3139 // Create a list of inactive and the least recently used origins
3140 // whose aggregate size is greater or equals the minimal size to be freed.
3141 uint64_t sizeToBeFreed = 0;
3142 for (uint32_t count = inactiveOrigins.Length(), index = 0; index < count;
3143 index++) {
3144 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3145 inactiveOrigins.TruncateLength(index);
3146 break;
3149 sizeToBeFreed += inactiveOrigins[index]->mUsage;
3152 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3153 // Success, add directory locks for these origins, so any other
3154 // operations for them will be delayed (until origin eviction is finalized).
3156 for (OriginInfo* originInfo : inactiveOrigins) {
3157 RefPtr<DirectoryLockImpl> lock = CreateDirectoryLockForEviction(
3158 originInfo->mGroupInfo->mPersistenceType,
3159 originInfo->mGroupInfo->mGroup, originInfo->mOrigin);
3160 aLocks.AppendElement(lock.forget());
3163 return sizeToBeFreed;
3166 return 0;
3169 nsresult QuotaManager::Init(const nsAString& aBasePath) {
3170 mBasePath = aBasePath;
3172 nsCOMPtr<nsIFile> baseDir;
3173 nsresult rv = NS_NewLocalFile(aBasePath, false, getter_AddRefs(baseDir));
3174 if (NS_WARN_IF(NS_FAILED(rv))) {
3175 return rv;
3178 rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(INDEXEDDB_DIRECTORY_NAME),
3179 mIndexedDBPath);
3180 if (NS_WARN_IF(NS_FAILED(rv))) {
3181 return rv;
3184 rv = baseDir->Append(NS_LITERAL_STRING(STORAGE_DIRECTORY_NAME));
3185 if (NS_WARN_IF(NS_FAILED(rv))) {
3186 return rv;
3189 rv = baseDir->GetPath(mStoragePath);
3190 if (NS_WARN_IF(NS_FAILED(rv))) {
3191 return rv;
3194 rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(PERMANENT_DIRECTORY_NAME),
3195 mPermanentStoragePath);
3196 if (NS_WARN_IF(NS_FAILED(rv))) {
3197 return rv;
3200 rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(TEMPORARY_DIRECTORY_NAME),
3201 mTemporaryStoragePath);
3202 if (NS_WARN_IF(NS_FAILED(rv))) {
3203 return rv;
3206 rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME),
3207 mDefaultStoragePath);
3208 if (NS_WARN_IF(NS_FAILED(rv))) {
3209 return rv;
3212 rv = NS_NewNamedThread("QuotaManager IO", getter_AddRefs(mIOThread));
3213 if (NS_WARN_IF(NS_FAILED(rv))) {
3214 return rv;
3217 // Make a timer here to avoid potential failures later. We don't actually
3218 // initialize the timer until shutdown.
3219 mShutdownTimer = NS_NewTimer();
3220 if (NS_WARN_IF(!mShutdownTimer)) {
3221 return NS_ERROR_FAILURE;
3224 static_assert(Client::IDB == 0 && Client::ASMJS == 1 &&
3225 Client::DOMCACHE == 2 && Client::SDB == 3 &&
3226 Client::LS == 4 && Client::TYPE_MAX == 5,
3227 "Fix the registration!");
3229 MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
3230 "Should be using an auto array with correct capacity!");
3232 // Register clients.
3233 mClients.AppendElement(indexedDB::CreateQuotaClient());
3234 mClients.AppendElement(asmjscache::CreateClient());
3235 mClients.AppendElement(cache::CreateQuotaClient());
3236 mClients.AppendElement(simpledb::CreateQuotaClient());
3237 if (CachedNextGenLocalStorageEnabled()) {
3238 mClients.AppendElement(localstorage::CreateQuotaClient());
3239 } else {
3240 mClients.SetLength(Client::TypeMax());
3243 return NS_OK;
3246 void QuotaManager::Shutdown() {
3247 AssertIsOnOwningThread();
3249 // Setting this flag prevents the service from being recreated and prevents
3250 // further storagess from being created.
3251 if (gShutdown.exchange(true)) {
3252 NS_ERROR("Shutdown more than once?!");
3255 StopIdleMaintenance();
3257 // Kick off the shutdown timer.
3258 MOZ_ALWAYS_SUCCEEDS(mShutdownTimer->InitWithNamedFuncCallback(
3259 &ShutdownTimerCallback, this, DEFAULT_SHUTDOWN_TIMER_MS,
3260 nsITimer::TYPE_ONE_SHOT, "QuotaManager::ShutdownTimerCallback"));
3262 // Each client will spin the event loop while we wait on all the threads
3263 // to close. Our timer may fire during that loop.
3264 for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
3265 mClients[index]->ShutdownWorkThreads();
3268 // Cancel the timer regardless of whether it actually fired.
3269 if (NS_FAILED(mShutdownTimer->Cancel())) {
3270 NS_WARNING("Failed to cancel shutdown timer!");
3273 // NB: It's very important that runnable is destroyed on this thread
3274 // (i.e. after we join the IO thread) because we can't release the
3275 // QuotaManager on the IO thread. This should probably use
3276 // NewNonOwningRunnableMethod ...
3277 RefPtr<Runnable> runnable =
3278 NewRunnableMethod("dom::quota::QuotaManager::ReleaseIOThreadObjects",
3279 this, &QuotaManager::ReleaseIOThreadObjects);
3280 MOZ_ASSERT(runnable);
3282 // Give clients a chance to cleanup IO thread only objects.
3283 if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
3284 NS_WARNING("Failed to dispatch runnable!");
3287 // Make sure to join with our IO thread.
3288 if (NS_FAILED(mIOThread->Shutdown())) {
3289 NS_WARNING("Failed to shutdown IO thread!");
3292 for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
3293 lock->Invalidate();
3297 void QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType,
3298 const nsACString& aGroup,
3299 const nsACString& aOrigin,
3300 uint64_t aUsageBytes, int64_t aAccessTime,
3301 bool aPersisted) {
3302 AssertIsOnIOThread();
3303 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3305 MutexAutoLock lock(mQuotaMutex);
3307 GroupInfoPair* pair;
3308 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
3309 pair = new GroupInfoPair();
3310 mGroupInfoPairs.Put(aGroup, pair);
3311 // The hashtable is now responsible to delete the GroupInfoPair.
3314 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3315 if (!groupInfo) {
3316 groupInfo = new GroupInfo(pair, aPersistenceType, aGroup);
3317 pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
3320 RefPtr<OriginInfo> originInfo =
3321 new OriginInfo(groupInfo, aOrigin, aUsageBytes, aAccessTime, aPersisted);
3322 groupInfo->LockedAddOriginInfo(originInfo);
3325 void QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
3326 const nsACString& aGroup,
3327 const nsACString& aOrigin,
3328 int64_t aSize) {
3329 MOZ_ASSERT(!NS_IsMainThread());
3330 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3332 MutexAutoLock lock(mQuotaMutex);
3334 GroupInfoPair* pair;
3335 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
3336 return;
3339 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3340 if (!groupInfo) {
3341 return;
3344 RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
3345 if (originInfo) {
3346 originInfo->LockedDecreaseUsage(aSize);
3350 void QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType,
3351 const nsACString& aGroup,
3352 const nsACString& aOrigin) {
3353 AssertIsOnOwningThread();
3354 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
3356 MutexAutoLock lock(mQuotaMutex);
3358 GroupInfoPair* pair;
3359 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
3360 return;
3363 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3364 if (!groupInfo) {
3365 return;
3368 RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
3369 if (originInfo) {
3370 int64_t timestamp = PR_Now();
3371 originInfo->LockedUpdateAccessTime(timestamp);
3373 MutexAutoUnlock autoUnlock(mQuotaMutex);
3375 RefPtr<SaveOriginAccessTimeOp> op =
3376 new SaveOriginAccessTimeOp(aPersistenceType, aOrigin, timestamp);
3378 op->RunImmediately();
3382 void QuotaManager::RemoveQuota() {
3383 AssertIsOnIOThread();
3385 MutexAutoLock lock(mQuotaMutex);
3387 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
3388 nsAutoPtr<GroupInfoPair>& pair = iter.Data();
3390 MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
3391 MOZ_ASSERT(pair, "Null pointer!");
3393 RefPtr<GroupInfo> groupInfo =
3394 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3395 if (groupInfo) {
3396 groupInfo->LockedRemoveOriginInfos();
3399 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3400 if (groupInfo) {
3401 groupInfo->LockedRemoveOriginInfos();
3404 iter.Remove();
3407 NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
3410 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3411 PersistenceType aPersistenceType, const nsACString& aGroup,
3412 const nsACString& aOrigin, nsIFile* aFile, int64_t aFileSize,
3413 int64_t* aFileSizeOut /* = nullptr */) {
3414 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3416 if (aFileSizeOut) {
3417 *aFileSizeOut = 0;
3420 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3421 return nullptr;
3424 nsString path;
3425 nsresult rv = aFile->GetPath(path);
3426 NS_ENSURE_SUCCESS(rv, nullptr);
3428 int64_t fileSize;
3430 if (aFileSize == -1) {
3431 bool exists;
3432 rv = aFile->Exists(&exists);
3433 NS_ENSURE_SUCCESS(rv, nullptr);
3435 if (exists) {
3436 rv = aFile->GetFileSize(&fileSize);
3437 NS_ENSURE_SUCCESS(rv, nullptr);
3438 } else {
3439 fileSize = 0;
3441 } else {
3442 fileSize = aFileSize;
3445 // Re-escape our parameters above to make sure we get the right quota group.
3446 nsAutoCString group;
3447 rv = NS_EscapeURL(aGroup, esc_Query, group, fallible);
3448 NS_ENSURE_SUCCESS(rv, nullptr);
3450 nsAutoCString origin;
3451 rv = NS_EscapeURL(aOrigin, esc_Query, origin, fallible);
3452 NS_ENSURE_SUCCESS(rv, nullptr);
3454 RefPtr<QuotaObject> result;
3456 MutexAutoLock lock(mQuotaMutex);
3458 GroupInfoPair* pair;
3459 if (!mGroupInfoPairs.Get(group, &pair)) {
3460 return nullptr;
3463 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
3465 if (!groupInfo) {
3466 return nullptr;
3469 RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(origin);
3471 if (!originInfo) {
3472 return nullptr;
3475 // We need this extra raw pointer because we can't assign to the smart
3476 // pointer directly since QuotaObject::AddRef would try to acquire the same
3477 // mutex.
3478 QuotaObject* quotaObject;
3479 if (!originInfo->mQuotaObjects.Get(path, &quotaObject)) {
3480 // Create a new QuotaObject.
3481 quotaObject = new QuotaObject(originInfo, path, fileSize);
3483 // Put it to the hashtable. The hashtable is not responsible to delete
3484 // the QuotaObject.
3485 originInfo->mQuotaObjects.Put(path, quotaObject);
3488 // Addref the QuotaObject and move the ownership to the result. This must
3489 // happen before we unlock!
3490 result = quotaObject->LockedAddRef();
3493 if (aFileSizeOut) {
3494 *aFileSizeOut = fileSize;
3497 // The caller becomes the owner of the QuotaObject, that is, the caller is
3498 // is responsible to delete it when the last reference is removed.
3499 return result.forget();
3502 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
3503 PersistenceType aPersistenceType, const nsACString& aGroup,
3504 const nsACString& aOrigin, const nsAString& aPath, int64_t aFileSize,
3505 int64_t* aFileSizeOut /* = nullptr */) {
3506 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3508 if (aFileSizeOut) {
3509 *aFileSizeOut = 0;
3512 nsCOMPtr<nsIFile> file;
3513 nsresult rv = NS_NewLocalFile(aPath, false, getter_AddRefs(file));
3514 NS_ENSURE_SUCCESS(rv, nullptr);
3516 return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file, aFileSize,
3517 aFileSizeOut);
3520 Nullable<bool> QuotaManager::OriginPersisted(const nsACString& aGroup,
3521 const nsACString& aOrigin) {
3522 AssertIsOnIOThread();
3524 MutexAutoLock lock(mQuotaMutex);
3526 RefPtr<OriginInfo> originInfo =
3527 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aGroup, aOrigin);
3528 if (originInfo) {
3529 return Nullable<bool>(originInfo->LockedPersisted());
3532 return Nullable<bool>();
3535 void QuotaManager::PersistOrigin(const nsACString& aGroup,
3536 const nsACString& aOrigin) {
3537 AssertIsOnIOThread();
3539 MutexAutoLock lock(mQuotaMutex);
3541 RefPtr<OriginInfo> originInfo =
3542 LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aGroup, aOrigin);
3543 if (originInfo && !originInfo->LockedPersisted()) {
3544 originInfo->LockedPersist();
3548 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
3549 AssertIsOnOwningThread();
3551 for (RefPtr<Client>& client : mClients) {
3552 client->AbortOperationsForProcess(aContentParentId);
3556 nsresult QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType,
3557 const nsACString& aASCIIOrigin,
3558 nsIFile** aDirectory) const {
3559 nsCOMPtr<nsIFile> directory;
3560 nsresult rv = NS_NewLocalFile(GetStoragePath(aPersistenceType), false,
3561 getter_AddRefs(directory));
3562 NS_ENSURE_SUCCESS(rv, rv);
3564 nsAutoCString originSanitized(aASCIIOrigin);
3565 SanitizeOriginString(originSanitized);
3567 rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized));
3568 NS_ENSURE_SUCCESS(rv, rv);
3570 directory.forget(aDirectory);
3571 return NS_OK;
3574 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory,
3575 bool aPersistent) {
3576 AssertIsOnIOThread();
3577 MOZ_ASSERT(aDirectory);
3578 MOZ_ASSERT(mStorageInitialized);
3580 RefPtr<RestoreDirectoryMetadata2Helper> helper =
3581 new RestoreDirectoryMetadata2Helper(aDirectory, aPersistent);
3583 nsresult rv = helper->RestoreMetadata2File();
3584 if (NS_WARN_IF(NS_FAILED(rv))) {
3585 return rv;
3588 return NS_OK;
3591 nsresult QuotaManager::GetDirectoryMetadata2(
3592 nsIFile* aDirectory, int64_t* aTimestamp, bool* aPersisted,
3593 nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin) {
3594 MOZ_ASSERT(!NS_IsMainThread());
3595 MOZ_ASSERT(aDirectory);
3596 MOZ_ASSERT(aTimestamp);
3597 MOZ_ASSERT(aPersisted);
3598 MOZ_ASSERT(mStorageInitialized);
3600 nsCOMPtr<nsIBinaryInputStream> binaryStream;
3601 nsresult rv =
3602 GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
3603 getter_AddRefs(binaryStream));
3604 NS_ENSURE_SUCCESS(rv, rv);
3606 uint64_t timestamp;
3607 rv = binaryStream->Read64(&timestamp);
3608 NS_ENSURE_SUCCESS(rv, rv);
3610 bool persisted;
3611 rv = binaryStream->ReadBoolean(&persisted);
3612 if (NS_WARN_IF(NS_FAILED(rv))) {
3613 return rv;
3616 uint32_t reservedData1;
3617 rv = binaryStream->Read32(&reservedData1);
3618 if (NS_WARN_IF(NS_FAILED(rv))) {
3619 return rv;
3622 uint32_t reservedData2;
3623 rv = binaryStream->Read32(&reservedData2);
3624 if (NS_WARN_IF(NS_FAILED(rv))) {
3625 return rv;
3628 nsCString suffix;
3629 rv = binaryStream->ReadCString(suffix);
3630 if (NS_WARN_IF(NS_FAILED(rv))) {
3631 return rv;
3634 nsCString group;
3635 rv = binaryStream->ReadCString(group);
3636 NS_ENSURE_SUCCESS(rv, rv);
3638 nsCString origin;
3639 rv = binaryStream->ReadCString(origin);
3640 NS_ENSURE_SUCCESS(rv, rv);
3642 // Currently unused (used to be isApp).
3643 bool dummy;
3644 rv = binaryStream->ReadBoolean(&dummy);
3645 if (NS_WARN_IF(NS_FAILED(rv))) {
3646 return rv;
3649 *aTimestamp = timestamp;
3650 *aPersisted = persisted;
3651 aSuffix = suffix;
3652 aGroup = group;
3653 aOrigin = origin;
3654 return NS_OK;
3657 nsresult QuotaManager::GetDirectoryMetadata2WithRestore(
3658 nsIFile* aDirectory, bool aPersistent, int64_t* aTimestamp,
3659 bool* aPersisted, nsACString& aSuffix, nsACString& aGroup,
3660 nsACString& aOrigin, const bool aTelemetry) {
3661 nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted,
3662 aSuffix, aGroup, aOrigin);
3663 if (NS_WARN_IF(NS_FAILED(rv))) {
3664 rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
3665 if (NS_WARN_IF(NS_FAILED(rv))) {
3666 if (aTelemetry) {
3667 REPORT_TELEMETRY_INIT_ERR(kInternalError, Rep_RestoreDirMeta);
3669 return rv;
3672 rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted, aSuffix,
3673 aGroup, aOrigin);
3674 if (NS_WARN_IF(NS_FAILED(rv))) {
3675 if (aTelemetry) {
3676 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_GetDirMeta);
3678 return rv;
3682 return NS_OK;
3685 nsresult QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory,
3686 int64_t* aTimestamp,
3687 bool* aPersisted) {
3688 AssertIsOnIOThread();
3689 MOZ_ASSERT(aDirectory);
3690 MOZ_ASSERT(aTimestamp != nullptr || aPersisted != nullptr);
3691 MOZ_ASSERT(mStorageInitialized);
3693 nsCOMPtr<nsIBinaryInputStream> binaryStream;
3694 nsresult rv =
3695 GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
3696 getter_AddRefs(binaryStream));
3697 if (NS_WARN_IF(NS_FAILED(rv))) {
3698 return rv;
3701 uint64_t timestamp;
3702 rv = binaryStream->Read64(&timestamp);
3703 if (NS_WARN_IF(NS_FAILED(rv))) {
3704 return rv;
3707 bool persisted;
3708 if (aPersisted != nullptr) {
3709 rv = binaryStream->ReadBoolean(&persisted);
3710 if (NS_WARN_IF(NS_FAILED(rv))) {
3711 return rv;
3715 if (aTimestamp != nullptr) {
3716 *aTimestamp = timestamp;
3718 if (aPersisted != nullptr) {
3719 *aPersisted = persisted;
3721 return NS_OK;
3724 nsresult QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
3725 bool aPersistent,
3726 int64_t* aTimestamp,
3727 bool* aPersisted) {
3728 nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted);
3729 if (NS_WARN_IF(NS_FAILED(rv))) {
3730 rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
3731 if (NS_WARN_IF(NS_FAILED(rv))) {
3732 return rv;
3735 rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted);
3736 if (NS_WARN_IF(NS_FAILED(rv))) {
3737 return rv;
3741 return NS_OK;
3744 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType) {
3745 MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
3746 aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
3748 nsCOMPtr<nsIFile> directory;
3749 nsresult rv = NS_NewLocalFile(GetStoragePath(aPersistenceType), false,
3750 getter_AddRefs(directory));
3751 if (NS_WARN_IF(NS_FAILED(rv))) {
3752 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_NewLocalFile);
3753 return rv;
3756 bool created;
3757 rv = EnsureDirectory(directory, &created);
3758 if (NS_WARN_IF(NS_FAILED(rv))) {
3759 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_EnsureDirectory);
3760 return rv;
3763 nsCOMPtr<nsIDirectoryEnumerator> entries;
3764 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
3765 if (NS_WARN_IF(NS_FAILED(rv))) {
3766 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_GetDirEntries);
3767 return rv;
3770 // A keeper to defer the return only in Nightly, so that the telemetry data
3771 // for whole profile can be collected
3772 #ifdef NIGHTLY_BUILD
3773 nsresult statusKeeper = NS_OK;
3774 #endif
3776 nsCOMPtr<nsIFile> childDirectory;
3777 while (NS_SUCCEEDED(
3778 (rv = entries->GetNextFile(getter_AddRefs(childDirectory)))) &&
3779 childDirectory) {
3780 bool isDirectory;
3781 rv = childDirectory->IsDirectory(&isDirectory);
3782 if (NS_WARN_IF(NS_FAILED(rv))) {
3783 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_IsDirectory);
3784 RECORD_IN_NIGHTLY(statusKeeper, rv);
3785 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3788 if (!isDirectory) {
3789 nsString leafName;
3790 rv = childDirectory->GetLeafName(leafName);
3791 if (NS_WARN_IF(NS_FAILED(rv))) {
3792 REPORT_TELEMETRY_INIT_ERR(kExternalError, Rep_GetLeafName);
3793 RECORD_IN_NIGHTLY(statusKeeper, rv);
3794 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3797 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3798 continue;
3801 UNKNOWN_FILE_WARNING(leafName);
3803 REPORT_TELEMETRY_INIT_ERR(kInternalError, Rep_UnexpectedFile);
3804 RECORD_IN_NIGHTLY(statusKeeper, NS_ERROR_UNEXPECTED);
3805 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(NS_ERROR_UNEXPECTED);
3808 int64_t timestamp;
3809 bool persisted;
3810 nsCString suffix;
3811 nsCString group;
3812 nsCString origin;
3813 rv = GetDirectoryMetadata2WithRestore(childDirectory,
3814 /* aPersistent */ false, &timestamp,
3815 &persisted, suffix, group, origin,
3816 /* aTelemetry */ true);
3817 if (NS_WARN_IF(NS_FAILED(rv))) {
3818 // Error should have reported in GetDirectoryMetadata2WithRestore
3819 RECORD_IN_NIGHTLY(statusKeeper, rv);
3820 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3823 rv = InitializeOrigin(aPersistenceType, group, origin, timestamp, persisted,
3824 childDirectory);
3825 if (NS_WARN_IF(NS_FAILED(rv))) {
3826 // Error should have reported in InitializeOrigin
3827 RECORD_IN_NIGHTLY(statusKeeper, rv);
3828 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3831 if (NS_WARN_IF(NS_FAILED(rv))) {
3832 REPORT_TELEMETRY_INIT_ERR(kInternalError, Rep_GetNextFile);
3833 RECORD_IN_NIGHTLY(statusKeeper, rv);
3834 #ifndef NIGHTLY_BUILD
3835 return rv;
3836 #endif
3839 #ifdef NIGHTLY_BUILD
3840 if (NS_FAILED(statusKeeper)) {
3841 return statusKeeper;
3843 #endif
3845 return NS_OK;
3848 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
3849 const nsACString& aGroup,
3850 const nsACString& aOrigin,
3851 int64_t aAccessTime, bool aPersisted,
3852 nsIFile* aDirectory) {
3853 AssertIsOnIOThread();
3855 nsresult rv;
3857 bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
3859 // We need to initialize directories of all clients if they exists and also
3860 // get the total usage to initialize the quota.
3861 nsAutoPtr<UsageInfo> usageInfo;
3862 if (trackQuota) {
3863 usageInfo = new UsageInfo();
3866 // A keeper to defer the return only in Nightly, so that the telemetry data
3867 // for whole profile can be collected
3868 #ifdef NIGHTLY_BUILD
3869 nsresult statusKeeper = NS_OK;
3870 #endif
3872 nsCOMPtr<nsIDirectoryEnumerator> entries;
3873 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
3874 if (NS_WARN_IF(NS_FAILED(rv))) {
3875 REPORT_TELEMETRY_INIT_ERR(kExternalError, Ori_GetDirEntries);
3876 return rv;
3879 nsCOMPtr<nsIFile> file;
3880 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
3881 file) {
3882 bool isDirectory;
3883 rv = file->IsDirectory(&isDirectory);
3884 if (NS_WARN_IF(NS_FAILED(rv))) {
3885 REPORT_TELEMETRY_INIT_ERR(kExternalError, Ori_IsDirectory);
3886 RECORD_IN_NIGHTLY(statusKeeper, rv);
3887 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3890 nsString leafName;
3891 rv = file->GetLeafName(leafName);
3892 if (NS_WARN_IF(NS_FAILED(rv))) {
3893 REPORT_TELEMETRY_INIT_ERR(kExternalError, Ori_GetLeafName);
3894 RECORD_IN_NIGHTLY(statusKeeper, rv);
3895 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3898 if (!isDirectory) {
3899 if (IsOriginMetadata(leafName)) {
3900 continue;
3903 if (IsTempMetadata(leafName)) {
3904 rv = file->Remove(/* recursive */ false);
3905 if (NS_WARN_IF(NS_FAILED(rv))) {
3906 REPORT_TELEMETRY_INIT_ERR(kExternalError, Ori_Remove);
3907 RECORD_IN_NIGHTLY(statusKeeper, rv);
3908 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3911 continue;
3914 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
3915 continue;
3918 UNKNOWN_FILE_WARNING(leafName);
3919 REPORT_TELEMETRY_INIT_ERR(kInternalError, Ori_UnexpectedFile);
3920 RECORD_IN_NIGHTLY(statusKeeper, NS_ERROR_UNEXPECTED);
3921 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(NS_ERROR_UNEXPECTED);
3924 Client::Type clientType;
3925 rv = Client::TypeFromText(leafName, clientType);
3926 if (NS_FAILED(rv)) {
3927 UNKNOWN_FILE_WARNING(leafName);
3928 REPORT_TELEMETRY_INIT_ERR(kInternalError, Ori_UnexpectedClient);
3929 RECORD_IN_NIGHTLY(statusKeeper, NS_ERROR_UNEXPECTED);
3930 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(NS_ERROR_UNEXPECTED);
3933 Atomic<bool> dummy(false);
3934 rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin,
3935 /* aCanceled */ dummy, usageInfo);
3936 if (NS_WARN_IF(NS_FAILED(rv))) {
3937 // error should have reported in InitOrigin
3938 RECORD_IN_NIGHTLY(statusKeeper, rv);
3939 CONTINUE_IN_NIGHTLY_RETURN_IN_OTHERS(rv);
3942 if (NS_WARN_IF(NS_FAILED(rv))) {
3943 REPORT_TELEMETRY_INIT_ERR(kInternalError, Ori_GetNextFile);
3944 RECORD_IN_NIGHTLY(statusKeeper, rv);
3945 #ifndef NIGHTLY_BUILD
3946 return rv;
3947 #endif
3950 #ifdef NIGHTLY_BUILD
3951 if (NS_FAILED(statusKeeper)) {
3952 return statusKeeper;
3954 #endif
3956 if (trackQuota) {
3957 InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin,
3958 usageInfo->TotalUsage(), aAccessTime, aPersisted);
3961 return NS_OK;
3964 nsresult QuotaManager::MaybeUpgradeIndexedDBDirectory() {
3965 AssertIsOnIOThread();
3967 nsCOMPtr<nsIFile> indexedDBDir;
3968 nsresult rv =
3969 NS_NewLocalFile(mIndexedDBPath, false, getter_AddRefs(indexedDBDir));
3970 NS_ENSURE_SUCCESS(rv, rv);
3972 bool exists;
3973 rv = indexedDBDir->Exists(&exists);
3974 NS_ENSURE_SUCCESS(rv, rv);
3976 if (!exists) {
3977 // Nothing to upgrade.
3978 return NS_OK;
3981 bool isDirectory;
3982 rv = indexedDBDir->IsDirectory(&isDirectory);
3983 NS_ENSURE_SUCCESS(rv, rv);
3985 if (!isDirectory) {
3986 NS_WARNING("indexedDB entry is not a directory!");
3987 return NS_OK;
3990 nsCOMPtr<nsIFile> persistentStorageDir;
3991 rv = NS_NewLocalFile(mStoragePath, false,
3992 getter_AddRefs(persistentStorageDir));
3993 NS_ENSURE_SUCCESS(rv, rv);
3995 rv = persistentStorageDir->Append(
3996 NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
3997 NS_ENSURE_SUCCESS(rv, rv);
3999 rv = persistentStorageDir->Exists(&exists);
4000 NS_ENSURE_SUCCESS(rv, rv);
4002 if (exists) {
4003 NS_WARNING("indexedDB directory shouldn't exist after the upgrade!");
4004 return NS_OK;
4007 nsCOMPtr<nsIFile> storageDir;
4008 rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir));
4009 NS_ENSURE_SUCCESS(rv, rv);
4011 // MoveTo() is atomic if the move happens on the same volume which should
4012 // be our case, so even if we crash in the middle of the operation nothing
4013 // breaks next time we try to initialize.
4014 // However there's a theoretical possibility that the indexedDB directory
4015 // is on different volume, but it should be rare enough that we don't have
4016 // to worry about it.
4017 rv = indexedDBDir->MoveTo(storageDir,
4018 NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
4019 NS_ENSURE_SUCCESS(rv, rv);
4021 return NS_OK;
4024 nsresult QuotaManager::MaybeUpgradePersistentStorageDirectory() {
4025 AssertIsOnIOThread();
4027 nsCOMPtr<nsIFile> persistentStorageDir;
4028 nsresult rv = NS_NewLocalFile(mStoragePath, false,
4029 getter_AddRefs(persistentStorageDir));
4030 if (NS_WARN_IF(NS_FAILED(rv))) {
4031 return rv;
4034 rv = persistentStorageDir->Append(
4035 NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
4036 if (NS_WARN_IF(NS_FAILED(rv))) {
4037 return rv;
4040 bool exists;
4041 rv = persistentStorageDir->Exists(&exists);
4042 if (NS_WARN_IF(NS_FAILED(rv))) {
4043 return rv;
4046 if (!exists) {
4047 // Nothing to upgrade.
4048 return NS_OK;
4051 bool isDirectory;
4052 rv = persistentStorageDir->IsDirectory(&isDirectory);
4053 if (NS_WARN_IF(NS_FAILED(rv))) {
4054 return rv;
4057 if (!isDirectory) {
4058 NS_WARNING("persistent entry is not a directory!");
4059 return NS_OK;
4062 nsCOMPtr<nsIFile> defaultStorageDir;
4063 rv = NS_NewLocalFile(mDefaultStoragePath, false,
4064 getter_AddRefs(defaultStorageDir));
4065 if (NS_WARN_IF(NS_FAILED(rv))) {
4066 return rv;
4069 rv = defaultStorageDir->Exists(&exists);
4070 if (NS_WARN_IF(NS_FAILED(rv))) {
4071 return rv;
4074 if (exists) {
4075 NS_WARNING("storage/persistent shouldn't exist after the upgrade!");
4076 return NS_OK;
4079 // Create real metadata files for origin directories in persistent storage.
4080 RefPtr<CreateOrUpgradeDirectoryMetadataHelper> helper =
4081 new CreateOrUpgradeDirectoryMetadataHelper(persistentStorageDir,
4082 /* aPersistent */ true);
4084 rv = helper->ProcessRepository();
4085 if (NS_WARN_IF(NS_FAILED(rv))) {
4086 return rv;
4089 // Upgrade metadata files for origin directories in temporary storage.
4090 nsCOMPtr<nsIFile> temporaryStorageDir;
4091 rv = NS_NewLocalFile(mTemporaryStoragePath, false,
4092 getter_AddRefs(temporaryStorageDir));
4093 if (NS_WARN_IF(NS_FAILED(rv))) {
4094 return rv;
4097 rv = temporaryStorageDir->Exists(&exists);
4098 if (NS_WARN_IF(NS_FAILED(rv))) {
4099 return rv;
4102 if (exists) {
4103 rv = temporaryStorageDir->IsDirectory(&isDirectory);
4104 if (NS_WARN_IF(NS_FAILED(rv))) {
4105 return rv;
4108 if (!isDirectory) {
4109 NS_WARNING("temporary entry is not a directory!");
4110 return NS_OK;
4113 helper =
4114 new CreateOrUpgradeDirectoryMetadataHelper(temporaryStorageDir,
4115 /* aPersistent */ false);
4117 rv = helper->ProcessRepository();
4118 if (NS_WARN_IF(NS_FAILED(rv))) {
4119 return rv;
4123 // And finally rename persistent to default.
4124 rv = persistentStorageDir->RenameTo(
4125 nullptr, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME));
4126 if (NS_WARN_IF(NS_FAILED(rv))) {
4127 return rv;
4130 return NS_OK;
4133 nsresult QuotaManager::MaybeRemoveOldDirectories() {
4134 AssertIsOnIOThread();
4136 nsCOMPtr<nsIFile> indexedDBDir;
4137 nsresult rv =
4138 NS_NewLocalFile(mIndexedDBPath, false, getter_AddRefs(indexedDBDir));
4139 if (NS_WARN_IF(NS_FAILED(rv))) {
4140 return rv;
4143 bool exists;
4144 rv = indexedDBDir->Exists(&exists);
4145 if (NS_WARN_IF(NS_FAILED(rv))) {
4146 return rv;
4149 if (exists) {
4150 QM_WARNING("Deleting old <profile>/indexedDB directory!");
4152 rv = indexedDBDir->Remove(/* aRecursive */ true);
4153 if (NS_WARN_IF(NS_FAILED(rv))) {
4154 return rv;
4158 nsCOMPtr<nsIFile> persistentStorageDir;
4159 rv = NS_NewLocalFile(mStoragePath, false,
4160 getter_AddRefs(persistentStorageDir));
4161 if (NS_WARN_IF(NS_FAILED(rv))) {
4162 return rv;
4165 rv = persistentStorageDir->Append(
4166 NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
4167 if (NS_WARN_IF(NS_FAILED(rv))) {
4168 return rv;
4171 rv = persistentStorageDir->Exists(&exists);
4172 if (NS_WARN_IF(NS_FAILED(rv))) {
4173 return rv;
4176 if (exists) {
4177 QM_WARNING("Deleting old <profile>/storage/persistent directory!");
4179 rv = persistentStorageDir->Remove(/* aRecursive */ true);
4180 if (NS_WARN_IF(NS_FAILED(rv))) {
4181 return rv;
4185 return NS_OK;
4188 template <typename Helper>
4189 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
4190 const int32_t aNewVersion,
4191 mozIStorageConnection* aConnection) {
4192 AssertIsOnIOThread();
4193 MOZ_ASSERT(aNewVersion > aOldVersion);
4194 MOZ_ASSERT(aNewVersion <= kStorageVersion);
4195 MOZ_ASSERT(aConnection);
4197 nsresult rv;
4199 for (const PersistenceType persistenceType : kAllPersistenceTypes) {
4200 nsCOMPtr<nsIFile> directory;
4201 rv = NS_NewLocalFile(GetStoragePath(persistenceType), false,
4202 getter_AddRefs(directory));
4203 if (NS_WARN_IF(NS_FAILED(rv))) {
4204 return rv;
4207 bool exists;
4208 rv = directory->Exists(&exists);
4209 if (NS_WARN_IF(NS_FAILED(rv))) {
4210 return rv;
4213 if (!exists) {
4214 continue;
4217 bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT;
4218 RefPtr<RepositoryOperationBase> helper = new Helper(directory, persistent);
4219 rv = helper->ProcessRepository();
4220 if (NS_WARN_IF(NS_FAILED(rv))) {
4221 return rv;
4225 #ifdef DEBUG
4227 int32_t storageVersion;
4228 rv = aConnection->GetSchemaVersion(&storageVersion);
4229 if (NS_WARN_IF(NS_FAILED(rv))) {
4230 return rv;
4233 MOZ_ASSERT(storageVersion == aOldVersion);
4235 #endif
4237 rv = aConnection->SetSchemaVersion(aNewVersion);
4238 if (NS_WARN_IF(NS_FAILED(rv))) {
4239 return rv;
4242 return NS_OK;
4245 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
4246 mozIStorageConnection* aConnection) {
4247 AssertIsOnIOThread();
4248 MOZ_ASSERT(aConnection);
4250 nsresult rv = MaybeUpgradeIndexedDBDirectory();
4251 if (NS_WARN_IF(NS_FAILED(rv))) {
4252 return rv;
4255 rv = MaybeUpgradePersistentStorageDirectory();
4256 if (NS_WARN_IF(NS_FAILED(rv))) {
4257 return rv;
4260 rv = MaybeRemoveOldDirectories();
4261 if (NS_WARN_IF(NS_FAILED(rv))) {
4262 return rv;
4265 rv = UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
4266 0, MakeStorageVersion(1, 0), aConnection);
4267 if (NS_WARN_IF(NS_FAILED(rv))) {
4268 return rv;
4271 return NS_OK;
4274 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
4275 mozIStorageConnection* aConnection) {
4276 AssertIsOnIOThread();
4277 MOZ_ASSERT(aConnection);
4279 // The upgrade consists of a number of logically distinct bugs that
4280 // intentionally got fixed at the same time to trigger just one major
4281 // version bump.
4284 // Morgue directory cleanup
4285 // [Feature/Bug]:
4286 // The original bug that added "on demand" morgue cleanup is 1165119.
4288 // [Mutations]:
4289 // Morgue directories are removed from all origin directories during the
4290 // upgrade process. Origin initialization and usage calculation doesn't try
4291 // to remove morgue directories anymore.
4293 // [Downgrade-incompatible changes]:
4294 // Morgue directories can reappear if user runs an already upgraded profile
4295 // in an older version of Firefox. Morgue directories then prevent current
4296 // Firefox from initializing and using the storage.
4299 // App data removal
4300 // [Feature/Bug]:
4301 // The bug that removes isApp flags is 1311057.
4303 // [Mutations]:
4304 // Origin directories with appIds are removed during the upgrade process.
4306 // [Downgrade-incompatible changes]:
4307 // Origin directories with appIds can reappear if user runs an already
4308 // upgraded profile in an older version of Firefox. Origin directories with
4309 // appIds don't prevent current Firefox from initializing and using the
4310 // storage, but they wouldn't ever be removed again, potentially causing
4311 // problems once appId is removed from origin attributes.
4314 // Strip obsolete origin attributes
4315 // [Feature/Bug]:
4316 // The bug that strips obsolete origin attributes is 1314361.
4318 // [Mutations]:
4319 // Origin directories with obsolete origin attributes are renamed and their
4320 // metadata files are updated during the upgrade process.
4322 // [Downgrade-incompatible changes]:
4323 // Origin directories with obsolete origin attributes can reappear if user
4324 // runs an already upgraded profile in an older version of Firefox. Origin
4325 // directories with obsolete origin attributes don't prevent current Firefox
4326 // from initializing and using the storage, but they wouldn't ever be upgraded
4327 // again, potentially causing problems in future.
4330 // File manager directory renaming (client specific)
4331 // [Feature/Bug]:
4332 // The original bug that added "on demand" file manager directory renaming is
4333 // 1056939.
4335 // [Mutations]:
4336 // All file manager directories are renamed to contain the ".files" suffix.
4338 // [Downgrade-incompatible changes]:
4339 // File manager directories with the ".files" suffix prevent older versions of
4340 // Firefox from initializing and using the storage.
4341 // File manager directories without the ".files" suffix can appear if user
4342 // runs an already upgraded profile in an older version of Firefox. File
4343 // manager directories without the ".files" suffix then prevent current
4344 // Firefox from initializing and using the storage.
4346 nsresult rv = UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
4347 MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection);
4348 if (NS_WARN_IF(NS_FAILED(rv))) {
4349 return rv;
4352 return NS_OK;
4355 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
4356 mozIStorageConnection* aConnection) {
4357 AssertIsOnIOThread();
4358 MOZ_ASSERT(aConnection);
4360 // The upgrade is mainly to create a directory padding file in DOM Cache
4361 // directory to record the overall padding size of an origin.
4363 nsresult rv = UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
4364 MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection);
4365 if (NS_WARN_IF(NS_FAILED(rv))) {
4366 return rv;
4369 return NS_OK;
4372 nsresult QuotaManager::MaybeRemoveLocalStorageData() {
4373 AssertIsOnIOThread();
4374 MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
4376 // Cleanup the tmp file first, if there's any.
4377 nsCOMPtr<nsIFile> lsArchiveTmpFile;
4378 nsresult rv =
4379 NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveTmpFile));
4380 if (NS_WARN_IF(NS_FAILED(rv))) {
4381 return rv;
4384 rv = lsArchiveTmpFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
4385 if (NS_WARN_IF(NS_FAILED(rv))) {
4386 return rv;
4389 bool exists;
4390 rv = lsArchiveTmpFile->Exists(&exists);
4391 if (NS_WARN_IF(NS_FAILED(rv))) {
4392 return rv;
4395 if (exists) {
4396 rv = lsArchiveTmpFile->Remove(false);
4397 if (NS_WARN_IF(NS_FAILED(rv))) {
4398 return rv;
4402 // Now check the real archive file.
4403 nsCOMPtr<nsIFile> lsArchiveFile;
4404 rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveFile));
4405 if (NS_WARN_IF(NS_FAILED(rv))) {
4406 return rv;
4409 rv = lsArchiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
4410 if (NS_WARN_IF(NS_FAILED(rv))) {
4411 return rv;
4414 rv = lsArchiveFile->Exists(&exists);
4415 if (NS_WARN_IF(NS_FAILED(rv))) {
4416 return rv;
4419 if (!exists) {
4420 // If the ls archive doesn't exist then ls directories can't exist either.
4421 return NS_OK;
4424 rv = MaybeRemoveLocalStorageDirectories();
4425 if (NS_WARN_IF(NS_FAILED(rv))) {
4426 return rv;
4429 // Finally remove the ls archive, so we don't have to check all origin
4430 // directories next time this method is called.
4431 rv = lsArchiveFile->Remove(false);
4432 if (NS_WARN_IF(NS_FAILED(rv))) {
4433 return rv;
4436 return NS_OK;
4439 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
4440 AssertIsOnIOThread();
4442 nsCOMPtr<nsIFile> defaultStorageDir;
4443 nsresult rv = NS_NewLocalFile(mDefaultStoragePath, false,
4444 getter_AddRefs(defaultStorageDir));
4445 if (NS_WARN_IF(NS_FAILED(rv))) {
4446 return rv;
4449 bool exists;
4450 rv = defaultStorageDir->Exists(&exists);
4451 if (NS_WARN_IF(NS_FAILED(rv))) {
4452 return rv;
4455 if (!exists) {
4456 return NS_OK;
4459 nsCOMPtr<nsIDirectoryEnumerator> entries;
4460 rv = defaultStorageDir->GetDirectoryEntries(getter_AddRefs(entries));
4461 if (NS_WARN_IF(NS_FAILED(rv))) {
4462 return rv;
4465 if (!entries) {
4466 return NS_OK;
4469 while (true) {
4470 bool hasMore;
4471 rv = entries->HasMoreElements(&hasMore);
4472 if (NS_WARN_IF(NS_FAILED(rv))) {
4473 return rv;
4476 if (!hasMore) {
4477 break;
4480 nsCOMPtr<nsISupports> entry;
4481 rv = entries->GetNext(getter_AddRefs(entry));
4482 if (NS_WARN_IF(NS_FAILED(rv))) {
4483 return rv;
4486 nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry);
4487 MOZ_ASSERT(originDir);
4489 rv = originDir->Exists(&exists);
4490 if (NS_WARN_IF(NS_FAILED(rv))) {
4491 return rv;
4494 MOZ_ASSERT(exists);
4496 bool isDirectory;
4497 rv = originDir->IsDirectory(&isDirectory);
4498 if (NS_WARN_IF(NS_FAILED(rv))) {
4499 return rv;
4502 if (!isDirectory) {
4503 nsString leafName;
4504 rv = originDir->GetLeafName(leafName);
4505 if (NS_WARN_IF(NS_FAILED(rv))) {
4506 return rv;
4509 // Unknown files during upgrade are allowed. Just warn if we find them.
4510 if (!IsOSMetadata(leafName)) {
4511 UNKNOWN_FILE_WARNING(leafName);
4514 continue;
4517 nsCOMPtr<nsIFile> lsDir;
4518 rv = originDir->Clone(getter_AddRefs(lsDir));
4519 if (NS_WARN_IF(NS_FAILED(rv))) {
4520 return rv;
4523 rv = lsDir->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
4524 if (NS_WARN_IF(NS_FAILED(rv))) {
4525 return rv;
4528 rv = lsDir->Exists(&exists);
4529 if (NS_WARN_IF(NS_FAILED(rv))) {
4530 return rv;
4533 if (!exists) {
4534 continue;
4537 rv = lsDir->IsDirectory(&isDirectory);
4538 if (NS_WARN_IF(NS_FAILED(rv))) {
4539 return rv;
4542 if (!isDirectory) {
4543 QM_WARNING("ls entry is not a directory!");
4545 continue;
4548 nsString path;
4549 rv = lsDir->GetPath(path);
4550 if (NS_WARN_IF(NS_FAILED(rv))) {
4551 return rv;
4554 QM_WARNING("Deleting %s directory!", NS_ConvertUTF16toUTF8(path).get());
4556 rv = lsDir->Remove(/* aRecursive */ true);
4557 if (NS_WARN_IF(NS_FAILED(rv))) {
4558 return rv;
4562 return NS_OK;
4565 nsresult QuotaManager::MaybeCreateLocalStorageArchive() {
4566 AssertIsOnIOThread();
4567 MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
4569 // Check if the archive was already successfully created.
4570 nsCOMPtr<nsIFile> lsArchiveFile;
4571 nsresult rv =
4572 NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveFile));
4573 if (NS_WARN_IF(NS_FAILED(rv))) {
4574 return rv;
4577 rv = lsArchiveFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
4578 if (NS_WARN_IF(NS_FAILED(rv))) {
4579 return rv;
4582 bool exists;
4583 rv = lsArchiveFile->Exists(&exists);
4584 if (NS_WARN_IF(NS_FAILED(rv))) {
4585 return rv;
4588 if (exists) {
4589 // ls-archive.sqlite already exists, nothing to create.
4590 return NS_OK;
4593 // Get the storage service first, we will need it at multiple places.
4594 nsCOMPtr<mozIStorageService> ss =
4595 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
4596 if (NS_WARN_IF(NS_FAILED(rv))) {
4597 return rv;
4600 // Get the web apps store file.
4601 nsCOMPtr<nsIFile> webAppsStoreFile;
4602 rv = NS_NewLocalFile(mBasePath, false, getter_AddRefs(webAppsStoreFile));
4603 if (NS_WARN_IF(NS_FAILED(rv))) {
4604 return rv;
4607 rv = webAppsStoreFile->Append(NS_LITERAL_STRING(WEB_APPS_STORE_FILE_NAME));
4608 if (NS_WARN_IF(NS_FAILED(rv))) {
4609 return rv;
4612 // Now check if the web apps store is useable.
4613 nsCOMPtr<mozIStorageConnection> connection;
4614 rv = CreateWebAppsStoreConnection(webAppsStoreFile, ss,
4615 getter_AddRefs(connection));
4616 if (NS_WARN_IF(NS_FAILED(rv))) {
4617 return rv;
4620 if (connection) {
4621 // Find out the journal mode.
4622 nsCOMPtr<mozIStorageStatement> stmt;
4623 rv = connection->CreateStatement(NS_LITERAL_CSTRING("PRAGMA journal_mode;"),
4624 getter_AddRefs(stmt));
4625 if (NS_WARN_IF(NS_FAILED(rv))) {
4626 return rv;
4629 bool hasResult;
4630 rv = stmt->ExecuteStep(&hasResult);
4631 if (NS_WARN_IF(NS_FAILED(rv))) {
4632 return rv;
4635 MOZ_ASSERT(hasResult);
4637 nsCString journalMode;
4638 rv = stmt->GetUTF8String(0, journalMode);
4639 if (NS_WARN_IF(NS_FAILED(rv))) {
4640 return rv;
4643 rv = stmt->Finalize();
4644 if (NS_WARN_IF(NS_FAILED(rv))) {
4645 return rv;
4648 if (journalMode.EqualsLiteral("wal")) {
4649 // We don't copy the WAL file, so make sure the old database is fully
4650 // checkpointed.
4651 rv = connection->ExecuteSimpleSQL(
4652 NS_LITERAL_CSTRING("PRAGMA wal_checkpoint(TRUNCATE);"));
4653 if (NS_WARN_IF(NS_FAILED(rv))) {
4654 return rv;
4658 // Explicitely close the connection before the old database is copied.
4659 rv = connection->Close();
4660 if (NS_WARN_IF(NS_FAILED(rv))) {
4661 return rv;
4664 // Copy the old database. The database is copied from
4665 // <profile>/webappsstore.sqlite to
4666 // <profile>/storage/ls-archive-tmp.sqlite
4667 // We use a "-tmp" postfix since we are not done yet.
4668 nsCOMPtr<nsIFile> storageDir;
4669 rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(storageDir));
4670 if (NS_WARN_IF(NS_FAILED(rv))) {
4671 return rv;
4674 rv = webAppsStoreFile->CopyTo(storageDir,
4675 NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
4676 if (NS_WARN_IF(NS_FAILED(rv))) {
4677 return rv;
4680 nsCOMPtr<nsIFile> lsArchiveTmpFile;
4681 rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(lsArchiveTmpFile));
4682 if (NS_WARN_IF(NS_FAILED(rv))) {
4683 return rv;
4686 rv = lsArchiveTmpFile->Append(NS_LITERAL_STRING(LS_ARCHIVE_TMP_FILE_NAME));
4687 if (NS_WARN_IF(NS_FAILED(rv))) {
4688 return rv;
4691 if (journalMode.EqualsLiteral("wal")) {
4692 nsCOMPtr<mozIStorageConnection> lsArchiveTmpConnection;
4693 rv = ss->OpenUnsharedDatabase(lsArchiveTmpFile,
4694 getter_AddRefs(lsArchiveTmpConnection));
4695 if (NS_WARN_IF(NS_FAILED(rv))) {
4696 return rv;
4699 // The archive will only be used for lazy data migration. There won't be
4700 // any concurrent readers and writers that could benefit from Write-Ahead
4701 // Logging. So switch to a standard rollback journal. The standard
4702 // rollback journal also provides atomicity across multiple attached
4703 // databases which is import for the lazy data migration to work safely.
4704 rv = lsArchiveTmpConnection->ExecuteSimpleSQL(
4705 NS_LITERAL_CSTRING("PRAGMA journal_mode = DELETE;"));
4706 if (NS_WARN_IF(NS_FAILED(rv))) {
4707 return rv;
4710 // The connection will be now implicitely closed (it's always safer to
4711 // close database connection before we manipulate underlying file)
4714 // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
4715 rv = lsArchiveTmpFile->MoveTo(nullptr,
4716 NS_LITERAL_STRING(LS_ARCHIVE_FILE_NAME));
4717 if (NS_WARN_IF(NS_FAILED(rv))) {
4718 return rv;
4720 } else {
4721 // If webappsstore database is not useable, just create an empty archive.
4723 // Ensure the storage directory actually exists.
4724 nsCOMPtr<nsIFile> storageDirectory;
4725 rv = NS_NewLocalFile(GetStoragePath(), false,
4726 getter_AddRefs(storageDirectory));
4727 if (NS_WARN_IF(NS_FAILED(rv))) {
4728 return rv;
4731 bool dummy;
4732 rv = EnsureDirectory(storageDirectory, &dummy);
4733 if (NS_WARN_IF(NS_FAILED(rv))) {
4734 return rv;
4737 nsCOMPtr<mozIStorageConnection> lsArchiveConnection;
4738 rv = ss->OpenUnsharedDatabase(lsArchiveFile,
4739 getter_AddRefs(lsArchiveConnection));
4740 if (NS_WARN_IF(NS_FAILED(rv))) {
4741 return rv;
4744 rv = StorageDBUpdater::Update(lsArchiveConnection);
4745 if (NS_WARN_IF(NS_FAILED(rv))) {
4746 return rv;
4750 return NS_OK;
4753 #ifdef DEBUG
4755 void QuotaManager::AssertStorageIsInitialized() const {
4756 AssertIsOnIOThread();
4757 MOZ_ASSERT(mStorageInitialized);
4760 #endif // DEBUG
4762 nsresult QuotaManager::EnsureStorageIsInitialized() {
4763 AssertIsOnIOThread();
4765 if (mStorageInitialized) {
4766 return NS_OK;
4769 nsCOMPtr<nsIFile> storageFile;
4770 nsresult rv = NS_NewLocalFile(mBasePath, false, getter_AddRefs(storageFile));
4771 if (NS_WARN_IF(NS_FAILED(rv))) {
4772 return rv;
4775 rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME));
4776 if (NS_WARN_IF(NS_FAILED(rv))) {
4777 return rv;
4780 nsCOMPtr<mozIStorageService> ss =
4781 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
4782 if (NS_WARN_IF(NS_FAILED(rv))) {
4783 return rv;
4786 nsCOMPtr<mozIStorageConnection> connection;
4787 rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection));
4788 if (rv == NS_ERROR_FILE_CORRUPTED) {
4789 // Nuke the database file.
4790 rv = storageFile->Remove(false);
4791 if (NS_WARN_IF(NS_FAILED(rv))) {
4792 return rv;
4795 rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection));
4798 if (NS_WARN_IF(NS_FAILED(rv))) {
4799 return rv;
4802 // We want extra durability for this important file.
4803 rv = connection->ExecuteSimpleSQL(
4804 NS_LITERAL_CSTRING("PRAGMA synchronous = EXTRA;"));
4805 if (NS_WARN_IF(NS_FAILED(rv))) {
4806 return rv;
4809 // Check to make sure that the storage version is correct.
4810 int32_t storageVersion;
4811 rv = connection->GetSchemaVersion(&storageVersion);
4812 if (NS_WARN_IF(NS_FAILED(rv))) {
4813 return rv;
4816 // Hacky downgrade logic!
4817 // If we see major.minor of 3.0, downgrade it to be 2.1.
4818 if (storageVersion == kHackyPreDowngradeStorageVersion) {
4819 storageVersion = kHackyPostDowngradeStorageVersion;
4820 rv = connection->SetSchemaVersion(storageVersion);
4821 if (NS_WARN_IF(NS_FAILED(rv))) {
4822 MOZ_ASSERT(false, "Downgrade didn't take.");
4823 return rv;
4827 if (GetMajorStorageVersion(storageVersion) > kMajorStorageVersion) {
4828 NS_WARNING("Unable to initialize storage, version is too high!");
4829 return NS_ERROR_FAILURE;
4832 if (storageVersion < kStorageVersion) {
4833 const bool newDatabase = !storageVersion;
4835 nsCOMPtr<nsIFile> storageDir;
4836 rv = NS_NewLocalFile(mStoragePath, false, getter_AddRefs(storageDir));
4837 if (NS_WARN_IF(NS_FAILED(rv))) {
4838 return rv;
4841 bool exists;
4842 rv = storageDir->Exists(&exists);
4843 if (NS_WARN_IF(NS_FAILED(rv))) {
4844 return rv;
4847 if (!exists) {
4848 nsCOMPtr<nsIFile> indexedDBDir;
4849 rv = NS_NewLocalFile(mIndexedDBPath, false, getter_AddRefs(indexedDBDir));
4850 if (NS_WARN_IF(NS_FAILED(rv))) {
4851 return rv;
4854 rv = indexedDBDir->Exists(&exists);
4855 if (NS_WARN_IF(NS_FAILED(rv))) {
4856 return rv;
4860 const bool newDirectory = !exists;
4862 if (newDatabase) {
4863 // Set the page size first.
4864 if (kSQLitePageSizeOverride) {
4865 rv = connection->ExecuteSimpleSQL(nsPrintfCString(
4866 "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride));
4867 if (NS_WARN_IF(NS_FAILED(rv))) {
4868 return rv;
4873 mozStorageTransaction transaction(
4874 connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4876 // An upgrade method can upgrade the database, the storage or both.
4877 // The upgrade loop below can only be avoided when there's no database and
4878 // no storage yet (e.g. new profile).
4879 if (newDatabase && newDirectory) {
4880 rv = CreateTables(connection);
4881 if (NS_WARN_IF(NS_FAILED(rv))) {
4882 return rv;
4885 MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&storageVersion)));
4886 MOZ_ASSERT(storageVersion == kStorageVersion);
4887 } else {
4888 // This logic needs to change next time we change the storage!
4889 static_assert(kStorageVersion == int32_t((2 << 16) + 1),
4890 "Upgrade function needed due to storage version increase.");
4892 while (storageVersion != kStorageVersion) {
4893 if (storageVersion == 0) {
4894 rv = UpgradeStorageFrom0_0To1_0(connection);
4895 } else if (storageVersion == MakeStorageVersion(1, 0)) {
4896 rv = UpgradeStorageFrom1_0To2_0(connection);
4897 } else if (storageVersion == MakeStorageVersion(2, 0)) {
4898 rv = UpgradeStorageFrom2_0To2_1(connection);
4899 } else {
4900 NS_WARNING(
4901 "Unable to initialize storage, no upgrade path is "
4902 "available!");
4903 return NS_ERROR_FAILURE;
4906 if (NS_WARN_IF(NS_FAILED(rv))) {
4907 return rv;
4910 rv = connection->GetSchemaVersion(&storageVersion);
4911 if (NS_WARN_IF(NS_FAILED(rv))) {
4912 return rv;
4916 MOZ_ASSERT(storageVersion == kStorageVersion);
4919 rv = transaction.Commit();
4920 if (NS_WARN_IF(NS_FAILED(rv))) {
4921 return rv;
4925 if (CachedNextGenLocalStorageEnabled()) {
4926 rv = MaybeCreateLocalStorageArchive();
4927 } else {
4928 rv = MaybeRemoveLocalStorageData();
4930 if (NS_WARN_IF(NS_FAILED(rv))) {
4931 return rv;
4934 mStorageInitialized = true;
4936 return NS_OK;
4939 void QuotaManager::OpenDirectory(PersistenceType aPersistenceType,
4940 const nsACString& aGroup,
4941 const nsACString& aOrigin,
4942 Client::Type aClientType, bool aExclusive,
4943 OpenDirectoryListener* aOpenListener) {
4944 AssertIsOnOwningThread();
4946 RefPtr<DirectoryLockImpl> lock = CreateDirectoryLock(
4947 Nullable<PersistenceType>(aPersistenceType), aGroup,
4948 OriginScope::FromOrigin(aOrigin), Nullable<Client::Type>(aClientType),
4949 aExclusive, false, aOpenListener);
4950 MOZ_ASSERT(lock);
4953 void QuotaManager::OpenDirectoryInternal(
4954 const Nullable<PersistenceType>& aPersistenceType,
4955 const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
4956 bool aExclusive, OpenDirectoryListener* aOpenListener) {
4957 AssertIsOnOwningThread();
4959 RefPtr<DirectoryLockImpl> lock = CreateDirectoryLock(
4960 aPersistenceType, EmptyCString(), aOriginScope,
4961 Nullable<Client::Type>(aClientType), aExclusive, true, aOpenListener);
4962 MOZ_ASSERT(lock);
4964 if (!aExclusive) {
4965 return;
4968 // All the locks that block this new exclusive lock need to be invalidated.
4969 // We also need to notify clients to abort operations for them.
4970 AutoTArray<nsAutoPtr<nsTHashtable<nsCStringHashKey>>, Client::TYPE_MAX>
4971 origins;
4972 origins.SetLength(Client::TypeMax());
4974 const nsTArray<DirectoryLockImpl*>& blockedOnLocks =
4975 lock->GetBlockedOnLocks();
4977 for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) {
4978 if (!blockedOnLock->IsInternal()) {
4979 blockedOnLock->Invalidate();
4981 MOZ_ASSERT(!blockedOnLock->GetClientType().IsNull());
4982 Client::Type clientType = blockedOnLock->GetClientType().Value();
4983 MOZ_ASSERT(clientType < Client::TypeMax());
4985 const OriginScope& originScope = blockedOnLock->GetOriginScope();
4986 MOZ_ASSERT(originScope.IsOrigin());
4987 MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
4989 nsAutoPtr<nsTHashtable<nsCStringHashKey>>& origin = origins[clientType];
4990 if (!origin) {
4991 origin = new nsTHashtable<nsCStringHashKey>();
4993 origin->PutEntry(originScope.GetOrigin());
4997 for (uint32_t index : IntegerRange(uint32_t(Client::TypeMax()))) {
4998 if (origins[index]) {
4999 for (auto iter = origins[index]->Iter(); !iter.Done(); iter.Next()) {
5000 MOZ_ASSERT(mClients[index]);
5002 mClients[index]->AbortOperations(iter.Get()->GetKey());
5008 nsresult QuotaManager::EnsureOriginIsInitialized(
5009 PersistenceType aPersistenceType, const nsACString& aSuffix,
5010 const nsACString& aGroup, const nsACString& aOrigin,
5011 bool aCreateIfNotExists, nsIFile** aDirectory) {
5012 AssertIsOnIOThread();
5013 MOZ_ASSERT(aDirectory);
5015 nsCOMPtr<nsIFile> directory;
5016 bool created;
5017 nsresult rv = EnsureOriginIsInitializedInternal(
5018 aPersistenceType, aSuffix, aGroup, aOrigin, aCreateIfNotExists,
5019 getter_AddRefs(directory), &created);
5020 if (rv == NS_ERROR_NOT_AVAILABLE) {
5021 return rv;
5023 if (NS_WARN_IF(NS_FAILED(rv))) {
5024 return rv;
5027 directory.forget(aDirectory);
5028 return NS_OK;
5031 nsresult QuotaManager::EnsureOriginIsInitializedInternal(
5032 PersistenceType aPersistenceType, const nsACString& aSuffix,
5033 const nsACString& aGroup, const nsACString& aOrigin,
5034 bool aCreateIfNotExists, nsIFile** aDirectory, bool* aCreated) {
5035 AssertIsOnIOThread();
5036 MOZ_ASSERT(aDirectory);
5037 MOZ_ASSERT(aCreated);
5039 nsresult rv = EnsureStorageIsInitialized();
5040 NS_ENSURE_SUCCESS(rv, rv);
5042 // Get directory for this origin and persistence type.
5043 nsCOMPtr<nsIFile> directory;
5044 rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
5045 getter_AddRefs(directory));
5046 NS_ENSURE_SUCCESS(rv, rv);
5048 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5049 if (mInitializedOrigins.Contains(aOrigin)) {
5050 directory.forget(aDirectory);
5051 *aCreated = false;
5052 return NS_OK;
5054 } else {
5055 rv = EnsureTemporaryStorageIsInitialized();
5056 if (NS_WARN_IF(NS_FAILED(rv))) {
5057 return rv;
5061 bool created;
5062 rv = EnsureOriginDirectory(directory, aCreateIfNotExists, &created);
5063 if (rv == NS_ERROR_NOT_AVAILABLE) {
5064 return rv;
5066 if (NS_WARN_IF(NS_FAILED(rv))) {
5067 return rv;
5070 int64_t timestamp;
5071 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5072 if (created) {
5073 rv = CreateDirectoryMetadataFiles(directory,
5074 /* aPersisted */ true, aSuffix, aGroup,
5075 aOrigin, &timestamp);
5076 if (NS_WARN_IF(NS_FAILED(rv))) {
5077 return rv;
5079 } else {
5080 rv = GetDirectoryMetadata2WithRestore(directory,
5081 /* aPersistent */ true, &timestamp,
5082 /* aPersisted */ nullptr);
5083 if (NS_WARN_IF(NS_FAILED(rv))) {
5084 return rv;
5087 MOZ_ASSERT(timestamp <= PR_Now());
5090 rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp,
5091 /* aPersisted */ true, directory);
5092 NS_ENSURE_SUCCESS(rv, rv);
5094 mInitializedOrigins.AppendElement(aOrigin);
5095 } else if (created) {
5096 rv = CreateDirectoryMetadataFiles(directory,
5097 /* aPersisted */ false, aSuffix, aGroup,
5098 aOrigin, &timestamp);
5099 if (NS_WARN_IF(NS_FAILED(rv))) {
5100 return rv;
5103 // Don't need to traverse the directory, since it's empty.
5104 InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin,
5105 /* aUsageBytes */ 0, timestamp,
5106 /* aPersisted */ false);
5109 directory.forget(aDirectory);
5110 *aCreated = created;
5111 return NS_OK;
5114 nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() {
5115 AssertIsOnIOThread();
5116 MOZ_ASSERT(mStorageInitialized);
5118 if (mTemporaryStorageInitialized) {
5119 return NS_OK;
5122 TimeStamp startTime = TimeStamp::Now();
5124 // A keeper to defer the return only in Nightly, so that the telemetry data
5125 // for whole profile can be collected
5126 nsresult statusKeeper = NS_OK;
5128 nsresult rv = InitializeRepository(PERSISTENCE_TYPE_DEFAULT);
5129 if (NS_WARN_IF(NS_FAILED(rv))) {
5130 RECORD_IN_NIGHTLY(statusKeeper, rv);
5132 #ifndef NIGHTLY_BUILD
5133 // We have to cleanup partially initialized quota.
5134 RemoveQuota();
5136 return rv;
5137 #endif
5140 rv = InitializeRepository(PERSISTENCE_TYPE_TEMPORARY);
5141 if (NS_WARN_IF(NS_FAILED(rv)) || NS_FAILED(statusKeeper)) {
5142 // We have to cleanup partially initialized quota.
5143 RemoveQuota();
5145 #ifdef NIGHTLY_BUILD
5146 return NS_FAILED(statusKeeper) ? statusKeeper : rv;
5147 #else
5148 return rv;
5149 #endif
5152 Telemetry::AccumulateTimeDelta(Telemetry::QM_REPOSITORIES_INITIALIZATION_TIME,
5153 startTime, TimeStamp::Now());
5155 if (gFixedLimitKB >= 0) {
5156 mTemporaryStorageLimit = static_cast<uint64_t>(gFixedLimitKB) * 1024;
5157 } else {
5158 nsCOMPtr<nsIFile> storageDir =
5159 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
5160 if (NS_WARN_IF(NS_FAILED(rv))) {
5161 return rv;
5164 rv = storageDir->InitWithPath(GetStoragePath());
5165 if (NS_WARN_IF(NS_FAILED(rv))) {
5166 return rv;
5169 rv = GetTemporaryStorageLimit(storageDir, mTemporaryStorageUsage,
5170 &mTemporaryStorageLimit);
5171 if (NS_WARN_IF(NS_FAILED(rv))) {
5172 return rv;
5176 mTemporaryStorageInitialized = true;
5178 CheckTemporaryStorageLimits();
5180 return rv;
5183 nsresult QuotaManager::EnsureOriginDirectory(nsIFile* aDirectory,
5184 bool aCreateIfNotExists,
5185 bool* aCreated) {
5186 AssertIsOnIOThread();
5187 MOZ_ASSERT(aDirectory);
5188 MOZ_ASSERT(aCreated);
5190 bool exists;
5191 nsresult rv = aDirectory->Exists(&exists);
5192 if (NS_WARN_IF(NS_FAILED(rv))) {
5193 return rv;
5196 if (!exists) {
5197 if (!aCreateIfNotExists) {
5198 return NS_ERROR_NOT_AVAILABLE;
5201 nsString leafName;
5202 rv = aDirectory->GetLeafName(leafName);
5203 if (NS_WARN_IF(NS_FAILED(rv))) {
5204 return rv;
5207 if (!leafName.EqualsLiteral(kChromeOrigin) &&
5208 !IsSanitizedOriginValid(NS_ConvertUTF16toUTF8(leafName))) {
5209 QM_WARNING(
5210 "Preventing creation of a new origin directory which is not "
5211 "supported by our origin parser or is obsolete!");
5212 return NS_ERROR_FAILURE;
5216 rv = EnsureDirectory(aDirectory, aCreated);
5217 if (NS_WARN_IF(NS_FAILED(rv))) {
5218 return rv;
5221 return NS_OK;
5224 nsresult QuotaManager::AboutToClearOrigins(
5225 const Nullable<PersistenceType>& aPersistenceType,
5226 const OriginScope& aOriginScope,
5227 const Nullable<Client::Type>& aClientType) {
5228 AssertIsOnIOThread();
5230 nsresult rv;
5232 if (aClientType.IsNull()) {
5233 for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
5234 rv = mClients[index]->AboutToClearOrigins(aPersistenceType, aOriginScope);
5235 if (NS_WARN_IF(NS_FAILED(rv))) {
5236 return rv;
5239 } else {
5240 rv = mClients[aClientType.Value()]->AboutToClearOrigins(aPersistenceType,
5241 aOriginScope);
5242 if (NS_WARN_IF(NS_FAILED(rv))) {
5243 return rv;
5247 return NS_OK;
5250 void QuotaManager::OriginClearCompleted(
5251 PersistenceType aPersistenceType, const nsACString& aOrigin,
5252 const Nullable<Client::Type>& aClientType) {
5253 AssertIsOnIOThread();
5255 if (aClientType.IsNull()) {
5256 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
5257 mInitializedOrigins.RemoveElement(aOrigin);
5260 for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
5261 mClients[index]->OnOriginClearCompleted(aPersistenceType, aOrigin);
5263 } else {
5264 mClients[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
5265 aOrigin);
5269 void QuotaManager::ResetOrClearCompleted() {
5270 AssertIsOnIOThread();
5272 mInitializedOrigins.Clear();
5273 mTemporaryStorageInitialized = false;
5274 mStorageInitialized = false;
5276 ReleaseIOThreadObjects();
5279 Client* QuotaManager::GetClient(Client::Type aClientType) {
5280 MOZ_ASSERT(aClientType >= Client::IDB);
5281 MOZ_ASSERT(aClientType < Client::TypeMax());
5283 return mClients.ElementAt(aClientType);
5286 uint64_t QuotaManager::GetGroupLimit() const {
5287 MOZ_ASSERT(mTemporaryStorageInitialized);
5289 // To avoid one group evicting all the rest, limit the amount any one group
5290 // can use to 20%. To prevent individual sites from using exorbitant amounts
5291 // of storage where there is a lot of free space, cap the group limit to 2GB.
5292 uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit * .20, 2 GB);
5294 // In low-storage situations, make an exception (while not exceeding the total
5295 // storage limit).
5296 return std::min<uint64_t>(mTemporaryStorageLimit,
5297 std::max<uint64_t>(x, 10 MB));
5300 void QuotaManager::GetGroupUsageAndLimit(const nsACString& aGroup,
5301 UsageInfo* aUsageInfo) {
5302 AssertIsOnIOThread();
5303 MOZ_ASSERT(aUsageInfo);
5306 MutexAutoLock lock(mQuotaMutex);
5308 aUsageInfo->SetLimit(GetGroupLimit());
5309 aUsageInfo->ResetUsage();
5311 GroupInfoPair* pair;
5312 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
5313 return;
5316 // Calculate temporary group usage
5317 RefPtr<GroupInfo> temporaryGroupInfo =
5318 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
5319 if (temporaryGroupInfo) {
5320 aUsageInfo->AppendToDatabaseUsage(temporaryGroupInfo->mUsage);
5323 // Calculate default group usage
5324 RefPtr<GroupInfo> defaultGroupInfo =
5325 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
5326 if (defaultGroupInfo) {
5327 aUsageInfo->AppendToDatabaseUsage(defaultGroupInfo->mUsage);
5332 void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
5333 mQuotaMutex.AssertNotCurrentThreadOwns();
5335 RefPtr<StoragePressureRunnable> storagePressureRunnable =
5336 new StoragePressureRunnable(aUsage);
5338 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
5341 // static
5342 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
5343 const nsACString& aOrigin,
5344 Client::Type aClientType,
5345 nsACString& aDatabaseId) {
5346 nsAutoCString str;
5347 str.AppendInt(aPersistenceType);
5348 str.Append('*');
5349 str.Append(aOrigin);
5350 str.Append('*');
5351 str.AppendInt(aClientType);
5353 aDatabaseId = str;
5356 // static
5357 nsresult QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
5358 nsACString* aSuffix,
5359 nsACString* aGroup,
5360 nsACString* aOrigin) {
5361 MOZ_ASSERT(NS_IsMainThread());
5362 MOZ_ASSERT(aPrincipal);
5364 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
5365 GetInfoForChrome(aSuffix, aGroup, aOrigin);
5366 return NS_OK;
5369 if (aPrincipal->GetIsNullPrincipal()) {
5370 NS_WARNING("IndexedDB not supported from this principal!");
5371 return NS_ERROR_FAILURE;
5374 nsCString origin;
5375 nsresult rv = aPrincipal->GetOrigin(origin);
5376 NS_ENSURE_SUCCESS(rv, rv);
5378 if (origin.EqualsLiteral(kChromeOrigin)) {
5379 NS_WARNING("Non-chrome principal can't use chrome origin!");
5380 return NS_ERROR_FAILURE;
5383 nsCString suffix;
5384 aPrincipal->OriginAttributesRef().CreateSuffix(suffix);
5386 if (aSuffix) {
5387 aSuffix->Assign(suffix);
5390 if (aGroup) {
5391 nsCString baseDomain;
5392 rv = aPrincipal->GetBaseDomain(baseDomain);
5393 if (NS_FAILED(rv)) {
5394 // A hack for JetPack.
5396 nsCOMPtr<nsIURI> uri;
5397 rv = aPrincipal->GetURI(getter_AddRefs(uri));
5398 NS_ENSURE_SUCCESS(rv, rv);
5400 bool isIndexedDBURI = false;
5401 rv = uri->SchemeIs("indexedDB", &isIndexedDBURI);
5402 NS_ENSURE_SUCCESS(rv, rv);
5404 if (isIndexedDBURI) {
5405 rv = NS_OK;
5408 NS_ENSURE_SUCCESS(rv, rv);
5410 if (baseDomain.IsEmpty()) {
5411 aGroup->Assign(origin);
5412 } else {
5413 aGroup->Assign(baseDomain + suffix);
5417 if (aOrigin) {
5418 aOrigin->Assign(origin);
5421 return NS_OK;
5424 // static
5425 nsresult QuotaManager::GetInfoFromWindow(nsPIDOMWindowOuter* aWindow,
5426 nsACString* aSuffix,
5427 nsACString* aGroup,
5428 nsACString* aOrigin) {
5429 MOZ_ASSERT(NS_IsMainThread());
5430 MOZ_ASSERT(aWindow);
5432 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
5433 NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE);
5435 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
5436 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
5438 nsresult rv = GetInfoFromPrincipal(principal, aSuffix, aGroup, aOrigin);
5439 NS_ENSURE_SUCCESS(rv, rv);
5441 return NS_OK;
5444 // static
5445 void QuotaManager::GetInfoForChrome(nsACString* aSuffix, nsACString* aGroup,
5446 nsACString* aOrigin) {
5447 MOZ_ASSERT(NS_IsMainThread());
5448 MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode());
5450 if (aSuffix) {
5451 aSuffix->Assign(EmptyCString());
5453 if (aGroup) {
5454 ChromeOrigin(*aGroup);
5456 if (aOrigin) {
5457 ChromeOrigin(*aOrigin);
5461 // static
5462 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
5463 MOZ_ASSERT(!aOrigin.IsEmpty());
5465 // The first prompt is not required for these origins.
5466 if (aOrigin.EqualsLiteral(kChromeOrigin) ||
5467 StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
5468 StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
5469 StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
5470 return true;
5473 return false;
5476 // static
5477 void QuotaManager::ChromeOrigin(nsACString& aOrigin) {
5478 aOrigin.AssignLiteral(kChromeOrigin);
5481 // static
5482 bool QuotaManager::AreOriginsEqualOnDisk(nsACString& aOrigin1,
5483 nsACString& aOrigin2) {
5484 nsCString origin1Sanitized(aOrigin1);
5485 SanitizeOriginString(origin1Sanitized);
5487 nsCString origin2Sanitized(aOrigin2);
5488 SanitizeOriginString(origin2Sanitized);
5490 return origin1Sanitized == origin2Sanitized;
5493 // static
5494 bool QuotaManager::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
5495 OriginAttributes* aAttrs) {
5496 MOZ_ASSERT(aAttrs);
5498 if (aOrigin.Equals(kChromeOrigin)) {
5499 aSpec = kChromeOrigin;
5500 return true;
5503 nsCString sanitizedOrigin(aOrigin);
5504 SanitizeOriginString(sanitizedOrigin);
5506 OriginParser::ResultType result =
5507 OriginParser::ParseOrigin(sanitizedOrigin, aSpec, aAttrs);
5508 if (NS_WARN_IF(result != OriginParser::ValidOrigin)) {
5509 return false;
5512 return true;
5515 uint64_t QuotaManager::LockedCollectOriginsForEviction(
5516 uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) {
5517 mQuotaMutex.AssertCurrentThreadOwns();
5519 RefPtr<CollectOriginsHelper> helper =
5520 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
5522 // Unlock while calling out to XPCOM (code behind the dispatch method needs
5523 // to acquire its own lock which can potentially lead to a deadlock and it
5524 // also calls an observer that can do various stuff like IO, so it's better
5525 // to not hold our mutex while that happens).
5527 MutexAutoUnlock autoUnlock(mQuotaMutex);
5529 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
5532 return helper->BlockAndReturnOriginsForEviction(aLocks);
5535 void QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
5536 const nsACString& aGroup,
5537 const nsACString& aOrigin) {
5538 mQuotaMutex.AssertCurrentThreadOwns();
5539 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5541 GroupInfoPair* pair;
5542 mGroupInfoPairs.Get(aGroup, &pair);
5544 if (!pair) {
5545 return;
5548 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
5549 if (groupInfo) {
5550 groupInfo->LockedRemoveOriginInfo(aOrigin);
5552 if (!groupInfo->LockedHasOriginInfos()) {
5553 pair->LockedClearGroupInfo(aPersistenceType);
5555 if (!pair->LockedHasGroupInfos()) {
5556 mGroupInfoPairs.Remove(aGroup);
5562 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
5563 PersistenceType aPersistenceType, const nsACString& aGroup,
5564 const nsACString& aOrigin) {
5565 mQuotaMutex.AssertCurrentThreadOwns();
5566 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
5568 GroupInfoPair* pair;
5569 if (mGroupInfoPairs.Get(aGroup, &pair)) {
5570 RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
5571 if (groupInfo) {
5572 return groupInfo->LockedGetOriginInfo(aOrigin);
5576 return nullptr;
5579 void QuotaManager::CheckTemporaryStorageLimits() {
5580 AssertIsOnIOThread();
5582 nsTArray<OriginInfo*> doomedOriginInfos;
5584 MutexAutoLock lock(mQuotaMutex);
5586 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
5587 GroupInfoPair* pair = iter.UserData();
5589 MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
5590 MOZ_ASSERT(pair, "Null pointer!");
5592 uint64_t groupUsage = 0;
5594 RefPtr<GroupInfo> temporaryGroupInfo =
5595 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
5596 if (temporaryGroupInfo) {
5597 groupUsage += temporaryGroupInfo->mUsage;
5600 RefPtr<GroupInfo> defaultGroupInfo =
5601 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
5602 if (defaultGroupInfo) {
5603 groupUsage += defaultGroupInfo->mUsage;
5606 if (groupUsage > 0) {
5607 QuotaManager* quotaManager = QuotaManager::Get();
5608 MOZ_ASSERT(quotaManager, "Shouldn't be null!");
5610 if (groupUsage > quotaManager->GetGroupLimit()) {
5611 nsTArray<OriginInfo*> originInfos;
5612 if (temporaryGroupInfo) {
5613 originInfos.AppendElements(temporaryGroupInfo->mOriginInfos);
5615 if (defaultGroupInfo) {
5616 originInfos.AppendElements(defaultGroupInfo->mOriginInfos);
5618 originInfos.Sort(OriginInfoLRUComparator());
5620 for (uint32_t i = 0; i < originInfos.Length(); i++) {
5621 OriginInfo* originInfo = originInfos[i];
5622 if (originInfo->LockedPersisted()) {
5623 continue;
5626 doomedOriginInfos.AppendElement(originInfo);
5627 groupUsage -= originInfo->mUsage;
5629 if (groupUsage <= quotaManager->GetGroupLimit()) {
5630 break;
5637 uint64_t usage = 0;
5638 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
5639 usage += doomedOriginInfos[index]->mUsage;
5642 if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) {
5643 nsTArray<OriginInfo*> originInfos;
5645 for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
5646 GroupInfoPair* pair = iter.UserData();
5648 MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
5649 MOZ_ASSERT(pair, "Null pointer!");
5651 RefPtr<GroupInfo> groupInfo =
5652 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
5653 if (groupInfo) {
5654 originInfos.AppendElements(groupInfo->mOriginInfos);
5657 groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
5658 if (groupInfo) {
5659 originInfos.AppendElements(groupInfo->mOriginInfos);
5663 for (uint32_t index = originInfos.Length(); index > 0; index--) {
5664 if (doomedOriginInfos.Contains(originInfos[index - 1]) ||
5665 originInfos[index - 1]->LockedPersisted()) {
5666 originInfos.RemoveElementAt(index - 1);
5670 originInfos.Sort(OriginInfoLRUComparator());
5672 for (uint32_t i = 0; i < originInfos.Length(); i++) {
5673 if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) {
5674 originInfos.TruncateLength(i);
5675 break;
5678 usage += originInfos[i]->mUsage;
5681 doomedOriginInfos.AppendElements(originInfos);
5685 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
5686 OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
5688 #ifdef DEBUG
5690 MutexAutoLock lock(mQuotaMutex);
5691 MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
5693 #endif
5695 DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
5696 doomedOriginInfo->mOrigin);
5699 nsTArray<OriginParams> doomedOrigins;
5701 MutexAutoLock lock(mQuotaMutex);
5703 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
5704 OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
5706 PersistenceType persistenceType =
5707 doomedOriginInfo->mGroupInfo->mPersistenceType;
5708 nsCString group = doomedOriginInfo->mGroupInfo->mGroup;
5709 nsCString origin = doomedOriginInfo->mOrigin;
5710 LockedRemoveQuotaForOrigin(persistenceType, group, origin);
5712 #ifdef DEBUG
5713 doomedOriginInfos[index] = nullptr;
5714 #endif
5716 doomedOrigins.AppendElement(OriginParams(persistenceType, origin));
5720 for (const OriginParams& doomedOrigin : doomedOrigins) {
5721 OriginClearCompleted(doomedOrigin.mPersistenceType, doomedOrigin.mOrigin,
5722 Nullable<Client::Type>());
5725 if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
5726 // If disk space is still low after origin clear, notify storage pressure.
5727 NotifyStoragePressure(mTemporaryStorageUsage);
5731 void QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
5732 const nsACString& aOrigin) {
5733 nsCOMPtr<nsIFile> directory;
5734 nsresult rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
5735 getter_AddRefs(directory));
5736 NS_ENSURE_SUCCESS_VOID(rv);
5738 rv = directory->Remove(true);
5739 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
5740 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
5741 // This should never fail if we've closed all storage connections
5742 // correctly...
5743 NS_ERROR("Failed to remove directory!");
5747 void QuotaManager::FinalizeOriginEviction(
5748 nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) {
5749 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
5751 RefPtr<FinalizeOriginEvictionOp> op =
5752 new FinalizeOriginEvictionOp(mOwningThread, aLocks);
5754 if (IsOnIOThread()) {
5755 op->RunOnIOThreadImmediately();
5756 } else {
5757 op->Dispatch();
5761 void QuotaManager::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) {
5762 AssertIsOnBackgroundThread();
5764 auto quotaManager = static_cast<QuotaManager*>(aClosure);
5765 MOZ_ASSERT(quotaManager);
5767 NS_WARNING(
5768 "Some storage operations are taking longer than expected "
5769 "during shutdown and will be aborted!");
5771 // Abort all operations.
5772 for (RefPtr<Client>& client : quotaManager->mClients) {
5773 client->AbortOperations(VoidCString());
5777 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
5778 -> DirectoryLockTable& {
5779 switch (aPersistenceType) {
5780 case PERSISTENCE_TYPE_TEMPORARY:
5781 return mTemporaryDirectoryLockTable;
5782 case PERSISTENCE_TYPE_DEFAULT:
5783 return mDefaultDirectoryLockTable;
5785 case PERSISTENCE_TYPE_PERSISTENT:
5786 case PERSISTENCE_TYPE_INVALID:
5787 default:
5788 MOZ_CRASH("Bad persistence type value!");
5792 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
5793 AssertIsOnIOThread();
5794 MOZ_ASSERT(!aSanitizedOrigin.Equals(kChromeOrigin));
5796 bool valid;
5797 if (auto entry = mValidOrigins.LookupForAdd(aSanitizedOrigin)) {
5798 // We already parsed this sanitized origin string.
5799 valid = entry.Data();
5800 } else {
5801 nsCString spec;
5802 OriginAttributes attrs;
5803 OriginParser::ResultType result =
5804 OriginParser::ParseOrigin(aSanitizedOrigin, spec, &attrs);
5806 valid = result == OriginParser::ValidOrigin;
5807 entry.OrInsert([valid]() { return valid; });
5810 return valid;
5813 /*******************************************************************************
5814 * Local class implementations
5815 ******************************************************************************/
5817 OriginInfo::OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
5818 uint64_t aUsage, int64_t aAccessTime, bool aPersisted)
5819 : mGroupInfo(aGroupInfo),
5820 mOrigin(aOrigin),
5821 mUsage(aUsage),
5822 mAccessTime(aAccessTime),
5823 mPersisted(aPersisted) {
5824 MOZ_ASSERT(aGroupInfo);
5825 MOZ_ASSERT_IF(aPersisted,
5826 aGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
5828 MOZ_COUNT_CTOR(OriginInfo);
5831 void OriginInfo::LockedDecreaseUsage(int64_t aSize) {
5832 AssertCurrentThreadOwnsQuotaMutex();
5834 AssertNoUnderflow(mUsage, aSize);
5835 mUsage -= aSize;
5837 if (!LockedPersisted()) {
5838 AssertNoUnderflow(mGroupInfo->mUsage, aSize);
5839 mGroupInfo->mUsage -= aSize;
5842 QuotaManager* quotaManager = QuotaManager::Get();
5843 MOZ_ASSERT(quotaManager);
5845 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
5846 quotaManager->mTemporaryStorageUsage -= aSize;
5849 void OriginInfo::LockedPersist() {
5850 AssertCurrentThreadOwnsQuotaMutex();
5851 MOZ_ASSERT(mGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
5852 MOZ_ASSERT(!mPersisted);
5854 mPersisted = true;
5856 // Remove Usage from GroupInfo
5857 AssertNoUnderflow(mGroupInfo->mUsage, mUsage);
5858 mGroupInfo->mUsage -= mUsage;
5861 already_AddRefed<OriginInfo> GroupInfo::LockedGetOriginInfo(
5862 const nsACString& aOrigin) {
5863 AssertCurrentThreadOwnsQuotaMutex();
5865 for (RefPtr<OriginInfo>& originInfo : mOriginInfos) {
5866 if (originInfo->mOrigin == aOrigin) {
5867 RefPtr<OriginInfo> result = originInfo;
5868 return result.forget();
5872 return nullptr;
5875 void GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo) {
5876 AssertCurrentThreadOwnsQuotaMutex();
5878 NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
5879 "Replacing an existing entry!");
5880 mOriginInfos.AppendElement(aOriginInfo);
5882 if (!aOriginInfo->LockedPersisted()) {
5883 AssertNoOverflow(mUsage, aOriginInfo->mUsage);
5884 mUsage += aOriginInfo->mUsage;
5887 QuotaManager* quotaManager = QuotaManager::Get();
5888 MOZ_ASSERT(quotaManager);
5890 AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage);
5891 quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage;
5894 void GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) {
5895 AssertCurrentThreadOwnsQuotaMutex();
5897 for (uint32_t index = 0; index < mOriginInfos.Length(); index++) {
5898 if (mOriginInfos[index]->mOrigin == aOrigin) {
5899 if (!mOriginInfos[index]->LockedPersisted()) {
5900 AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage);
5901 mUsage -= mOriginInfos[index]->mUsage;
5904 QuotaManager* quotaManager = QuotaManager::Get();
5905 MOZ_ASSERT(quotaManager);
5907 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage,
5908 mOriginInfos[index]->mUsage);
5909 quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage;
5911 mOriginInfos.RemoveElementAt(index);
5913 return;
5918 void GroupInfo::LockedRemoveOriginInfos() {
5919 AssertCurrentThreadOwnsQuotaMutex();
5921 QuotaManager* quotaManager = QuotaManager::Get();
5922 MOZ_ASSERT(quotaManager);
5924 for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
5925 OriginInfo* originInfo = mOriginInfos[index - 1];
5927 if (!originInfo->LockedPersisted()) {
5928 AssertNoUnderflow(mUsage, originInfo->mUsage);
5929 mUsage -= originInfo->mUsage;
5932 AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage);
5933 quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
5935 mOriginInfos.RemoveElementAt(index - 1);
5939 RefPtr<GroupInfo>& GroupInfoPair::GetGroupInfoForPersistenceType(
5940 PersistenceType aPersistenceType) {
5941 switch (aPersistenceType) {
5942 case PERSISTENCE_TYPE_TEMPORARY:
5943 return mTemporaryStorageGroupInfo;
5944 case PERSISTENCE_TYPE_DEFAULT:
5945 return mDefaultStorageGroupInfo;
5947 case PERSISTENCE_TYPE_PERSISTENT:
5948 case PERSISTENCE_TYPE_INVALID:
5949 default:
5950 MOZ_CRASH("Bad persistence type value!");
5954 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
5955 uint64_t aMinSizeToBeFreed)
5956 : Runnable("dom::quota::CollectOriginsHelper"),
5957 mMinSizeToBeFreed(aMinSizeToBeFreed),
5958 mMutex(aMutex),
5959 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
5960 mSizeToBeFreed(0),
5961 mWaiting(true) {
5962 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
5963 mMutex.AssertCurrentThreadOwns();
5966 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
5967 nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) {
5968 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
5969 mMutex.AssertCurrentThreadOwns();
5971 while (mWaiting) {
5972 mCondVar.Wait();
5975 mLocks.SwapElements(aLocks);
5976 return mSizeToBeFreed;
5979 NS_IMETHODIMP
5980 CollectOriginsHelper::Run() {
5981 AssertIsOnBackgroundThread();
5983 QuotaManager* quotaManager = QuotaManager::Get();
5984 NS_ASSERTION(quotaManager, "Shouldn't be null!");
5986 // We use extra stack vars here to avoid race detector warnings (the same
5987 // memory accessed with and without the lock held).
5988 nsTArray<RefPtr<DirectoryLockImpl>> locks;
5989 uint64_t sizeToBeFreed =
5990 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
5992 MutexAutoLock lock(mMutex);
5994 NS_ASSERTION(mWaiting, "Huh?!");
5996 mLocks.SwapElements(locks);
5997 mSizeToBeFreed = sizeToBeFreed;
5998 mWaiting = false;
5999 mCondVar.Notify();
6001 return NS_OK;
6004 /*******************************************************************************
6005 * OriginOperationBase
6006 ******************************************************************************/
6008 NS_IMETHODIMP
6009 OriginOperationBase::Run() {
6010 nsresult rv;
6012 switch (mState) {
6013 case State_Initial: {
6014 rv = Init();
6015 break;
6018 case State_Initializing: {
6019 rv = InitOnMainThread();
6020 break;
6023 case State_FinishingInit: {
6024 rv = FinishInit();
6025 break;
6028 case State_CreatingQuotaManager: {
6029 rv = QuotaManagerOpen();
6030 break;
6033 case State_DirectoryOpenPending: {
6034 rv = DirectoryOpen();
6035 break;
6038 case State_DirectoryWorkOpen: {
6039 rv = DirectoryWork();
6040 break;
6043 case State_UnblockingOpen: {
6044 UnblockOpen();
6045 return NS_OK;
6048 default:
6049 MOZ_CRASH("Bad state!");
6052 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
6053 Finish(rv);
6056 return NS_OK;
6059 nsresult OriginOperationBase::DirectoryOpen() {
6060 AssertIsOnOwningThread();
6061 MOZ_ASSERT(mState == State_DirectoryOpenPending);
6063 QuotaManager* quotaManager = QuotaManager::Get();
6064 if (NS_WARN_IF(!quotaManager)) {
6065 return NS_ERROR_FAILURE;
6068 // Must set this before dispatching otherwise we will race with the IO thread.
6069 AdvanceState();
6071 nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
6072 if (NS_WARN_IF(NS_FAILED(rv))) {
6073 return NS_ERROR_FAILURE;
6076 return NS_OK;
6079 void OriginOperationBase::Finish(nsresult aResult) {
6080 if (NS_SUCCEEDED(mResultCode)) {
6081 mResultCode = aResult;
6084 // Must set mState before dispatching otherwise we will race with the main
6085 // thread.
6086 mState = State_UnblockingOpen;
6088 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
6091 nsresult OriginOperationBase::Init() {
6092 AssertIsOnOwningThread();
6093 MOZ_ASSERT(mState == State_Initial);
6095 AdvanceState();
6097 if (mNeedsMainThreadInit) {
6098 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
6099 } else {
6100 AdvanceState();
6101 MOZ_ALWAYS_SUCCEEDS(Run());
6104 return NS_OK;
6107 nsresult OriginOperationBase::InitOnMainThread() {
6108 MOZ_ASSERT(NS_IsMainThread());
6109 MOZ_ASSERT(mState == State_Initializing);
6111 nsresult rv = DoInitOnMainThread();
6112 if (NS_WARN_IF(NS_FAILED(rv))) {
6113 return rv;
6116 AdvanceState();
6118 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
6120 return NS_OK;
6123 nsresult OriginOperationBase::FinishInit() {
6124 AssertIsOnOwningThread();
6125 MOZ_ASSERT(mState == State_FinishingInit);
6127 if (QuotaManager::IsShuttingDown()) {
6128 return NS_ERROR_FAILURE;
6131 AdvanceState();
6133 if (mNeedsQuotaManagerInit && !QuotaManager::Get()) {
6134 QuotaManager::GetOrCreate(this);
6135 } else {
6136 Open();
6139 return NS_OK;
6142 nsresult OriginOperationBase::QuotaManagerOpen() {
6143 AssertIsOnOwningThread();
6144 MOZ_ASSERT(mState == State_CreatingQuotaManager);
6146 if (NS_WARN_IF(!QuotaManager::Get())) {
6147 return NS_ERROR_FAILURE;
6150 Open();
6152 return NS_OK;
6155 nsresult OriginOperationBase::DirectoryWork() {
6156 AssertIsOnIOThread();
6157 MOZ_ASSERT(mState == State_DirectoryWorkOpen);
6159 QuotaManager* quotaManager = QuotaManager::Get();
6160 if (NS_WARN_IF(!quotaManager)) {
6161 return NS_ERROR_FAILURE;
6164 nsresult rv;
6166 if (mNeedsQuotaManagerInit) {
6167 rv = quotaManager->EnsureStorageIsInitialized();
6168 if (NS_WARN_IF(NS_FAILED(rv))) {
6169 return rv;
6173 rv = DoDirectoryWork(quotaManager);
6174 if (NS_WARN_IF(NS_FAILED(rv))) {
6175 return rv;
6178 // Must set mState before dispatching otherwise we will race with the owning
6179 // thread.
6180 AdvanceState();
6182 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
6184 return NS_OK;
6187 void FinalizeOriginEvictionOp::Dispatch() {
6188 MOZ_ASSERT(!NS_IsMainThread());
6189 MOZ_ASSERT(GetState() == State_Initial);
6191 SetState(State_DirectoryOpenPending);
6193 MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
6196 void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() {
6197 AssertIsOnIOThread();
6198 MOZ_ASSERT(GetState() == State_Initial);
6200 SetState(State_DirectoryWorkOpen);
6202 MOZ_ALWAYS_SUCCEEDS(this->Run());
6205 void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); }
6207 nsresult FinalizeOriginEvictionOp::DoDirectoryWork(
6208 QuotaManager* aQuotaManager) {
6209 AssertIsOnIOThread();
6211 AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER);
6213 for (RefPtr<DirectoryLockImpl>& lock : mLocks) {
6214 aQuotaManager->OriginClearCompleted(lock->GetPersistenceType().Value(),
6215 lock->GetOriginScope().GetOrigin(),
6216 Nullable<Client::Type>());
6219 return NS_OK;
6222 void FinalizeOriginEvictionOp::UnblockOpen() {
6223 AssertIsOnOwningThread();
6224 MOZ_ASSERT(GetState() == State_UnblockingOpen);
6226 #ifdef DEBUG
6227 NoteActorDestroyed();
6228 #endif
6230 mLocks.Clear();
6232 AdvanceState();
6235 NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable)
6237 void NormalOriginOperationBase::Open() {
6238 AssertIsOnOwningThread();
6239 MOZ_ASSERT(GetState() == State_CreatingQuotaManager);
6240 MOZ_ASSERT(QuotaManager::Get());
6242 AdvanceState();
6244 QuotaManager::Get()->OpenDirectoryInternal(mPersistenceType, mOriginScope,
6245 mClientType, mExclusive, this);
6248 void NormalOriginOperationBase::UnblockOpen() {
6249 AssertIsOnOwningThread();
6250 MOZ_ASSERT(GetState() == State_UnblockingOpen);
6252 SendResults();
6254 mDirectoryLock = nullptr;
6256 AdvanceState();
6259 void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) {
6260 AssertIsOnOwningThread();
6261 MOZ_ASSERT(aLock);
6262 MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
6263 MOZ_ASSERT(!mDirectoryLock);
6265 mDirectoryLock = aLock;
6267 nsresult rv = DirectoryOpen();
6268 if (NS_WARN_IF(NS_FAILED(rv))) {
6269 Finish(rv);
6270 return;
6274 void NormalOriginOperationBase::DirectoryLockFailed() {
6275 AssertIsOnOwningThread();
6276 MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
6277 MOZ_ASSERT(!mDirectoryLock);
6279 Finish(NS_ERROR_FAILURE);
6282 nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
6283 AssertIsOnIOThread();
6284 MOZ_ASSERT(!mPersistenceType.IsNull());
6285 MOZ_ASSERT(mOriginScope.IsOrigin());
6287 AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER);
6289 nsCOMPtr<nsIFile> file;
6290 nsresult rv = aQuotaManager->GetDirectoryForOrigin(
6291 mPersistenceType.Value(), mOriginScope.GetOrigin(), getter_AddRefs(file));
6292 if (NS_WARN_IF(NS_FAILED(rv))) {
6293 return rv;
6296 rv = file->Append(NS_LITERAL_STRING(METADATA_V2_FILE_NAME));
6297 if (NS_WARN_IF(NS_FAILED(rv))) {
6298 return rv;
6301 nsCOMPtr<nsIBinaryOutputStream> stream;
6302 rv = GetBinaryOutputStream(file, kUpdateFileFlag, getter_AddRefs(stream));
6303 if (NS_WARN_IF(NS_FAILED(rv))) {
6304 return rv;
6307 // The origin directory may not exist anymore.
6308 if (stream) {
6309 rv = stream->Write64(mTimestamp);
6310 if (NS_WARN_IF(NS_FAILED(rv))) {
6311 return rv;
6315 return NS_OK;
6318 void SaveOriginAccessTimeOp::SendResults() {
6319 #ifdef DEBUG
6320 NoteActorDestroyed();
6321 #endif
6324 NS_IMETHODIMP
6325 StoragePressureRunnable::Run() {
6326 MOZ_ASSERT(NS_IsMainThread());
6328 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
6329 if (NS_WARN_IF(!obsSvc)) {
6330 return NS_ERROR_FAILURE;
6333 nsCOMPtr<nsISupportsPRUint64> wrapper =
6334 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
6335 if (NS_WARN_IF(!wrapper)) {
6336 return NS_ERROR_FAILURE;
6339 wrapper->SetData(mUsage);
6341 obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
6343 return NS_OK;
6346 /*******************************************************************************
6347 * Quota
6348 ******************************************************************************/
6350 Quota::Quota()
6351 #ifdef DEBUG
6352 : mActorDestroyed(false)
6353 #endif
6357 Quota::~Quota() { MOZ_ASSERT(mActorDestroyed); }
6359 void Quota::StartIdleMaintenance() {
6360 AssertIsOnBackgroundThread();
6361 MOZ_ASSERT(!QuotaManager::IsShuttingDown());
6363 QuotaManager* quotaManager = QuotaManager::Get();
6364 if (NS_WARN_IF(!quotaManager)) {
6365 return;
6368 quotaManager->StartIdleMaintenance();
6371 void Quota::ActorDestroy(ActorDestroyReason aWhy) {
6372 AssertIsOnBackgroundThread();
6373 #ifdef DEBUG
6374 MOZ_ASSERT(!mActorDestroyed);
6375 mActorDestroyed = true;
6376 #endif
6379 PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(
6380 const UsageRequestParams& aParams) {
6381 AssertIsOnBackgroundThread();
6382 MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
6384 RefPtr<QuotaUsageRequestBase> actor;
6386 switch (aParams.type()) {
6387 case UsageRequestParams::TAllUsageParams:
6388 actor = new GetUsageOp(aParams);
6389 break;
6391 case UsageRequestParams::TOriginUsageParams:
6392 actor = new GetOriginUsageOp(aParams);
6393 break;
6395 default:
6396 MOZ_CRASH("Should never get here!");
6399 MOZ_ASSERT(actor);
6401 // Transfer ownership to IPDL.
6402 return actor.forget().take();
6405 mozilla::ipc::IPCResult Quota::RecvPQuotaUsageRequestConstructor(
6406 PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) {
6407 AssertIsOnBackgroundThread();
6408 MOZ_ASSERT(aActor);
6409 MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
6411 auto* op = static_cast<QuotaUsageRequestBase*>(aActor);
6413 if (NS_WARN_IF(!op->Init(this))) {
6414 return IPC_FAIL_NO_REASON(this);
6417 op->RunImmediately();
6418 return IPC_OK();
6421 bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) {
6422 AssertIsOnBackgroundThread();
6423 MOZ_ASSERT(aActor);
6425 // Transfer ownership back from IPDL.
6426 RefPtr<QuotaUsageRequestBase> actor =
6427 dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor));
6428 return true;
6431 PQuotaRequestParent* Quota::AllocPQuotaRequestParent(
6432 const RequestParams& aParams) {
6433 AssertIsOnBackgroundThread();
6434 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
6436 if (aParams.type() == RequestParams::TClearDataParams) {
6437 PBackgroundParent* actor = Manager();
6438 MOZ_ASSERT(actor);
6440 if (BackgroundParent::IsOtherProcessActor(actor)) {
6441 ASSERT_UNLESS_FUZZING();
6442 return nullptr;
6446 RefPtr<QuotaRequestBase> actor;
6448 switch (aParams.type()) {
6449 case RequestParams::TInitParams:
6450 actor = new InitOp();
6451 break;
6453 case RequestParams::TInitTemporaryStorageParams:
6454 actor = new InitTemporaryStorageOp();
6455 break;
6457 case RequestParams::TInitOriginParams:
6458 actor = new InitOriginOp(aParams);
6459 break;
6461 case RequestParams::TClearOriginParams:
6462 case RequestParams::TResetOriginParams:
6463 actor = new ClearOriginOp(aParams);
6464 break;
6466 case RequestParams::TClearDataParams:
6467 actor = new ClearDataOp(aParams);
6468 break;
6470 case RequestParams::TClearAllParams:
6471 actor = new ResetOrClearOp(/* aClear */ true);
6472 break;
6474 case RequestParams::TResetAllParams:
6475 actor = new ResetOrClearOp(/* aClear */ false);
6476 break;
6478 case RequestParams::TPersistedParams:
6479 actor = new PersistedOp(aParams);
6480 break;
6482 case RequestParams::TPersistParams:
6483 actor = new PersistOp(aParams);
6484 break;
6486 default:
6487 MOZ_CRASH("Should never get here!");
6490 MOZ_ASSERT(actor);
6492 // Transfer ownership to IPDL.
6493 return actor.forget().take();
6496 mozilla::ipc::IPCResult Quota::RecvPQuotaRequestConstructor(
6497 PQuotaRequestParent* aActor, const RequestParams& aParams) {
6498 AssertIsOnBackgroundThread();
6499 MOZ_ASSERT(aActor);
6500 MOZ_ASSERT(aParams.type() != RequestParams::T__None);
6502 auto* op = static_cast<QuotaRequestBase*>(aActor);
6504 if (NS_WARN_IF(!op->Init(this))) {
6505 return IPC_FAIL_NO_REASON(this);
6508 op->RunImmediately();
6509 return IPC_OK();
6512 bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) {
6513 AssertIsOnBackgroundThread();
6514 MOZ_ASSERT(aActor);
6516 // Transfer ownership back from IPDL.
6517 RefPtr<QuotaRequestBase> actor =
6518 dont_AddRef(static_cast<QuotaRequestBase*>(aActor));
6519 return true;
6522 mozilla::ipc::IPCResult Quota::RecvStartIdleMaintenance() {
6523 AssertIsOnBackgroundThread();
6525 PBackgroundParent* actor = Manager();
6526 MOZ_ASSERT(actor);
6528 if (BackgroundParent::IsOtherProcessActor(actor)) {
6529 ASSERT_UNLESS_FUZZING();
6530 return IPC_FAIL_NO_REASON(this);
6533 if (QuotaManager::IsShuttingDown()) {
6534 return IPC_OK();
6537 QuotaManager* quotaManager = QuotaManager::Get();
6538 if (!quotaManager) {
6539 nsCOMPtr<nsIRunnable> callback =
6540 NewRunnableMethod("dom::quota::Quota::StartIdleMaintenance", this,
6541 &Quota::StartIdleMaintenance);
6543 QuotaManager::GetOrCreate(callback);
6544 return IPC_OK();
6547 quotaManager->StartIdleMaintenance();
6549 return IPC_OK();
6552 mozilla::ipc::IPCResult Quota::RecvStopIdleMaintenance() {
6553 AssertIsOnBackgroundThread();
6555 PBackgroundParent* actor = Manager();
6556 MOZ_ASSERT(actor);
6558 if (BackgroundParent::IsOtherProcessActor(actor)) {
6559 ASSERT_UNLESS_FUZZING();
6560 return IPC_FAIL_NO_REASON(this);
6563 if (QuotaManager::IsShuttingDown()) {
6564 return IPC_OK();
6567 QuotaManager* quotaManager = QuotaManager::Get();
6568 if (!quotaManager) {
6569 return IPC_OK();
6572 quotaManager->StopIdleMaintenance();
6574 return IPC_OK();
6577 bool QuotaUsageRequestBase::Init(Quota* aQuota) {
6578 AssertIsOnOwningThread();
6579 MOZ_ASSERT(aQuota);
6581 mNeedsQuotaManagerInit = true;
6583 return true;
6586 nsresult QuotaUsageRequestBase::GetUsageForOrigin(
6587 QuotaManager* aQuotaManager, PersistenceType aPersistenceType,
6588 const nsACString& aGroup, const nsACString& aOrigin,
6589 UsageInfo* aUsageInfo) {
6590 AssertIsOnIOThread();
6591 MOZ_ASSERT(aQuotaManager);
6592 MOZ_ASSERT(aUsageInfo);
6593 MOZ_ASSERT(aUsageInfo->TotalUsage() == 0);
6595 nsCOMPtr<nsIFile> directory;
6596 nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin,
6597 getter_AddRefs(directory));
6598 NS_ENSURE_SUCCESS(rv, rv);
6600 bool exists;
6601 rv = directory->Exists(&exists);
6602 NS_ENSURE_SUCCESS(rv, rv);
6604 // If the directory exists then enumerate all the files inside, adding up
6605 // the sizes to get the final usage statistic.
6606 if (exists && !mCanceled) {
6607 bool initialized;
6609 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
6610 initialized = aQuotaManager->IsOriginInitialized(aOrigin);
6611 } else {
6612 initialized = aQuotaManager->IsTemporaryStorageInitialized();
6615 nsCOMPtr<nsIDirectoryEnumerator> entries;
6616 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
6617 NS_ENSURE_SUCCESS(rv, rv);
6619 nsCOMPtr<nsIFile> file;
6620 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
6621 file && !mCanceled) {
6622 bool isDirectory;
6623 rv = file->IsDirectory(&isDirectory);
6624 if (NS_WARN_IF(NS_FAILED(rv))) {
6625 return rv;
6628 nsString leafName;
6629 rv = file->GetLeafName(leafName);
6630 if (NS_WARN_IF(NS_FAILED(rv))) {
6631 return rv;
6634 if (!isDirectory) {
6635 // We are maintaining existing behavior here (failing if the origin is
6636 // not yet initialized or just continuing otherwise).
6637 // This can possibly be used by developers to add temporary backups into
6638 // origin directories without losing get usage functionality.
6639 if (IsOriginMetadata(leafName)) {
6640 continue;
6643 if (IsTempMetadata(leafName)) {
6644 if (!initialized) {
6645 rv = file->Remove(/* recursive */ false);
6646 if (NS_WARN_IF(NS_FAILED(rv))) {
6647 return rv;
6651 continue;
6654 if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
6655 continue;
6658 UNKNOWN_FILE_WARNING(leafName);
6659 if (!initialized) {
6660 return NS_ERROR_UNEXPECTED;
6662 continue;
6665 Client::Type clientType;
6666 rv = Client::TypeFromText(leafName, clientType);
6667 if (NS_FAILED(rv)) {
6668 UNKNOWN_FILE_WARNING(leafName);
6669 if (!initialized) {
6670 return NS_ERROR_UNEXPECTED;
6672 continue;
6675 Client* client = aQuotaManager->GetClient(clientType);
6676 MOZ_ASSERT(client);
6678 if (initialized) {
6679 rv = client->GetUsageForOrigin(aPersistenceType, aGroup, aOrigin,
6680 mCanceled, aUsageInfo);
6681 } else {
6682 rv = client->InitOrigin(aPersistenceType, aGroup, aOrigin, mCanceled,
6683 aUsageInfo);
6685 NS_ENSURE_SUCCESS(rv, rv);
6689 return NS_OK;
6692 void QuotaUsageRequestBase::SendResults() {
6693 AssertIsOnOwningThread();
6695 if (IsActorDestroyed()) {
6696 if (NS_SUCCEEDED(mResultCode)) {
6697 mResultCode = NS_ERROR_FAILURE;
6699 } else {
6700 if (mCanceled) {
6701 mResultCode = NS_ERROR_FAILURE;
6704 UsageRequestResponse response;
6706 if (NS_SUCCEEDED(mResultCode)) {
6707 GetResponse(response);
6708 } else {
6709 response = mResultCode;
6712 Unused << PQuotaUsageRequestParent::Send__delete__(this, response);
6716 void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
6717 AssertIsOnOwningThread();
6719 NoteActorDestroyed();
6722 mozilla::ipc::IPCResult QuotaUsageRequestBase::RecvCancel() {
6723 AssertIsOnOwningThread();
6725 if (mCanceled.exchange(true)) {
6726 NS_WARNING("Canceled more than once?!");
6727 return IPC_FAIL_NO_REASON(this);
6730 return IPC_OK();
6733 GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
6734 : mGetAll(aParams.get_AllUsageParams().getAll()) {
6735 AssertIsOnOwningThread();
6736 MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
6739 nsresult GetUsageOp::TraverseRepository(QuotaManager* aQuotaManager,
6740 PersistenceType aPersistenceType) {
6741 AssertIsOnIOThread();
6742 MOZ_ASSERT(aQuotaManager);
6744 nsCOMPtr<nsIFile> directory;
6745 nsresult rv = NS_NewLocalFile(aQuotaManager->GetStoragePath(aPersistenceType),
6746 false, getter_AddRefs(directory));
6747 if (NS_WARN_IF(NS_FAILED(rv))) {
6748 return rv;
6751 bool exists;
6752 rv = directory->Exists(&exists);
6753 if (NS_WARN_IF(NS_FAILED(rv))) {
6754 return rv;
6757 if (!exists) {
6758 return NS_OK;
6761 nsCOMPtr<nsIDirectoryEnumerator> entries;
6762 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
6763 if (NS_WARN_IF(NS_FAILED(rv))) {
6764 return rv;
6767 bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
6769 nsCOMPtr<nsIFile> originDir;
6770 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(originDir)))) &&
6771 originDir && !mCanceled) {
6772 bool isDirectory;
6773 rv = originDir->IsDirectory(&isDirectory);
6774 if (NS_WARN_IF(NS_FAILED(rv))) {
6775 return rv;
6778 if (!isDirectory) {
6779 nsString leafName;
6780 rv = originDir->GetLeafName(leafName);
6781 if (NS_WARN_IF(NS_FAILED(rv))) {
6782 return rv;
6785 // Unknown files during getting usages are allowed. Just warn if we find
6786 // them.
6787 if (!IsOSMetadata(leafName)) {
6788 UNKNOWN_FILE_WARNING(leafName);
6790 continue;
6793 int64_t timestamp;
6794 bool persisted;
6795 nsCString suffix;
6796 nsCString group;
6797 nsCString origin;
6798 rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
6799 originDir, persistent, &timestamp, &persisted, suffix, group, origin);
6800 if (NS_WARN_IF(NS_FAILED(rv))) {
6801 return rv;
6804 if (!mGetAll && aQuotaManager->IsOriginInternal(origin)) {
6805 continue;
6808 OriginUsage* originUsage;
6810 // We can't store pointers to OriginUsage objects in the hashtable
6811 // since AppendElement() reallocates its internal array buffer as number
6812 // of elements grows.
6813 uint32_t index;
6814 if (mOriginUsagesIndex.Get(origin, &index)) {
6815 originUsage = &mOriginUsages[index];
6816 } else {
6817 index = mOriginUsages.Length();
6819 originUsage = mOriginUsages.AppendElement();
6821 originUsage->origin() = origin;
6822 originUsage->persisted() = false;
6823 originUsage->usage() = 0;
6825 mOriginUsagesIndex.Put(origin, index);
6828 if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
6829 originUsage->persisted() = persisted;
6832 originUsage->lastAccessed() = timestamp;
6834 UsageInfo usageInfo;
6835 rv = GetUsageForOrigin(aQuotaManager, aPersistenceType, group, origin,
6836 &usageInfo);
6837 if (NS_WARN_IF(NS_FAILED(rv))) {
6838 return rv;
6841 originUsage->usage() = originUsage->usage() + usageInfo.TotalUsage();
6843 if (NS_WARN_IF(NS_FAILED(rv))) {
6844 return rv;
6847 return NS_OK;
6850 nsresult GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
6851 AssertIsOnIOThread();
6853 AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
6855 nsresult rv;
6857 for (const PersistenceType type : kAllPersistenceTypes) {
6858 rv = TraverseRepository(aQuotaManager, type);
6859 if (NS_WARN_IF(NS_FAILED(rv))) {
6860 return rv;
6864 return NS_OK;
6867 void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
6868 AssertIsOnOwningThread();
6870 aResponse = AllUsageResponse();
6872 if (!mOriginUsages.IsEmpty()) {
6873 nsTArray<OriginUsage>& originUsages =
6874 aResponse.get_AllUsageResponse().originUsages();
6876 mOriginUsages.SwapElements(originUsages);
6880 GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
6881 : mParams(aParams.get_OriginUsageParams()),
6882 mGetGroupUsage(aParams.get_OriginUsageParams().getGroupUsage()) {
6883 AssertIsOnOwningThread();
6884 MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
6887 bool GetOriginUsageOp::Init(Quota* aQuota) {
6888 AssertIsOnOwningThread();
6889 MOZ_ASSERT(aQuota);
6891 if (NS_WARN_IF(!QuotaUsageRequestBase::Init(aQuota))) {
6892 return false;
6895 mNeedsMainThreadInit = true;
6897 return true;
6900 nsresult GetOriginUsageOp::DoInitOnMainThread() {
6901 MOZ_ASSERT(NS_IsMainThread());
6902 MOZ_ASSERT(GetState() == State_Initializing);
6903 MOZ_ASSERT(mNeedsMainThreadInit);
6905 const PrincipalInfo& principalInfo = mParams.principalInfo();
6907 nsresult rv;
6908 nsCOMPtr<nsIPrincipal> principal =
6909 PrincipalInfoToPrincipal(principalInfo, &rv);
6910 if (NS_WARN_IF(NS_FAILED(rv))) {
6911 return rv;
6914 // Figure out which origin we're dealing with.
6915 nsCString origin;
6916 rv =
6917 QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
6918 if (NS_WARN_IF(NS_FAILED(rv))) {
6919 return rv;
6922 mOriginScope.SetFromOrigin(origin);
6924 return NS_OK;
6927 nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
6928 AssertIsOnIOThread();
6929 MOZ_ASSERT(mUsageInfo.TotalUsage() == 0);
6931 AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
6933 nsresult rv;
6935 if (mGetGroupUsage) {
6936 // Ensure temporary storage is initialized first. It will initialize all
6937 // origins for temporary storage including origins belonging to our group by
6938 // traversing the repositories. EnsureStorageIsInitialized is needed before
6939 // EnsureTemporaryStorageIsInitialized.
6940 rv = aQuotaManager->EnsureStorageIsInitialized();
6941 if (NS_WARN_IF(NS_FAILED(rv))) {
6942 return rv;
6945 rv = aQuotaManager->EnsureTemporaryStorageIsInitialized();
6946 if (NS_WARN_IF(NS_FAILED(rv))) {
6947 return rv;
6950 // Get cached usage and limit (the method doesn't have to stat any files).
6951 aQuotaManager->GetGroupUsageAndLimit(mGroup, &mUsageInfo);
6953 return NS_OK;
6956 // Add all the persistent/temporary/default storage files we care about.
6957 for (const PersistenceType type : kAllPersistenceTypes) {
6958 UsageInfo usageInfo;
6959 rv = GetUsageForOrigin(aQuotaManager, type, mGroup,
6960 mOriginScope.GetOrigin(), &usageInfo);
6961 if (NS_WARN_IF(NS_FAILED(rv))) {
6962 return rv;
6965 mUsageInfo.Append(usageInfo);
6968 return NS_OK;
6971 void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) {
6972 AssertIsOnOwningThread();
6974 OriginUsageResponse usageResponse;
6976 // We'll get the group usage when mGetGroupUsage is true and get the
6977 // origin usage when mGetGroupUsage is false.
6978 usageResponse.usage() = mUsageInfo.TotalUsage();
6980 if (mGetGroupUsage) {
6981 usageResponse.limit() = mUsageInfo.Limit();
6982 } else {
6983 usageResponse.fileUsage() = mUsageInfo.FileUsage();
6986 aResponse = usageResponse;
6989 bool QuotaRequestBase::Init(Quota* aQuota) {
6990 AssertIsOnOwningThread();
6991 MOZ_ASSERT(aQuota);
6993 mNeedsQuotaManagerInit = true;
6995 return true;
6998 void QuotaRequestBase::SendResults() {
6999 AssertIsOnOwningThread();
7001 if (IsActorDestroyed()) {
7002 if (NS_SUCCEEDED(mResultCode)) {
7003 mResultCode = NS_ERROR_FAILURE;
7005 } else {
7006 RequestResponse response;
7008 if (NS_SUCCEEDED(mResultCode)) {
7009 GetResponse(response);
7010 } else {
7011 response = mResultCode;
7014 Unused << PQuotaRequestParent::Send__delete__(this, response);
7018 void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
7019 AssertIsOnOwningThread();
7021 NoteActorDestroyed();
7024 nsresult InitOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7025 AssertIsOnIOThread();
7027 AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER);
7029 aQuotaManager->AssertStorageIsInitialized();
7031 return NS_OK;
7034 void InitOp::GetResponse(RequestResponse& aResponse) {
7035 AssertIsOnOwningThread();
7037 aResponse = InitResponse();
7040 nsresult InitTemporaryStorageOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7041 AssertIsOnIOThread();
7043 AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER);
7045 aQuotaManager->AssertStorageIsInitialized();
7047 nsresult rv = aQuotaManager->EnsureTemporaryStorageIsInitialized();
7048 if (NS_WARN_IF(NS_FAILED(rv))) {
7049 return rv;
7052 return NS_OK;
7055 void InitTemporaryStorageOp::GetResponse(RequestResponse& aResponse) {
7056 AssertIsOnOwningThread();
7058 aResponse = InitTemporaryStorageResponse();
7061 InitOriginOp::InitOriginOp(const RequestParams& aParams)
7062 : QuotaRequestBase(/* aExclusive */ false),
7063 mParams(aParams.get_InitOriginParams()),
7064 mCreated(false) {
7065 AssertIsOnOwningThread();
7066 MOZ_ASSERT(aParams.type() == RequestParams::TInitOriginParams);
7069 bool InitOriginOp::Init(Quota* aQuota) {
7070 AssertIsOnOwningThread();
7071 MOZ_ASSERT(aQuota);
7073 if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
7074 return false;
7077 MOZ_ASSERT(mParams.persistenceType() != PERSISTENCE_TYPE_INVALID);
7079 mPersistenceType.SetValue(mParams.persistenceType());
7081 mNeedsMainThreadInit = true;
7083 return true;
7086 nsresult InitOriginOp::DoInitOnMainThread() {
7087 MOZ_ASSERT(NS_IsMainThread());
7088 MOZ_ASSERT(GetState() == State_Initializing);
7089 MOZ_ASSERT(mNeedsMainThreadInit);
7091 const PrincipalInfo& principalInfo = mParams.principalInfo();
7093 nsresult rv;
7094 nsCOMPtr<nsIPrincipal> principal =
7095 PrincipalInfoToPrincipal(principalInfo, &rv);
7096 if (NS_WARN_IF(NS_FAILED(rv))) {
7097 return rv;
7100 // Figure out which origin we're dealing with.
7101 nsCString origin;
7102 rv =
7103 QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
7104 if (NS_WARN_IF(NS_FAILED(rv))) {
7105 return rv;
7108 mOriginScope.SetFromOrigin(origin);
7110 return NS_OK;
7113 nsresult InitOriginOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7114 AssertIsOnIOThread();
7115 MOZ_ASSERT(!mPersistenceType.IsNull());
7117 AUTO_PROFILER_LABEL("InitOriginOp::DoDirectoryWork", OTHER);
7119 nsCOMPtr<nsIFile> directory;
7120 bool created;
7121 nsresult rv = aQuotaManager->EnsureOriginIsInitializedInternal(
7122 mPersistenceType.Value(), mSuffix, mGroup, mOriginScope.GetOrigin(),
7123 /* aCreateIfNotExists */ true, getter_AddRefs(directory), &created);
7124 if (NS_WARN_IF(NS_FAILED(rv))) {
7125 return rv;
7128 mCreated = created;
7130 return NS_OK;
7133 void InitOriginOp::GetResponse(RequestResponse& aResponse) {
7134 AssertIsOnOwningThread();
7136 InitOriginResponse response;
7138 response.created() = mCreated;
7140 aResponse = response;
7143 void ResetOrClearOp::DeleteFiles(QuotaManager* aQuotaManager) {
7144 AssertIsOnIOThread();
7145 MOZ_ASSERT(aQuotaManager);
7147 nsresult rv = aQuotaManager->AboutToClearOrigins(Nullable<PersistenceType>(),
7148 OriginScope::FromNull(),
7149 Nullable<Client::Type>());
7150 if (NS_WARN_IF(NS_FAILED(rv))) {
7151 return;
7154 nsCOMPtr<nsIFile> directory;
7155 rv = NS_NewLocalFile(aQuotaManager->GetStoragePath(), false,
7156 getter_AddRefs(directory));
7157 if (NS_WARN_IF(NS_FAILED(rv))) {
7158 return;
7161 rv = directory->Remove(true);
7162 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
7163 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
7164 // This should never fail if we've closed all storage connections
7165 // correctly...
7166 MOZ_ASSERT(false, "Failed to remove storage directory!");
7169 nsCOMPtr<nsIFile> storageFile;
7170 rv = NS_NewLocalFile(aQuotaManager->GetBasePath(), false,
7171 getter_AddRefs(storageFile));
7172 if (NS_WARN_IF(NS_FAILED(rv))) {
7173 return;
7176 rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME));
7177 if (NS_WARN_IF(NS_FAILED(rv))) {
7178 return;
7181 rv = storageFile->Remove(true);
7182 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
7183 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
7184 // This should never fail if we've closed the storage connection
7185 // correctly...
7186 MOZ_ASSERT(false, "Failed to remove storage file!");
7190 nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7191 AssertIsOnIOThread();
7193 AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER);
7195 if (mClear) {
7196 DeleteFiles(aQuotaManager);
7199 aQuotaManager->RemoveQuota();
7201 aQuotaManager->ResetOrClearCompleted();
7203 return NS_OK;
7206 void ResetOrClearOp::GetResponse(RequestResponse& aResponse) {
7207 AssertIsOnOwningThread();
7208 if (mClear) {
7209 aResponse = ClearAllResponse();
7210 } else {
7211 aResponse = ResetAllResponse();
7215 void ClearRequestBase::DeleteFiles(QuotaManager* aQuotaManager,
7216 PersistenceType aPersistenceType) {
7217 AssertIsOnIOThread();
7218 MOZ_ASSERT(aQuotaManager);
7220 nsresult rv = aQuotaManager->AboutToClearOrigins(
7221 Nullable<PersistenceType>(aPersistenceType), mOriginScope, mClientType);
7222 if (NS_WARN_IF(NS_FAILED(rv))) {
7223 return;
7226 nsCOMPtr<nsIFile> directory;
7227 rv = NS_NewLocalFile(aQuotaManager->GetStoragePath(aPersistenceType), false,
7228 getter_AddRefs(directory));
7229 if (NS_WARN_IF(NS_FAILED(rv))) {
7230 return;
7233 nsCOMPtr<nsIDirectoryEnumerator> entries;
7234 if (NS_WARN_IF(
7235 NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries)))) ||
7236 !entries) {
7237 return;
7240 OriginScope originScope = mOriginScope.Clone();
7241 if (originScope.IsOrigin()) {
7242 nsCString originSanitized(originScope.GetOrigin());
7243 SanitizeOriginString(originSanitized);
7244 originScope.SetOrigin(originSanitized);
7245 } else if (originScope.IsPrefix()) {
7246 nsCString originNoSuffixSanitized(originScope.GetOriginNoSuffix());
7247 SanitizeOriginString(originNoSuffixSanitized);
7248 originScope.SetOriginNoSuffix(originNoSuffixSanitized);
7251 nsCOMPtr<nsIFile> file;
7252 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
7253 file) {
7254 bool isDirectory;
7255 rv = file->IsDirectory(&isDirectory);
7256 if (NS_WARN_IF(NS_FAILED(rv))) {
7257 return;
7260 nsString leafName;
7261 rv = file->GetLeafName(leafName);
7262 if (NS_WARN_IF(NS_FAILED(rv))) {
7263 return;
7266 if (!isDirectory) {
7267 // Unknown files during clearing are allowed. Just warn if we find them.
7268 if (!IsOSMetadata(leafName)) {
7269 UNKNOWN_FILE_WARNING(leafName);
7271 continue;
7274 // Skip the origin directory if it doesn't match the pattern.
7275 if (!originScope.Matches(
7276 OriginScope::FromOrigin(NS_ConvertUTF16toUTF8(leafName)))) {
7277 continue;
7280 bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
7282 int64_t timestamp;
7283 nsCString suffix;
7284 nsCString group;
7285 nsCString origin;
7286 bool persisted;
7287 rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
7288 file, persistent, &timestamp, &persisted, suffix, group, origin);
7289 if (NS_WARN_IF(NS_FAILED(rv))) {
7290 return;
7293 UsageInfo usageInfo;
7295 if (!mClientType.IsNull()) {
7296 Client::Type clientType = mClientType.Value();
7298 nsAutoString clientDirectoryName;
7299 rv = Client::TypeToText(clientType, clientDirectoryName);
7300 if (NS_WARN_IF(NS_FAILED(rv))) {
7301 return;
7304 rv = file->Append(clientDirectoryName);
7305 if (NS_WARN_IF(NS_FAILED(rv))) {
7306 return;
7309 bool exists;
7310 rv = file->Exists(&exists);
7311 if (NS_WARN_IF(NS_FAILED(rv))) {
7312 return;
7315 if (!exists) {
7316 continue;
7319 bool initialized;
7320 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
7321 initialized = aQuotaManager->IsOriginInitialized(origin);
7322 } else {
7323 initialized = aQuotaManager->IsTemporaryStorageInitialized();
7326 Client* client = aQuotaManager->GetClient(clientType);
7327 MOZ_ASSERT(client);
7329 Atomic<bool> dummy(false);
7330 if (initialized) {
7331 rv = client->GetUsageForOrigin(aPersistenceType, group, origin, dummy,
7332 &usageInfo);
7333 } else {
7334 rv = client->InitOrigin(aPersistenceType, group, origin, dummy,
7335 &usageInfo);
7337 if (NS_WARN_IF(NS_FAILED(rv))) {
7338 return;
7342 for (uint32_t index = 0; index < 10; index++) {
7343 // We can't guarantee that this will always succeed on Windows...
7344 if (NS_SUCCEEDED((rv = file->Remove(true)))) {
7345 break;
7348 NS_WARNING("Failed to remove directory, retrying after a short delay.");
7350 PR_Sleep(PR_MillisecondsToInterval(200));
7353 if (NS_FAILED(rv)) {
7354 NS_WARNING("Failed to remove directory, giving up!");
7357 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
7358 if (mClientType.IsNull()) {
7359 aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
7360 } else {
7361 aQuotaManager->DecreaseUsageForOrigin(aPersistenceType, group, origin,
7362 usageInfo.TotalUsage());
7366 aQuotaManager->OriginClearCompleted(aPersistenceType, origin, mClientType);
7370 nsresult ClearRequestBase::DoDirectoryWork(QuotaManager* aQuotaManager) {
7371 AssertIsOnIOThread();
7373 AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
7375 if (mClear) {
7376 if (mPersistenceType.IsNull()) {
7377 for (const PersistenceType type : kAllPersistenceTypes) {
7378 DeleteFiles(aQuotaManager, type);
7380 } else {
7381 DeleteFiles(aQuotaManager, mPersistenceType.Value());
7385 return NS_OK;
7388 ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
7389 : ClearRequestBase(/* aExclusive */ true,
7390 aParams.type() == RequestParams::TClearOriginParams),
7391 mParams(aParams.type() == RequestParams::TClearOriginParams
7392 ? aParams.get_ClearOriginParams().commonParams()
7393 : aParams.get_ResetOriginParams().commonParams())
7396 MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams ||
7397 aParams.type() == RequestParams::TResetOriginParams);
7400 bool ClearOriginOp::Init(Quota* aQuota) {
7401 AssertIsOnOwningThread();
7402 MOZ_ASSERT(aQuota);
7404 if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
7405 return false;
7408 if (mParams.persistenceTypeIsExplicit()) {
7409 MOZ_ASSERT(mParams.persistenceType() != PERSISTENCE_TYPE_INVALID);
7411 mPersistenceType.SetValue(mParams.persistenceType());
7414 if (mParams.clientTypeIsExplicit()) {
7415 MOZ_ASSERT(mParams.clientType() != Client::TYPE_MAX);
7417 mClientType.SetValue(mParams.clientType());
7420 mNeedsMainThreadInit = true;
7422 return true;
7425 nsresult ClearOriginOp::DoInitOnMainThread() {
7426 MOZ_ASSERT(NS_IsMainThread());
7427 MOZ_ASSERT(GetState() == State_Initializing);
7428 MOZ_ASSERT(mNeedsMainThreadInit);
7430 const PrincipalInfo& principalInfo = mParams.principalInfo();
7432 nsresult rv;
7433 nsCOMPtr<nsIPrincipal> principal =
7434 PrincipalInfoToPrincipal(principalInfo, &rv);
7435 if (NS_WARN_IF(NS_FAILED(rv))) {
7436 return rv;
7439 // Figure out which origin we're dealing with.
7440 nsCString origin;
7441 rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin);
7442 if (NS_WARN_IF(NS_FAILED(rv))) {
7443 return rv;
7446 if (mParams.matchAll()) {
7447 mOriginScope.SetFromPrefix(origin);
7448 } else {
7449 mOriginScope.SetFromOrigin(origin);
7452 return NS_OK;
7455 void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
7456 AssertIsOnOwningThread();
7458 if (mClear) {
7459 aResponse = ClearOriginResponse();
7460 } else {
7461 aResponse = ResetOriginResponse();
7465 ClearDataOp::ClearDataOp(const RequestParams& aParams)
7466 : ClearRequestBase(/* aExclusive */ true,
7467 /* aClear */ true),
7468 mParams(aParams) {
7469 MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
7472 bool ClearDataOp::Init(Quota* aQuota) {
7473 AssertIsOnOwningThread();
7474 MOZ_ASSERT(aQuota);
7476 if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
7477 return false;
7480 mNeedsMainThreadInit = true;
7482 return true;
7485 nsresult ClearDataOp::DoInitOnMainThread() {
7486 MOZ_ASSERT(NS_IsMainThread());
7487 MOZ_ASSERT(GetState() == State_Initializing);
7488 MOZ_ASSERT(mNeedsMainThreadInit);
7490 mOriginScope.SetFromJSONPattern(mParams.pattern());
7492 return NS_OK;
7495 void ClearDataOp::GetResponse(RequestResponse& aResponse) {
7496 AssertIsOnOwningThread();
7498 aResponse = ClearDataResponse();
7501 PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
7502 : QuotaRequestBase(/* aExclusive */ false), mPrincipalInfo(aPrincipalInfo) {
7503 AssertIsOnOwningThread();
7506 bool PersistRequestBase::Init(Quota* aQuota) {
7507 AssertIsOnOwningThread();
7508 MOZ_ASSERT(aQuota);
7510 if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
7511 return false;
7514 mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
7516 mNeedsMainThreadInit = true;
7518 return true;
7521 nsresult PersistRequestBase::DoInitOnMainThread() {
7522 MOZ_ASSERT(NS_IsMainThread());
7523 MOZ_ASSERT(GetState() == State_Initializing);
7524 MOZ_ASSERT(mNeedsMainThreadInit);
7526 nsresult rv;
7527 nsCOMPtr<nsIPrincipal> principal =
7528 PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
7529 if (NS_WARN_IF(NS_FAILED(rv))) {
7530 return rv;
7533 // Figure out which origin we're dealing with.
7534 nsCString origin;
7535 rv =
7536 QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
7537 if (NS_WARN_IF(NS_FAILED(rv))) {
7538 return rv;
7541 mOriginScope.SetFromOrigin(origin);
7543 return NS_OK;
7546 PersistedOp::PersistedOp(const RequestParams& aParams)
7547 : PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
7548 mPersisted(false) {
7549 MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
7552 nsresult PersistedOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7553 AssertIsOnIOThread();
7554 MOZ_ASSERT(!mPersistenceType.IsNull());
7555 MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
7556 MOZ_ASSERT(mOriginScope.IsOrigin());
7558 AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER);
7560 Nullable<bool> persisted =
7561 aQuotaManager->OriginPersisted(mGroup, mOriginScope.GetOrigin());
7563 if (!persisted.IsNull()) {
7564 mPersisted = persisted.Value();
7565 return NS_OK;
7568 // If we get here, it means the origin hasn't been initialized yet.
7569 // Try to get the persisted flag from directory metadata on disk.
7571 nsCOMPtr<nsIFile> directory;
7572 nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
7573 mOriginScope.GetOrigin(),
7574 getter_AddRefs(directory));
7575 if (NS_WARN_IF(NS_FAILED(rv))) {
7576 return rv;
7579 bool exists;
7580 rv = directory->Exists(&exists);
7581 if (NS_WARN_IF(NS_FAILED(rv))) {
7582 return rv;
7585 if (exists) {
7586 // Get the persisted flag.
7587 bool persisted;
7588 rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
7589 directory,
7590 /* aPersistent */ false,
7591 /* aTimestamp */ nullptr, &persisted);
7592 if (NS_WARN_IF(NS_FAILED(rv))) {
7593 return rv;
7596 mPersisted = persisted;
7597 } else {
7598 // The directory has not been created yet.
7599 mPersisted = false;
7602 return NS_OK;
7605 void PersistedOp::GetResponse(RequestResponse& aResponse) {
7606 AssertIsOnOwningThread();
7608 PersistedResponse persistedResponse;
7609 persistedResponse.persisted() = mPersisted;
7611 aResponse = persistedResponse;
7614 PersistOp::PersistOp(const RequestParams& aParams)
7615 : PersistRequestBase(aParams.get_PersistParams().principalInfo()) {
7616 MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
7619 nsresult PersistOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
7620 AssertIsOnIOThread();
7621 MOZ_ASSERT(!mPersistenceType.IsNull());
7622 MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
7623 MOZ_ASSERT(mOriginScope.IsOrigin());
7625 AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER);
7627 // Update directory metadata on disk first. Then, create/update the originInfo
7628 // if needed.
7629 nsCOMPtr<nsIFile> directory;
7630 nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
7631 mOriginScope.GetOrigin(),
7632 getter_AddRefs(directory));
7633 if (NS_WARN_IF(NS_FAILED(rv))) {
7634 return rv;
7637 bool created;
7638 rv = aQuotaManager->EnsureOriginDirectory(directory,
7639 /* aCreateIfNotExists */ true,
7640 &created);
7641 if (NS_WARN_IF(NS_FAILED(rv))) {
7642 return rv;
7645 if (created) {
7646 int64_t timestamp;
7647 rv = CreateDirectoryMetadataFiles(directory,
7648 /* aPersisted */ true, mSuffix, mGroup,
7649 mOriginScope.GetOrigin(), &timestamp);
7650 if (NS_WARN_IF(NS_FAILED(rv))) {
7651 return rv;
7654 // Directory metadata has been successfully created.
7655 // Create OriginInfo too if temporary storage was already initialized.
7656 if (aQuotaManager->IsTemporaryStorageInitialized()) {
7657 aQuotaManager->InitQuotaForOrigin(mPersistenceType.Value(), mGroup,
7658 mOriginScope.GetOrigin(),
7659 /* aUsageBytes */ 0, timestamp,
7660 /* aPersisted */ true);
7662 } else {
7663 // Get the persisted flag (restore the metadata file if necessary).
7664 bool persisted;
7665 rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
7666 directory,
7667 /* aPersistent */ false,
7668 /* aTimestamp */ nullptr, &persisted);
7669 if (NS_WARN_IF(NS_FAILED(rv))) {
7670 return rv;
7673 if (!persisted) {
7674 nsCOMPtr<nsIFile> file;
7675 nsresult rv = directory->Clone(getter_AddRefs(file));
7676 if (NS_WARN_IF(NS_FAILED(rv))) {
7677 return rv;
7680 rv = file->Append(NS_LITERAL_STRING(METADATA_V2_FILE_NAME));
7681 if (NS_WARN_IF(NS_FAILED(rv))) {
7682 return rv;
7685 nsCOMPtr<nsIBinaryOutputStream> stream;
7686 rv = GetBinaryOutputStream(file, kUpdateFileFlag, getter_AddRefs(stream));
7687 if (NS_WARN_IF(NS_FAILED(rv))) {
7688 return rv;
7691 MOZ_ASSERT(stream);
7693 // Update origin access time while we are here.
7694 rv = stream->Write64(PR_Now());
7695 if (NS_WARN_IF(NS_FAILED(rv))) {
7696 return rv;
7699 // Set the persisted flag to true.
7700 rv = stream->WriteBoolean(true);
7701 if (NS_WARN_IF(NS_FAILED(rv))) {
7702 return rv;
7706 // Directory metadata has been successfully updated.
7707 // Update OriginInfo too if temporary storage was already initialized.
7708 if (aQuotaManager->IsTemporaryStorageInitialized()) {
7709 aQuotaManager->PersistOrigin(mGroup, mOriginScope.GetOrigin());
7713 return NS_OK;
7716 void PersistOp::GetResponse(RequestResponse& aResponse) {
7717 AssertIsOnOwningThread();
7719 aResponse = PersistResponse();
7722 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
7723 int64_t& aTimestamp,
7724 nsACString& aGroup,
7725 nsACString& aOrigin,
7726 Nullable<bool>& aIsApp) {
7727 AssertIsOnIOThread();
7728 MOZ_ASSERT(aDirectory);
7730 nsCOMPtr<nsIBinaryInputStream> binaryStream;
7731 nsresult rv =
7732 GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_FILE_NAME),
7733 getter_AddRefs(binaryStream));
7734 if (NS_WARN_IF(NS_FAILED(rv))) {
7735 return rv;
7738 uint64_t timestamp;
7739 rv = binaryStream->Read64(&timestamp);
7740 if (NS_WARN_IF(NS_FAILED(rv))) {
7741 return rv;
7744 nsCString group;
7745 rv = binaryStream->ReadCString(group);
7746 if (NS_WARN_IF(NS_FAILED(rv))) {
7747 return rv;
7750 nsCString origin;
7751 rv = binaryStream->ReadCString(origin);
7752 if (NS_WARN_IF(NS_FAILED(rv))) {
7753 return rv;
7756 Nullable<bool> isApp;
7757 bool value;
7758 if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
7759 isApp.SetValue(value);
7762 aTimestamp = timestamp;
7763 aGroup = group;
7764 aOrigin = origin;
7765 aIsApp = std::move(isApp);
7766 return NS_OK;
7769 nsresult StorageOperationBase::GetDirectoryMetadata2(
7770 nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
7771 nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
7772 AssertIsOnIOThread();
7773 MOZ_ASSERT(aDirectory);
7775 nsCOMPtr<nsIBinaryInputStream> binaryStream;
7776 nsresult rv =
7777 GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
7778 getter_AddRefs(binaryStream));
7779 if (NS_WARN_IF(NS_FAILED(rv))) {
7780 return rv;
7783 uint64_t timestamp;
7784 rv = binaryStream->Read64(&timestamp);
7785 if (NS_WARN_IF(NS_FAILED(rv))) {
7786 return rv;
7789 bool persisted;
7790 rv = binaryStream->ReadBoolean(&persisted);
7791 if (NS_WARN_IF(NS_FAILED(rv))) {
7792 return rv;
7795 uint32_t reservedData1;
7796 rv = binaryStream->Read32(&reservedData1);
7797 if (NS_WARN_IF(NS_FAILED(rv))) {
7798 return rv;
7801 uint32_t reservedData2;
7802 rv = binaryStream->Read32(&reservedData2);
7803 if (NS_WARN_IF(NS_FAILED(rv))) {
7804 return rv;
7807 nsCString suffix;
7808 rv = binaryStream->ReadCString(suffix);
7809 if (NS_WARN_IF(NS_FAILED(rv))) {
7810 return rv;
7813 nsCString group;
7814 rv = binaryStream->ReadCString(group);
7815 if (NS_WARN_IF(NS_FAILED(rv))) {
7816 return rv;
7819 nsCString origin;
7820 rv = binaryStream->ReadCString(origin);
7821 if (NS_WARN_IF(NS_FAILED(rv))) {
7822 return rv;
7825 bool isApp;
7826 rv = binaryStream->ReadBoolean(&isApp);
7827 if (NS_WARN_IF(NS_FAILED(rv))) {
7828 return rv;
7831 aTimestamp = timestamp;
7832 aSuffix = suffix;
7833 aGroup = group;
7834 aOrigin = origin;
7835 aIsApp = isApp;
7836 return NS_OK;
7839 nsresult StorageOperationBase::RemoveObsoleteOrigin(
7840 const OriginProps& aOriginProps) {
7841 AssertIsOnIOThread();
7842 MOZ_ASSERT(aOriginProps.mDirectory);
7844 QM_WARNING(
7845 "Deleting obsolete %s directory that is no longer a legal "
7846 "origin!",
7847 NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
7849 nsresult rv = aOriginProps.mDirectory->Remove(/* recursive */ true);
7850 if (NS_WARN_IF(NS_FAILED(rv))) {
7851 return rv;
7854 return NS_OK;
7857 nsresult StorageOperationBase::ProcessOriginDirectories() {
7858 AssertIsOnIOThread();
7859 MOZ_ASSERT(!mOriginProps.IsEmpty());
7861 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7864 mozilla::MutexAutoLock autolock(mMutex);
7865 while (mWaiting) {
7866 mCondVar.Wait();
7870 if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
7871 return mMainThreadResultCode;
7874 // Verify that the bounce to the main thread didn't start the shutdown
7875 // sequence.
7876 if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
7877 return NS_ERROR_FAILURE;
7880 nsresult rv;
7882 // Don't try to upgrade obsolete origins, remove them right after we detect
7883 // them.
7884 for (auto& originProps : mOriginProps) {
7885 if (originProps.mType == OriginProps::eObsolete) {
7886 MOZ_ASSERT(originProps.mSuffix.IsEmpty());
7887 MOZ_ASSERT(originProps.mGroup.IsEmpty());
7888 MOZ_ASSERT(originProps.mOrigin.IsEmpty());
7890 rv = RemoveObsoleteOrigin(originProps);
7891 } else {
7892 MOZ_ASSERT(!originProps.mGroup.IsEmpty());
7893 MOZ_ASSERT(!originProps.mOrigin.IsEmpty());
7895 rv = ProcessOriginDirectory(originProps);
7897 if (NS_WARN_IF(NS_FAILED(rv))) {
7898 return rv;
7902 return NS_OK;
7905 nsresult StorageOperationBase::RunOnMainThread() {
7906 MOZ_ASSERT(NS_IsMainThread());
7907 MOZ_ASSERT(!mOriginProps.IsEmpty());
7909 nsresult rv;
7911 for (uint32_t count = mOriginProps.Length(), index = 0; index < count;
7912 index++) {
7913 OriginProps& originProps = mOriginProps[index];
7915 switch (originProps.mType) {
7916 case OriginProps::eChrome: {
7917 QuotaManager::GetInfoForChrome(
7918 &originProps.mSuffix, &originProps.mGroup, &originProps.mOrigin);
7919 break;
7922 case OriginProps::eContent: {
7923 nsCOMPtr<nsIURI> uri;
7924 rv = NS_NewURI(getter_AddRefs(uri), originProps.mSpec);
7925 if (NS_WARN_IF(NS_FAILED(rv))) {
7926 return rv;
7929 nsCOMPtr<nsIPrincipal> principal =
7930 BasePrincipal::CreateCodebasePrincipal(uri, originProps.mAttrs);
7931 if (NS_WARN_IF(!principal)) {
7932 return NS_ERROR_FAILURE;
7935 rv = QuotaManager::GetInfoFromPrincipal(principal, &originProps.mSuffix,
7936 &originProps.mGroup,
7937 &originProps.mOrigin);
7938 if (NS_WARN_IF(NS_FAILED(rv))) {
7939 return rv;
7942 break;
7945 case OriginProps::eObsolete: {
7946 // There's no way to get info for obsolete origins.
7947 break;
7950 default:
7951 MOZ_CRASH("Bad type!");
7955 return NS_OK;
7958 NS_IMETHODIMP
7959 StorageOperationBase::Run() {
7960 MOZ_ASSERT(NS_IsMainThread());
7962 nsresult rv = RunOnMainThread();
7963 if (NS_WARN_IF(NS_FAILED(rv))) {
7964 mMainThreadResultCode = rv;
7967 MutexAutoLock lock(mMutex);
7968 MOZ_ASSERT(mWaiting);
7970 mWaiting = false;
7971 mCondVar.Notify();
7973 return NS_OK;
7976 nsresult StorageOperationBase::OriginProps::Init(nsIFile* aDirectory) {
7977 AssertIsOnIOThread();
7978 MOZ_ASSERT(aDirectory);
7980 nsString leafName;
7981 nsresult rv = aDirectory->GetLeafName(leafName);
7982 if (NS_WARN_IF(NS_FAILED(rv))) {
7983 return rv;
7986 if (leafName.EqualsLiteral(kChromeOrigin)) {
7987 // XXX We can remove this special handling once origin parser supports it
7988 // directly.
7989 mDirectory = aDirectory;
7990 mLeafName = leafName;
7991 mSpec = kChromeOrigin;
7992 mType = eChrome;
7993 } else if (leafName.EqualsLiteral("moz-safe-about+++home")) {
7994 // XXX We can remove this special handling once origin parser supports it
7995 // directly.
7997 // This directory was accidentally created by a buggy nightly and can be
7998 // safely removed.
8000 mDirectory = aDirectory;
8001 mLeafName = leafName;
8002 mType = eObsolete;
8003 } else {
8004 nsCString spec;
8005 OriginAttributes attrs;
8006 OriginParser::ResultType result = OriginParser::ParseOrigin(
8007 NS_ConvertUTF16toUTF8(leafName), spec, &attrs);
8008 if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
8009 return NS_ERROR_FAILURE;
8012 mDirectory = aDirectory;
8013 mLeafName = leafName;
8014 mSpec = spec;
8015 mAttrs = attrs;
8016 mType = result == OriginParser::ObsoleteOrigin ? eObsolete : eContent;
8019 return NS_OK;
8022 // static
8023 auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
8024 OriginAttributes* aAttrs) -> ResultType {
8025 MOZ_ASSERT(!aOrigin.IsEmpty());
8026 MOZ_ASSERT(aAttrs);
8028 OriginAttributes originAttributes;
8030 nsCString originNoSuffix;
8031 bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
8032 if (!ok) {
8033 return InvalidOrigin;
8036 OriginParser parser(originNoSuffix, originAttributes);
8037 return parser.Parse(aSpec, aAttrs);
8040 auto OriginParser::Parse(nsACString& aSpec, OriginAttributes* aAttrs)
8041 -> ResultType {
8042 MOZ_ASSERT(aAttrs);
8044 while (mTokenizer.hasMoreTokens()) {
8045 const nsDependentCSubstring& token = mTokenizer.nextToken();
8047 HandleToken(token);
8049 if (mError) {
8050 break;
8053 if (!mHandledTokens.IsEmpty()) {
8054 mHandledTokens.AppendLiteral(", ");
8056 mHandledTokens.Append('\'');
8057 mHandledTokens.Append(token);
8058 mHandledTokens.Append('\'');
8061 if (!mError && mTokenizer.separatorAfterCurrentToken()) {
8062 HandleTrailingSeparator();
8065 if (mError) {
8066 QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
8067 mHandledTokens.get());
8069 return InvalidOrigin;
8072 MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
8074 // For IPv6 URL, it should at least have three groups.
8075 MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
8077 if (mAppId == kNoAppId) {
8078 *aAttrs = mOriginAttributes;
8079 } else {
8080 MOZ_ASSERT(mOriginAttributes.mAppId == kNoAppId);
8082 *aAttrs = OriginAttributes(mAppId, mInIsolatedMozBrowser);
8085 nsAutoCString spec(mScheme);
8087 if (mSchemeType == eFile) {
8088 spec.AppendLiteral("://");
8090 if (mUniversalFileOrigin) {
8091 MOZ_ASSERT(mPathnameComponents.Length() == 1);
8093 spec.Append(mPathnameComponents[0]);
8094 } else {
8095 for (uint32_t count = mPathnameComponents.Length(), index = 0;
8096 index < count; index++) {
8097 spec.Append('/');
8098 spec.Append(mPathnameComponents[index]);
8102 aSpec = spec;
8104 return ValidOrigin;
8107 if (mSchemeType == eAbout) {
8108 spec.Append(':');
8109 } else {
8110 spec.AppendLiteral("://");
8113 spec.Append(mHost);
8115 if (!mPort.IsNull()) {
8116 spec.Append(':');
8117 spec.AppendInt(mPort.Value());
8120 aSpec = spec;
8122 return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin;
8125 void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) {
8126 MOZ_ASSERT(!aToken.IsEmpty());
8127 MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme);
8129 bool isAbout = false;
8130 bool isFile = false;
8131 if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") ||
8132 (isAbout = aToken.EqualsLiteral("about") ||
8133 aToken.EqualsLiteral("moz-safe-about")) ||
8134 aToken.EqualsLiteral("indexeddb") ||
8135 (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") ||
8136 aToken.EqualsLiteral("resource") ||
8137 aToken.EqualsLiteral("moz-extension")) {
8138 mScheme = aToken;
8140 if (isAbout) {
8141 mSchemeType = eAbout;
8142 mState = eExpectingHost;
8143 } else {
8144 if (isFile) {
8145 mSchemeType = eFile;
8147 mState = eExpectingEmptyToken1;
8150 return;
8153 QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get());
8155 mError = true;
8158 void OriginParser::HandlePathnameComponent(
8159 const nsDependentCSubstring& aToken) {
8160 MOZ_ASSERT(!aToken.IsEmpty());
8161 MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
8162 mState == eExpectingEmptyTokenOrPathnameComponent);
8163 MOZ_ASSERT(mSchemeType == eFile);
8165 mPathnameComponents.AppendElement(aToken);
8167 mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
8168 : eComplete;
8171 void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
8172 switch (mState) {
8173 case eExpectingAppIdOrScheme: {
8174 if (aToken.IsEmpty()) {
8175 QM_WARNING("Expected an app id or scheme (not an empty string)!");
8177 mError = true;
8178 return;
8181 if (IsAsciiDigit(aToken.First())) {
8182 // nsDependentCSubstring doesn't provice ToInteger()
8183 nsCString token(aToken);
8185 nsresult rv;
8186 uint32_t appId = token.ToInteger(&rv);
8187 if (NS_SUCCEEDED(rv)) {
8188 mAppId = appId;
8189 mState = eExpectingInMozBrowser;
8190 return;
8194 HandleScheme(aToken);
8196 return;
8199 case eExpectingInMozBrowser: {
8200 if (aToken.Length() != 1) {
8201 QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!",
8202 aToken.Length());
8204 mError = true;
8205 return;
8208 if (aToken.First() == 't') {
8209 mInIsolatedMozBrowser = true;
8210 } else if (aToken.First() == 'f') {
8211 mInIsolatedMozBrowser = false;
8212 } else {
8213 QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
8214 nsCString(aToken).get());
8216 mError = true;
8217 return;
8220 mState = eExpectingScheme;
8222 return;
8225 case eExpectingScheme: {
8226 if (aToken.IsEmpty()) {
8227 QM_WARNING("Expected a scheme (not an empty string)!");
8229 mError = true;
8230 return;
8233 HandleScheme(aToken);
8235 return;
8238 case eExpectingEmptyToken1: {
8239 if (!aToken.IsEmpty()) {
8240 QM_WARNING("Expected the first empty token!");
8242 mError = true;
8243 return;
8246 mState = eExpectingEmptyToken2;
8248 return;
8251 case eExpectingEmptyToken2: {
8252 if (!aToken.IsEmpty()) {
8253 QM_WARNING("Expected the second empty token!");
8255 mError = true;
8256 return;
8259 if (mSchemeType == eFile) {
8260 mState = eExpectingEmptyTokenOrUniversalFileOrigin;
8261 } else {
8262 mState = eExpectingHost;
8265 return;
8268 case eExpectingEmptyTokenOrUniversalFileOrigin: {
8269 MOZ_ASSERT(mSchemeType == eFile);
8271 if (aToken.IsEmpty()) {
8272 mState = mTokenizer.hasMoreTokens()
8273 ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
8274 : eComplete;
8276 return;
8279 if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
8280 mUniversalFileOrigin = true;
8282 mPathnameComponents.AppendElement(aToken);
8284 mState = eComplete;
8286 return;
8289 QM_WARNING(
8290 "Expected the third empty token or "
8291 "UNIVERSAL_FILE_URI_ORIGIN!");
8293 mError = true;
8294 return;
8297 case eExpectingHost: {
8298 if (aToken.IsEmpty()) {
8299 QM_WARNING("Expected a host (not an empty string)!");
8301 mError = true;
8302 return;
8305 mHost = aToken;
8307 if (aToken.First() == '[') {
8308 MOZ_ASSERT(mIPGroup == 0);
8310 ++mIPGroup;
8311 mState = eExpectingIPV6Token;
8313 MOZ_ASSERT(mTokenizer.hasMoreTokens());
8314 return;
8317 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
8319 return;
8322 case eExpectingPort: {
8323 MOZ_ASSERT(mSchemeType == eNone);
8325 if (aToken.IsEmpty()) {
8326 QM_WARNING("Expected a port (not an empty string)!");
8328 mError = true;
8329 return;
8332 // nsDependentCSubstring doesn't provice ToInteger()
8333 nsCString token(aToken);
8335 nsresult rv;
8336 uint32_t port = token.ToInteger(&rv);
8337 if (NS_SUCCEEDED(rv)) {
8338 mPort.SetValue() = port;
8339 } else {
8340 QM_WARNING("'%s' is not a valid port number!", token.get());
8342 mError = true;
8343 return;
8346 mState = eComplete;
8348 return;
8351 case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
8352 MOZ_ASSERT(mSchemeType == eFile);
8354 if (aToken.IsEmpty()) {
8355 mPathnameComponents.AppendElement(EmptyCString());
8357 mState = mTokenizer.hasMoreTokens()
8358 ? eExpectingEmptyTokenOrPathnameComponent
8359 : eComplete;
8361 return;
8364 if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) {
8365 mMaybeDriveLetter = true;
8367 mPathnameComponents.AppendElement(aToken);
8369 mState = mTokenizer.hasMoreTokens()
8370 ? eExpectingEmptyTokenOrPathnameComponent
8371 : eComplete;
8373 return;
8376 HandlePathnameComponent(aToken);
8378 return;
8381 case eExpectingEmptyTokenOrPathnameComponent: {
8382 MOZ_ASSERT(mSchemeType == eFile);
8384 if (aToken.IsEmpty()) {
8385 if (mMaybeDriveLetter) {
8386 MOZ_ASSERT(mPathnameComponents.Length() == 1);
8388 nsCString& pathnameComponent = mPathnameComponents[0];
8389 pathnameComponent.Append(':');
8391 mMaybeDriveLetter = false;
8392 } else {
8393 mPathnameComponents.AppendElement(EmptyCString());
8396 mState = mTokenizer.hasMoreTokens()
8397 ? eExpectingEmptyTokenOrPathnameComponent
8398 : eComplete;
8400 return;
8403 HandlePathnameComponent(aToken);
8405 return;
8408 case eExpectingIPV6Token: {
8409 // A safe check for preventing infinity recursion.
8410 if (++mIPGroup > 8) {
8411 mError = true;
8412 return;
8415 mHost.AppendLiteral(":");
8416 mHost.Append(aToken);
8417 if (!aToken.IsEmpty() && aToken.Last() == ']') {
8418 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
8421 return;
8424 default:
8425 MOZ_CRASH("Should never get here!");
8429 void OriginParser::HandleTrailingSeparator() {
8430 MOZ_ASSERT(mState == eComplete);
8431 MOZ_ASSERT(mSchemeType == eFile);
8433 mPathnameComponents.AppendElement(EmptyCString());
8435 mState = eHandledTrailingSeparator;
8438 nsresult RepositoryOperationBase::ProcessRepository() {
8439 AssertIsOnIOThread();
8441 DebugOnly<bool> exists;
8442 MOZ_ASSERT(NS_SUCCEEDED(mDirectory->Exists(&exists)));
8443 MOZ_ASSERT(exists);
8445 nsCOMPtr<nsIDirectoryEnumerator> entries;
8446 nsresult rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries));
8447 if (NS_WARN_IF(NS_FAILED(rv))) {
8448 return rv;
8451 while (true) {
8452 nsCOMPtr<nsIFile> originDir;
8453 rv = entries->GetNextFile(getter_AddRefs(originDir));
8454 if (NS_WARN_IF(NS_FAILED(rv))) {
8455 return rv;
8458 if (!originDir) {
8459 break;
8462 bool isDirectory;
8463 rv = originDir->IsDirectory(&isDirectory);
8464 if (NS_WARN_IF(NS_FAILED(rv))) {
8465 return rv;
8468 if (!isDirectory) {
8469 nsString leafName;
8470 rv = originDir->GetLeafName(leafName);
8471 if (NS_WARN_IF(NS_FAILED(rv))) {
8472 return rv;
8475 // Unknown files during upgrade are allowed. Just warn if we find them.
8476 if (!IsOSMetadata(leafName)) {
8477 UNKNOWN_FILE_WARNING(leafName);
8479 continue;
8482 OriginProps originProps;
8483 rv = originProps.Init(originDir);
8484 if (NS_WARN_IF(NS_FAILED(rv))) {
8485 return rv;
8488 if (originProps.mType != OriginProps::eObsolete) {
8489 bool removed;
8490 rv = PrepareOriginDirectory(originProps, &removed);
8491 if (NS_WARN_IF(NS_FAILED(rv))) {
8492 return rv;
8494 if (removed) {
8495 continue;
8499 mOriginProps.AppendElement(std::move(originProps));
8502 if (mOriginProps.IsEmpty()) {
8503 return NS_OK;
8506 rv = ProcessOriginDirectories();
8507 if (NS_WARN_IF(NS_FAILED(rv))) {
8508 return rv;
8511 return NS_OK;
8514 template <typename UpgradeMethod>
8515 nsresult RepositoryOperationBase::MaybeUpgradeClients(
8516 const OriginProps& aOriginProps, UpgradeMethod aMethod) {
8517 AssertIsOnIOThread();
8518 MOZ_ASSERT(aOriginProps.mDirectory);
8519 MOZ_ASSERT(aMethod);
8521 QuotaManager* quotaManager = QuotaManager::Get();
8522 MOZ_ASSERT(quotaManager);
8524 nsCOMPtr<nsIDirectoryEnumerator> entries;
8525 nsresult rv =
8526 aOriginProps.mDirectory->GetDirectoryEntries(getter_AddRefs(entries));
8527 if (NS_WARN_IF(NS_FAILED(rv))) {
8528 return rv;
8531 while (true) {
8532 nsCOMPtr<nsIFile> file;
8533 rv = entries->GetNextFile(getter_AddRefs(file));
8534 if (NS_WARN_IF(NS_FAILED(rv))) {
8535 return rv;
8538 if (!file) {
8539 break;
8542 bool isDirectory;
8543 rv = file->IsDirectory(&isDirectory);
8544 if (NS_WARN_IF(NS_FAILED(rv))) {
8545 return rv;
8548 nsString leafName;
8549 rv = file->GetLeafName(leafName);
8550 if (NS_WARN_IF(NS_FAILED(rv))) {
8551 return rv;
8554 if (!isDirectory) {
8555 // Unknown files during upgrade are allowed. Just warn if we find them.
8556 if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
8557 UNKNOWN_FILE_WARNING(leafName);
8559 continue;
8562 Client::Type clientType;
8563 rv = Client::TypeFromText(leafName, clientType);
8564 if (NS_FAILED(rv)) {
8565 UNKNOWN_FILE_WARNING(leafName);
8566 continue;
8569 Client* client = quotaManager->GetClient(clientType);
8570 MOZ_ASSERT(client);
8572 rv = (client->*aMethod)(file);
8573 if (NS_WARN_IF(NS_FAILED(rv))) {
8574 return rv;
8578 return NS_OK;
8581 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
8582 nsIFile* aDirectory) {
8583 AssertIsOnIOThread();
8584 MOZ_ASSERT(aDirectory);
8586 nsCOMPtr<nsIFile> metadataFile;
8587 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
8588 if (NS_WARN_IF(NS_FAILED(rv))) {
8589 return rv;
8592 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
8593 if (NS_WARN_IF(NS_FAILED(rv))) {
8594 return rv;
8597 bool exists;
8598 rv = metadataFile->Exists(&exists);
8599 if (NS_WARN_IF(NS_FAILED(rv))) {
8600 return rv;
8603 if (!exists) {
8604 // Directory structure upgrade needed.
8605 // Move all files to IDB specific directory.
8607 nsString idbDirectoryName;
8608 rv = Client::TypeToText(Client::IDB, idbDirectoryName);
8609 if (NS_WARN_IF(NS_FAILED(rv))) {
8610 return rv;
8613 nsCOMPtr<nsIFile> idbDirectory;
8614 rv = aDirectory->Clone(getter_AddRefs(idbDirectory));
8615 if (NS_WARN_IF(NS_FAILED(rv))) {
8616 return rv;
8619 rv = idbDirectory->Append(idbDirectoryName);
8620 if (NS_WARN_IF(NS_FAILED(rv))) {
8621 return rv;
8624 rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
8625 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
8626 NS_WARNING("IDB directory already exists!");
8628 bool isDirectory;
8629 rv = idbDirectory->IsDirectory(&isDirectory);
8630 if (NS_WARN_IF(NS_FAILED(rv))) {
8631 return rv;
8634 if (NS_WARN_IF(!isDirectory)) {
8635 return NS_ERROR_UNEXPECTED;
8637 } else {
8638 if (NS_WARN_IF(NS_FAILED(rv))) {
8639 return rv;
8643 nsCOMPtr<nsIDirectoryEnumerator> entries;
8644 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
8645 if (NS_WARN_IF(NS_FAILED(rv))) {
8646 return rv;
8649 nsCOMPtr<nsIFile> file;
8650 while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
8651 file) {
8652 nsString leafName;
8653 rv = file->GetLeafName(leafName);
8654 if (NS_WARN_IF(NS_FAILED(rv))) {
8655 return rv;
8658 if (!leafName.Equals(idbDirectoryName)) {
8659 rv = file->MoveTo(idbDirectory, EmptyString());
8660 if (NS_WARN_IF(NS_FAILED(rv))) {
8661 return rv;
8666 rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
8667 if (NS_WARN_IF(NS_FAILED(rv))) {
8668 return rv;
8672 return NS_OK;
8675 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
8676 OriginProps& aOriginProps, bool* aRemoved) {
8677 AssertIsOnIOThread();
8678 MOZ_ASSERT(aOriginProps.mDirectory);
8679 MOZ_ASSERT(aRemoved);
8681 nsresult rv;
8683 if (mPersistent) {
8684 rv = MaybeUpgradeOriginDirectory(aOriginProps.mDirectory);
8685 if (NS_WARN_IF(NS_FAILED(rv))) {
8686 return rv;
8689 bool persistent = QuotaManager::IsOriginInternal(aOriginProps.mSpec);
8690 aOriginProps.mTimestamp =
8691 GetLastModifiedTime(aOriginProps.mDirectory, persistent);
8692 } else {
8693 int64_t timestamp;
8694 nsCString group;
8695 nsCString origin;
8696 Nullable<bool> isApp;
8697 rv = GetDirectoryMetadata(aOriginProps.mDirectory, timestamp, group, origin,
8698 isApp);
8699 if (NS_FAILED(rv)) {
8700 aOriginProps.mTimestamp =
8701 GetLastModifiedTime(aOriginProps.mDirectory, mPersistent);
8702 aOriginProps.mNeedsRestore = true;
8703 } else if (!isApp.IsNull()) {
8704 aOriginProps.mIgnore = true;
8708 *aRemoved = false;
8709 return NS_OK;
8712 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
8713 const OriginProps& aOriginProps) {
8714 AssertIsOnIOThread();
8716 nsresult rv;
8718 if (mPersistent) {
8719 rv = CreateDirectoryMetadata(aOriginProps.mDirectory,
8720 aOriginProps.mTimestamp, aOriginProps.mSuffix,
8721 aOriginProps.mGroup, aOriginProps.mOrigin);
8722 if (NS_WARN_IF(NS_FAILED(rv))) {
8723 return rv;
8726 // Move internal origins to new persistent storage.
8727 if (QuotaManager::IsOriginInternal(aOriginProps.mSpec)) {
8728 if (!mPermanentStorageDir) {
8729 QuotaManager* quotaManager = QuotaManager::Get();
8730 MOZ_ASSERT(quotaManager);
8732 const nsString& permanentStoragePath =
8733 quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
8735 rv = NS_NewLocalFile(permanentStoragePath, false,
8736 getter_AddRefs(mPermanentStorageDir));
8737 if (NS_WARN_IF(NS_FAILED(rv))) {
8738 return rv;
8742 nsString leafName;
8743 rv = aOriginProps.mDirectory->GetLeafName(leafName);
8744 if (NS_WARN_IF(NS_FAILED(rv))) {
8745 return rv;
8748 nsCOMPtr<nsIFile> newDirectory;
8749 rv = mPermanentStorageDir->Clone(getter_AddRefs(newDirectory));
8750 if (NS_WARN_IF(NS_FAILED(rv))) {
8751 return rv;
8754 rv = newDirectory->Append(leafName);
8755 if (NS_WARN_IF(NS_FAILED(rv))) {
8756 return rv;
8759 bool exists;
8760 rv = newDirectory->Exists(&exists);
8761 if (NS_WARN_IF(NS_FAILED(rv))) {
8762 return rv;
8765 if (exists) {
8766 QM_WARNING("Found %s in storage/persistent and storage/permanent !",
8767 NS_ConvertUTF16toUTF8(leafName).get());
8769 rv = aOriginProps.mDirectory->Remove(/* recursive */ true);
8770 } else {
8771 rv = aOriginProps.mDirectory->MoveTo(mPermanentStorageDir,
8772 EmptyString());
8774 if (NS_WARN_IF(NS_FAILED(rv))) {
8775 return rv;
8778 } else if (aOriginProps.mNeedsRestore) {
8779 rv = CreateDirectoryMetadata(aOriginProps.mDirectory,
8780 aOriginProps.mTimestamp, aOriginProps.mSuffix,
8781 aOriginProps.mGroup, aOriginProps.mOrigin);
8782 if (NS_WARN_IF(NS_FAILED(rv))) {
8783 return rv;
8785 } else if (!aOriginProps.mIgnore) {
8786 nsCOMPtr<nsIFile> file;
8787 rv = aOriginProps.mDirectory->Clone(getter_AddRefs(file));
8788 if (NS_WARN_IF(NS_FAILED(rv))) {
8789 return rv;
8792 rv = file->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
8793 if (NS_WARN_IF(NS_FAILED(rv))) {
8794 return rv;
8797 nsCOMPtr<nsIBinaryOutputStream> stream;
8798 rv = GetBinaryOutputStream(file, kAppendFileFlag, getter_AddRefs(stream));
8799 if (NS_WARN_IF(NS_FAILED(rv))) {
8800 return rv;
8803 MOZ_ASSERT(stream);
8805 // Currently unused (used to be isApp).
8806 rv = stream->WriteBoolean(false);
8807 if (NS_WARN_IF(NS_FAILED(rv))) {
8808 return rv;
8812 return NS_OK;
8815 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
8816 OriginProps& aOriginProps, bool* aRemoved) {
8817 AssertIsOnIOThread();
8818 MOZ_ASSERT(aOriginProps.mDirectory);
8819 MOZ_ASSERT(aRemoved);
8821 int64_t timestamp;
8822 nsCString group;
8823 nsCString origin;
8824 Nullable<bool> isApp;
8825 nsresult rv = GetDirectoryMetadata(aOriginProps.mDirectory, timestamp, group,
8826 origin, isApp);
8827 if (NS_FAILED(rv) || isApp.IsNull()) {
8828 aOriginProps.mTimestamp =
8829 GetLastModifiedTime(aOriginProps.mDirectory, mPersistent);
8830 aOriginProps.mNeedsRestore = true;
8831 } else {
8832 aOriginProps.mTimestamp = timestamp;
8835 *aRemoved = false;
8836 return NS_OK;
8839 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
8840 const OriginProps& aOriginProps) {
8841 AssertIsOnIOThread();
8843 nsresult rv;
8845 if (aOriginProps.mNeedsRestore) {
8846 rv = CreateDirectoryMetadata(aOriginProps.mDirectory,
8847 aOriginProps.mTimestamp, aOriginProps.mSuffix,
8848 aOriginProps.mGroup, aOriginProps.mOrigin);
8849 if (NS_WARN_IF(NS_FAILED(rv))) {
8850 return rv;
8854 rv =
8855 CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp,
8856 /* aPersisted */ false, aOriginProps.mSuffix,
8857 aOriginProps.mGroup, aOriginProps.mOrigin);
8858 if (NS_WARN_IF(NS_FAILED(rv))) {
8859 return rv;
8862 nsString oldName;
8863 rv = aOriginProps.mDirectory->GetLeafName(oldName);
8864 if (NS_WARN_IF(NS_FAILED(rv))) {
8865 return rv;
8868 nsAutoCString originSanitized(aOriginProps.mOrigin);
8869 SanitizeOriginString(originSanitized);
8871 NS_ConvertASCIItoUTF16 newName(originSanitized);
8873 if (!oldName.Equals(newName)) {
8874 rv = aOriginProps.mDirectory->RenameTo(nullptr, newName);
8875 if (NS_WARN_IF(NS_FAILED(rv))) {
8876 return rv;
8880 return NS_OK;
8883 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
8884 const OriginProps& aOriginProps) {
8885 AssertIsOnIOThread();
8886 MOZ_ASSERT(aOriginProps.mDirectory);
8888 // The Cache API was creating top level morgue directories by accident for
8889 // a short time in nightly. This unfortunately prevents all storage from
8890 // working. So recover these profiles permanently by removing these corrupt
8891 // directories as part of this upgrade.
8893 nsCOMPtr<nsIFile> morgueDir;
8894 nsresult rv = aOriginProps.mDirectory->Clone(getter_AddRefs(morgueDir));
8895 if (NS_WARN_IF(NS_FAILED(rv))) {
8896 return rv;
8899 rv = morgueDir->Append(NS_LITERAL_STRING("morgue"));
8900 if (NS_WARN_IF(NS_FAILED(rv))) {
8901 return rv;
8904 bool exists;
8905 rv = morgueDir->Exists(&exists);
8906 if (NS_WARN_IF(NS_FAILED(rv))) {
8907 return rv;
8910 if (exists) {
8911 QM_WARNING("Deleting accidental morgue directory!");
8913 rv = morgueDir->Remove(/* recursive */ true);
8914 if (NS_WARN_IF(NS_FAILED(rv))) {
8915 return rv;
8919 return NS_OK;
8922 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
8923 const OriginProps& aOriginProps, bool* aRemoved) {
8924 AssertIsOnIOThread();
8926 // XXX This will need to be reworked as part of bug 1320404 (appId is
8927 // going to be removed from origin attributes).
8928 if (aOriginProps.mAttrs.mAppId != kNoAppId &&
8929 aOriginProps.mAttrs.mAppId != kUnknownAppId) {
8930 nsresult rv = RemoveObsoleteOrigin(aOriginProps);
8931 if (NS_WARN_IF(NS_FAILED(rv))) {
8932 return rv;
8935 *aRemoved = true;
8936 return NS_OK;
8939 *aRemoved = false;
8940 return NS_OK;
8943 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeStripObsoleteOriginAttributes(
8944 const OriginProps& aOriginProps, bool* aStripped) {
8945 AssertIsOnIOThread();
8946 MOZ_ASSERT(aOriginProps.mDirectory);
8948 const nsAString& oldLeafName = aOriginProps.mLeafName;
8950 nsCString originSanitized(aOriginProps.mOrigin);
8951 SanitizeOriginString(originSanitized);
8953 NS_ConvertUTF8toUTF16 newLeafName(originSanitized);
8955 if (oldLeafName == newLeafName) {
8956 *aStripped = false;
8957 return NS_OK;
8960 nsresult rv = CreateDirectoryMetadata(
8961 aOriginProps.mDirectory, aOriginProps.mTimestamp, aOriginProps.mSuffix,
8962 aOriginProps.mGroup, aOriginProps.mOrigin);
8963 if (NS_WARN_IF(NS_FAILED(rv))) {
8964 return rv;
8967 rv =
8968 CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp,
8969 /* aPersisted */ false, aOriginProps.mSuffix,
8970 aOriginProps.mGroup, aOriginProps.mOrigin);
8971 if (NS_WARN_IF(NS_FAILED(rv))) {
8972 return rv;
8975 nsCOMPtr<nsIFile> newFile;
8976 rv = aOriginProps.mDirectory->GetParent(getter_AddRefs(newFile));
8977 if (NS_WARN_IF(NS_FAILED(rv))) {
8978 return rv;
8981 rv = newFile->Append(newLeafName);
8982 if (NS_WARN_IF(NS_FAILED(rv))) {
8983 return rv;
8986 bool exists;
8987 rv = newFile->Exists(&exists);
8988 if (NS_WARN_IF(NS_FAILED(rv))) {
8989 return rv;
8992 if (exists) {
8993 QM_WARNING(
8994 "Can't rename %s directory, %s directory already exists, "
8995 "removing!",
8996 NS_ConvertUTF16toUTF8(oldLeafName).get(),
8997 NS_ConvertUTF16toUTF8(newLeafName).get());
8999 rv = aOriginProps.mDirectory->Remove(/* recursive */ true);
9000 } else {
9001 rv = aOriginProps.mDirectory->RenameTo(nullptr, newLeafName);
9003 if (NS_WARN_IF(NS_FAILED(rv))) {
9004 return rv;
9007 *aStripped = true;
9008 return NS_OK;
9011 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
9012 OriginProps& aOriginProps, bool* aRemoved) {
9013 AssertIsOnIOThread();
9014 MOZ_ASSERT(aOriginProps.mDirectory);
9015 MOZ_ASSERT(aRemoved);
9017 nsresult rv = MaybeRemoveMorgueDirectory(aOriginProps);
9018 if (NS_WARN_IF(NS_FAILED(rv))) {
9019 return rv;
9022 rv = MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0);
9023 if (NS_WARN_IF(NS_FAILED(rv))) {
9024 return rv;
9027 bool removed;
9028 rv = MaybeRemoveAppsData(aOriginProps, &removed);
9029 if (NS_WARN_IF(NS_FAILED(rv))) {
9030 return rv;
9032 if (removed) {
9033 *aRemoved = true;
9034 return NS_OK;
9037 int64_t timestamp;
9038 nsCString group;
9039 nsCString origin;
9040 Nullable<bool> isApp;
9041 rv = GetDirectoryMetadata(aOriginProps.mDirectory, timestamp, group, origin,
9042 isApp);
9043 if (NS_FAILED(rv) || isApp.IsNull()) {
9044 aOriginProps.mNeedsRestore = true;
9047 nsCString suffix;
9048 rv = GetDirectoryMetadata2(aOriginProps.mDirectory, timestamp, suffix, group,
9049 origin, isApp.SetValue());
9050 if (NS_FAILED(rv)) {
9051 aOriginProps.mTimestamp =
9052 GetLastModifiedTime(aOriginProps.mDirectory, mPersistent);
9053 aOriginProps.mNeedsRestore2 = true;
9054 } else {
9055 aOriginProps.mTimestamp = timestamp;
9058 *aRemoved = false;
9059 return NS_OK;
9062 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
9063 const OriginProps& aOriginProps) {
9064 AssertIsOnIOThread();
9066 bool stripped;
9067 nsresult rv = MaybeStripObsoleteOriginAttributes(aOriginProps, &stripped);
9068 if (NS_WARN_IF(NS_FAILED(rv))) {
9069 return rv;
9071 if (stripped) {
9072 return NS_OK;
9075 if (aOriginProps.mNeedsRestore) {
9076 rv = CreateDirectoryMetadata(aOriginProps.mDirectory,
9077 aOriginProps.mTimestamp, aOriginProps.mSuffix,
9078 aOriginProps.mGroup, aOriginProps.mOrigin);
9079 if (NS_WARN_IF(NS_FAILED(rv))) {
9080 return rv;
9084 if (aOriginProps.mNeedsRestore2) {
9085 rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
9086 aOriginProps.mTimestamp,
9087 /* aPersisted */ false, aOriginProps.mSuffix,
9088 aOriginProps.mGroup, aOriginProps.mOrigin);
9089 if (NS_WARN_IF(NS_FAILED(rv))) {
9090 return rv;
9094 return NS_OK;
9097 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
9098 OriginProps& aOriginProps, bool* aRemoved) {
9099 AssertIsOnIOThread();
9100 MOZ_ASSERT(aOriginProps.mDirectory);
9101 MOZ_ASSERT(aRemoved);
9103 nsresult rv =
9104 MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1);
9105 if (NS_WARN_IF(NS_FAILED(rv))) {
9106 return rv;
9109 int64_t timestamp;
9110 nsCString group;
9111 nsCString origin;
9112 Nullable<bool> isApp;
9113 rv = GetDirectoryMetadata(aOriginProps.mDirectory, timestamp, group, origin,
9114 isApp);
9115 if (NS_FAILED(rv) || isApp.IsNull()) {
9116 aOriginProps.mNeedsRestore = true;
9119 nsCString suffix;
9120 rv = GetDirectoryMetadata2(aOriginProps.mDirectory, timestamp, suffix, group,
9121 origin, isApp.SetValue());
9122 if (NS_FAILED(rv)) {
9123 aOriginProps.mTimestamp =
9124 GetLastModifiedTime(aOriginProps.mDirectory, mPersistent);
9125 aOriginProps.mNeedsRestore2 = true;
9126 } else {
9127 aOriginProps.mTimestamp = timestamp;
9130 *aRemoved = false;
9131 return NS_OK;
9134 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
9135 const OriginProps& aOriginProps) {
9136 AssertIsOnIOThread();
9138 nsresult rv;
9140 if (aOriginProps.mNeedsRestore) {
9141 rv = CreateDirectoryMetadata(aOriginProps.mDirectory,
9142 aOriginProps.mTimestamp, aOriginProps.mSuffix,
9143 aOriginProps.mGroup, aOriginProps.mOrigin);
9144 if (NS_WARN_IF(NS_FAILED(rv))) {
9145 return rv;
9149 if (aOriginProps.mNeedsRestore2) {
9150 rv = CreateDirectoryMetadata2(aOriginProps.mDirectory,
9151 aOriginProps.mTimestamp,
9152 /* aPersisted */ false, aOriginProps.mSuffix,
9153 aOriginProps.mGroup, aOriginProps.mOrigin);
9154 if (NS_WARN_IF(NS_FAILED(rv))) {
9155 return rv;
9159 return NS_OK;
9162 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
9163 AssertIsOnIOThread();
9165 nsresult rv;
9167 OriginProps originProps;
9168 rv = originProps.Init(mDirectory);
9169 if (NS_WARN_IF(NS_FAILED(rv))) {
9170 return rv;
9173 originProps.mTimestamp = GetLastModifiedTime(mDirectory, mPersistent);
9175 mOriginProps.AppendElement(std::move(originProps));
9177 rv = ProcessOriginDirectories();
9178 if (NS_WARN_IF(NS_FAILED(rv))) {
9179 return rv;
9182 return NS_OK;
9185 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
9186 const OriginProps& aOriginProps) {
9187 AssertIsOnIOThread();
9189 // We don't have any approach to restore aPersisted, so reset it to false.
9190 nsresult rv =
9191 CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp,
9192 /* aPersisted */ false, aOriginProps.mSuffix,
9193 aOriginProps.mGroup, aOriginProps.mOrigin);
9194 if (NS_WARN_IF(NS_FAILED(rv))) {
9195 return rv;
9198 return NS_OK;
9201 } // namespace quota
9202 } // namespace dom
9203 } // namespace mozilla