/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ActorsParent.h" #include "mozIStorageConnection.h" #include "mozIStorageService.h" #include "nsIBinaryInputStream.h" #include "nsIBinaryOutputStream.h" #include "nsIFile.h" #include "nsIFileStreams.h" #include "nsIObserverService.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" #include "nsIRunnable.h" #include "nsISimpleEnumerator.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptSecurityManager.h" #include "nsITimer.h" #include "nsIURI.h" #include "nsPIDOMWindow.h" #include #include "GeckoProfiler.h" #include "mozilla/Atomics.h" #include "mozilla/BasePrincipal.h" #include "mozilla/CondVar.h" #include "mozilla/dom/PContent.h" #include "mozilla/dom/asmjscache/AsmJSCache.h" #include "mozilla/dom/cache/QuotaClient.h" #include "mozilla/dom/indexedDB/ActorsParent.h" #include "mozilla/dom/quota/PQuotaParent.h" #include "mozilla/dom/quota/PQuotaRequestParent.h" #include "mozilla/dom/quota/PQuotaUsageRequestParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/IntegerRange.h" #include "mozilla/Mutex.h" #include "mozilla/LazyIdleThread.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/TypeTraits.h" #include "mozilla/Unused.h" #include "mozStorageCID.h" #include "mozStorageHelper.h" #include "nsAppDirectoryServiceDefs.h" #include "nsComponentManagerUtils.h" #include "nsAboutProtocolUtils.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsCRTGlue.h" #include "nsDirectoryServiceUtils.h" #include "nsEscape.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsScriptSecurityManager.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #include "prio.h" #include "xpcpublic.h" #include "OriginScope.h" #include "QuotaManager.h" #include "QuotaManagerService.h" #include "QuotaObject.h" #include "UsageInfo.h" #define DISABLE_ASSERTS_FOR_FUZZING 0 #if DISABLE_ASSERTS_FOR_FUZZING #define ASSERT_UNLESS_FUZZING(...) do { } while (0) #else #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) #endif // The amount of time, in milliseconds, that our IO thread will stay alive // after the last event it processes. #define DEFAULT_THREAD_TIMEOUT_MS 30000 // The amount of time, in milliseconds, that we will wait for active storage // transactions on shutdown before aborting them. #define DEFAULT_SHUTDOWN_TIMER_MS 30000 // Preference that users can set to override temporary storage smart limit // calculation. #define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit" #define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize" // Preference that is used to enable testing features #define PREF_TESTING_FEATURES "dom.quotaManager.testing" // profile-before-change, when we need to shut down quota manager #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm" #define KB * 1024ULL #define MB * 1024ULL KB #define GB * 1024ULL MB namespace mozilla { namespace dom { namespace quota { using namespace mozilla::ipc; // We want profiles to be platform-independent so we always need to replace // the same characters on every platform. Windows has the most extensive set // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and // FILE_PATH_SEPARATOR. const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; namespace { /******************************************************************************* * Constants ******************************************************************************/ const uint32_t kSQLitePageSizeOverride = 512; // Major storage version. Bump for backwards-incompatible changes. const uint32_t kMajorStorageVersion = 1; // Minor storage version. Bump for backwards-compatible changes. const uint32_t kMinorStorageVersion = 0; // The storage version we store in the SQLite database is a (signed) 32-bit // integer. The major version is left-shifted 16 bits so the max value is // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF. static_assert(kMajorStorageVersion <= 0xFFFF, "Major version needs to fit in 16 bits."); static_assert(kMinorStorageVersion <= 0xFFFF, "Minor version needs to fit in 16 bits."); const int32_t kStorageVersion = int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion); static_assert( static_cast(StorageType::Persistent) == static_cast(PERSISTENCE_TYPE_PERSISTENT), "Enum values should match."); static_assert( static_cast(StorageType::Temporary) == static_cast(PERSISTENCE_TYPE_TEMPORARY), "Enum values should match."); static_assert( static_cast(StorageType::Default) == static_cast(PERSISTENCE_TYPE_DEFAULT), "Enum values should match."); const char kChromeOrigin[] = "chrome"; const char kAboutHomeOriginPrefix[] = "moz-safe-about:home"; const char kIndexedDBOriginPrefix[] = "indexeddb://"; const char kResourceOriginPrefix[] = "resource://"; #define INDEXEDDB_DIRECTORY_NAME "indexedDB" #define STORAGE_DIRECTORY_NAME "storage" #define PERSISTENT_DIRECTORY_NAME "persistent" #define PERMANENT_DIRECTORY_NAME "permanent" #define TEMPORARY_DIRECTORY_NAME "temporary" #define DEFAULT_DIRECTORY_NAME "default" enum AppId { kNoAppId = nsIScriptSecurityManager::NO_APP_ID, kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID }; #define STORAGE_FILE_NAME "storage.sqlite" // The name of the file that we use to load/save the last access time of an // origin. #define METADATA_FILE_NAME ".metadata" #define METADATA_V2_FILE_NAME ".metadata-v2" /****************************************************************************** * SQLite functions ******************************************************************************/ #if 0 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion, uint32_t aMinorStorageVersion) { return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion); } #endif uint32_t GetMajorStorageVersion(int32_t aStorageVersion) { return uint32_t(aStorageVersion >> 16); } /****************************************************************************** * Quota manager class declarations ******************************************************************************/ } // namespace class DirectoryLockImpl final : public DirectoryLock { RefPtr mQuotaManager; const Nullable mPersistenceType; const nsCString mGroup; const OriginScope mOriginScope; const Nullable mIsApp; const Nullable mClientType; RefPtr mOpenListener; nsTArray mBlocking; nsTArray mBlockedOn; const bool mExclusive; // Internal quota manager operations use this flag to prevent directory lock // registraction/unregistration from updating origin access time, etc. const bool mInternal; bool mInvalidated; public: DirectoryLockImpl(QuotaManager* aQuotaManager, Nullable aPersistenceType, const nsACString& aGroup, const OriginScope& aOriginScope, Nullable aIsApp, Nullable aClientType, bool aExclusive, bool aInternal, OpenDirectoryListener* aOpenListener); void AssertIsOnOwningThread() const #ifdef DEBUG ; #else { } #endif const Nullable& GetPersistenceType() const { return mPersistenceType; } const nsACString& GetGroup() const { return mGroup; } const OriginScope& GetOriginScope() const { return mOriginScope; } const Nullable& GetIsApp() const { return mIsApp; } const Nullable& GetClientType() const { return mClientType; } bool IsInternal() const { return mInternal; } bool ShouldUpdateLockTable() { return !mInternal && mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT; } // Test whether this DirectoryLock needs to wait for the given lock. bool MustWaitFor(const DirectoryLockImpl& aLock); void AddBlockingLock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); mBlocking.AppendElement(aLock); } const nsTArray& GetBlockedOnLocks() { return mBlockedOn; } void AddBlockedOnLock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); mBlockedOn.AppendElement(aLock); } void MaybeUnblock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); mBlockedOn.RemoveElement(aLock); if (mBlockedOn.IsEmpty()) { NotifyOpenListener(); } } void NotifyOpenListener(); void Invalidate() { AssertIsOnOwningThread(); mInvalidated = true; } NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl) private: ~DirectoryLockImpl(); }; class QuotaManager::CreateRunnable final : public BackgroundThreadObject , public Runnable { nsTArray> mCallbacks; nsString mBaseDirPath; RefPtr mManager; nsresult mResultCode; enum class State { Initial, CreatingManager, RegisteringObserver, CallingCallbacks, Completed }; State mState; public: CreateRunnable() : mResultCode(NS_OK) , mState(State::Initial) { AssertIsOnBackgroundThread(); } void AddCallback(nsIRunnable* aCallback) { AssertIsOnOwningThread(); MOZ_ASSERT(aCallback); mCallbacks.AppendElement(aCallback); } private: ~CreateRunnable() { } nsresult Init(); nsresult CreateManager(); nsresult RegisterObserver(); void CallCallbacks(); State GetNextState(nsCOMPtr& aThread); NS_DECL_NSIRUNNABLE }; class QuotaManager::ShutdownRunnable final : public Runnable { // Only touched on the main thread. bool& mDone; public: explicit ShutdownRunnable(bool& aDone) : mDone(aDone) { MOZ_ASSERT(NS_IsMainThread()); } private: ~ShutdownRunnable() { } NS_DECL_NSIRUNNABLE }; class QuotaManager::ShutdownObserver final : public nsIObserver { nsCOMPtr mBackgroundThread; public: explicit ShutdownObserver(nsIEventTarget* aBackgroundThread) : mBackgroundThread(aBackgroundThread) { MOZ_ASSERT(NS_IsMainThread()); } NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER private: ~ShutdownObserver() { MOZ_ASSERT(NS_IsMainThread()); } }; namespace { /******************************************************************************* * Local class declarations ******************************************************************************/ } // namespace class OriginInfo final { friend class GroupInfo; friend class QuotaManager; friend class QuotaObject; public: OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, bool aIsApp, uint64_t aUsage, int64_t aAccessTime) : mGroupInfo(aGroupInfo), mOrigin(aOrigin), mUsage(aUsage), mAccessTime(aAccessTime), mIsApp(aIsApp) { MOZ_COUNT_CTOR(OriginInfo); } NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo) int64_t AccessTime() const { return mAccessTime; } private: // Private destructor, to discourage deletion outside of Release(): ~OriginInfo() { MOZ_COUNT_DTOR(OriginInfo); MOZ_ASSERT(!mQuotaObjects.Count()); } void LockedDecreaseUsage(int64_t aSize); void LockedUpdateAccessTime(int64_t aAccessTime) { AssertCurrentThreadOwnsQuotaMutex(); mAccessTime = aAccessTime; } nsDataHashtable mQuotaObjects; GroupInfo* mGroupInfo; const nsCString mOrigin; uint64_t mUsage; int64_t mAccessTime; const bool mIsApp; }; class OriginInfoLRUComparator { public: bool Equals(const OriginInfo* a, const OriginInfo* b) const { return a && b ? a->AccessTime() == b->AccessTime() : !a && !b ? true : false; } bool LessThan(const OriginInfo* a, const OriginInfo* b) const { return a && b ? a->AccessTime() < b->AccessTime() : b ? true : false; } }; class GroupInfo final { friend class GroupInfoPair; friend class OriginInfo; friend class QuotaManager; friend class QuotaObject; public: GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType, const nsACString& aGroup) : mGroupInfoPair(aGroupInfoPair), mPersistenceType(aPersistenceType), mGroup(aGroup), mUsage(0) { MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); MOZ_COUNT_CTOR(GroupInfo); } NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo) private: // Private destructor, to discourage deletion outside of Release(): ~GroupInfo() { MOZ_COUNT_DTOR(GroupInfo); } already_AddRefed LockedGetOriginInfo(const nsACString& aOrigin); void LockedAddOriginInfo(OriginInfo* aOriginInfo); void LockedRemoveOriginInfo(const nsACString& aOrigin); void LockedRemoveOriginInfos(); bool LockedHasOriginInfos() { AssertCurrentThreadOwnsQuotaMutex(); return !mOriginInfos.IsEmpty(); } nsTArray > mOriginInfos; GroupInfoPair* mGroupInfoPair; PersistenceType mPersistenceType; nsCString mGroup; uint64_t mUsage; }; class GroupInfoPair { friend class QuotaManager; friend class QuotaObject; public: GroupInfoPair() { MOZ_COUNT_CTOR(GroupInfoPair); } ~GroupInfoPair() { MOZ_COUNT_DTOR(GroupInfoPair); } private: already_AddRefed LockedGetGroupInfo(PersistenceType aPersistenceType) { AssertCurrentThreadOwnsQuotaMutex(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); RefPtr groupInfo = GetGroupInfoForPersistenceType(aPersistenceType); return groupInfo.forget(); } void LockedSetGroupInfo(PersistenceType aPersistenceType, GroupInfo* aGroupInfo) { AssertCurrentThreadOwnsQuotaMutex(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); RefPtr& groupInfo = GetGroupInfoForPersistenceType(aPersistenceType); groupInfo = aGroupInfo; } void LockedClearGroupInfo(PersistenceType aPersistenceType) { AssertCurrentThreadOwnsQuotaMutex(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); RefPtr& groupInfo = GetGroupInfoForPersistenceType(aPersistenceType); groupInfo = nullptr; } bool LockedHasGroupInfos() { AssertCurrentThreadOwnsQuotaMutex(); return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo; } RefPtr& GetGroupInfoForPersistenceType(PersistenceType aPersistenceType); RefPtr mTemporaryStorageGroupInfo; RefPtr mDefaultStorageGroupInfo; }; namespace { class CollectOriginsHelper final : public Runnable { uint64_t mMinSizeToBeFreed; Mutex& mMutex; CondVar mCondVar; // The members below are protected by mMutex. nsTArray> mLocks; uint64_t mSizeToBeFreed; bool mWaiting; public: CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed); // Blocks the current thread until origins are collected on the main thread. // The returned value contains an aggregate size of those origins. int64_t BlockAndReturnOriginsForEviction( nsTArray>& aLocks); private: ~CollectOriginsHelper() { } NS_IMETHOD Run() override; }; class OriginOperationBase : public BackgroundThreadObject , public Runnable { protected: nsresult mResultCode; enum State { // Not yet run. State_Initial, // Running initialization on the main thread. State_Initializing, // Running initialization on the owning thread. State_FinishingInit, // Running quota manager initialization on the owning thread. State_CreatingQuotaManager, // Running on the owning thread in the listener for OpenDirectory. State_DirectoryOpenPending, // Running on the IO thread. State_DirectoryWorkOpen, // Running on the owning thread after all work is done. State_UnblockingOpen, // All done. State_Complete }; private: State mState; bool mActorDestroyed; protected: bool mNeedsMainThreadInit; bool mNeedsQuotaManagerInit; public: void NoteActorDestroyed() { AssertIsOnOwningThread(); mActorDestroyed = true; } bool IsActorDestroyed() const { AssertIsOnOwningThread(); return mActorDestroyed; } protected: explicit OriginOperationBase( nsIEventTarget* aOwningThread = NS_GetCurrentThread()) : BackgroundThreadObject(aOwningThread) , mResultCode(NS_OK) , mState(State_Initial) , mActorDestroyed(false) , mNeedsMainThreadInit(false) , mNeedsQuotaManagerInit(false) { } // Reference counted. virtual ~OriginOperationBase() { MOZ_ASSERT(mState == State_Complete); MOZ_ASSERT(mActorDestroyed); } State GetState() const { return mState; } void SetState(State aState) { MOZ_ASSERT(mState == State_Initial); mState = aState; } void AdvanceState() { switch (mState) { case State_Initial: mState = State_Initializing; return; case State_Initializing: mState = State_FinishingInit; return; case State_FinishingInit: mState = State_CreatingQuotaManager; return; case State_CreatingQuotaManager: mState = State_DirectoryOpenPending; return; case State_DirectoryOpenPending: mState = State_DirectoryWorkOpen; return; case State_DirectoryWorkOpen: mState = State_UnblockingOpen; return; case State_UnblockingOpen: mState = State_Complete; return; default: MOZ_CRASH("Bad state!"); } } NS_IMETHOD Run() override; virtual nsresult DoInitOnMainThread() { return NS_OK; } virtual void Open() = 0; nsresult DirectoryOpen(); virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) = 0; void Finish(nsresult aResult); virtual void UnblockOpen() = 0; private: nsresult Init(); nsresult InitOnMainThread(); nsresult FinishInit(); nsresult QuotaManagerOpen(); nsresult DirectoryWork(); }; class FinalizeOriginEvictionOp : public OriginOperationBase { nsTArray> mLocks; public: FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread, nsTArray>& aLocks) : OriginOperationBase(aBackgroundThread) { MOZ_ASSERT(!NS_IsMainThread()); mLocks.SwapElements(aLocks); } void Dispatch(); void RunOnIOThreadImmediately(); private: ~FinalizeOriginEvictionOp() { } virtual void Open() override; virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; virtual void UnblockOpen() override; }; class NormalOriginOperationBase : public OriginOperationBase , public OpenDirectoryListener { RefPtr mDirectoryLock; protected: Nullable mPersistenceType; OriginScope mOriginScope; mozilla::Atomic mCanceled; const bool mExclusive; public: void RunImmediately() { MOZ_ASSERT(GetState() == State_Initial); MOZ_ALWAYS_SUCCEEDS(this->Run()); } protected: NormalOriginOperationBase(Nullable aPersistenceType, const OriginScope& aOriginScope, bool aExclusive) : mPersistenceType(aPersistenceType) , mOriginScope(aOriginScope) , mExclusive(aExclusive) { AssertIsOnOwningThread(); } ~NormalOriginOperationBase() { } private: NS_DECL_ISUPPORTS_INHERITED virtual void Open() override; virtual void UnblockOpen() override; // OpenDirectoryListener overrides. virtual void DirectoryLockAcquired(DirectoryLock* aLock) override; virtual void DirectoryLockFailed() override; // Used to send results before unblocking open. virtual void SendResults() = 0; }; class SaveOriginAccessTimeOp : public NormalOriginOperationBase { int64_t mTimestamp; public: SaveOriginAccessTimeOp(PersistenceType aPersistenceType, const nsACString& aOrigin, int64_t aTimestamp) : NormalOriginOperationBase(Nullable(aPersistenceType), OriginScope::FromOrigin(aOrigin), /* aExclusive */ false) , mTimestamp(aTimestamp) { AssertIsOnOwningThread(); } private: ~SaveOriginAccessTimeOp() { } virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; virtual void SendResults() override; }; /******************************************************************************* * Actor class declarations ******************************************************************************/ class Quota final : public PQuotaParent { #ifdef DEBUG bool mActorDestroyed; #endif public: Quota(); NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota) private: ~Quota(); void StartIdleMaintenance(); // IPDL methods. virtual void ActorDestroy(ActorDestroyReason aWhy) override; virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams) override; virtual bool RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) override; virtual bool DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) override; virtual PQuotaRequestParent* AllocPQuotaRequestParent(const RequestParams& aParams) override; virtual bool RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor, const RequestParams& aParams) override; virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override; virtual bool RecvStartIdleMaintenance() override; virtual bool RecvStopIdleMaintenance() override; }; class QuotaUsageRequestBase : public NormalOriginOperationBase , public PQuotaUsageRequestParent { public: // May be overridden by subclasses if they need to perform work on the // background thread before being run. virtual bool Init(Quota* aQuota); protected: QuotaUsageRequestBase() : NormalOriginOperationBase(Nullable(), OriginScope::FromNull(), /* aExclusive */ false) { } nsresult GetUsageForOrigin(QuotaManager* aQuotaManager, PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, UsageInfo* aUsageInfo); // Subclasses use this override to set the IPDL response value. virtual void GetResponse(UsageRequestResponse& aResponse) = 0; private: void SendResults() override; // IPDL methods. void ActorDestroy(ActorDestroyReason aWhy) override; bool RecvCancel() override; }; class GetUsageOp final : public QuotaUsageRequestBase { nsTArray mOriginUsages; nsDataHashtable mOriginUsagesIndex; bool mGetAll; public: explicit GetUsageOp(const UsageRequestParams& aParams); private: ~GetUsageOp() { } nsresult TraverseRepository(QuotaManager* aQuotaManager, PersistenceType aPersistenceType); nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; void GetResponse(UsageRequestResponse& aResponse) override; }; class GetOriginUsageOp final : public QuotaUsageRequestBase { // If mGetGroupUsage is false, we use mUsageInfo to record the origin usage // and the file usage. Otherwise, we use it to record the group usage and the // limit. UsageInfo mUsageInfo; const OriginUsageParams mParams; nsCString mSuffix; nsCString mGroup; bool mIsApp; bool mGetGroupUsage; public: explicit GetOriginUsageOp(const UsageRequestParams& aParams); MOZ_IS_CLASS_INIT bool Init(Quota* aQuota) override; private: ~GetOriginUsageOp() { } MOZ_IS_CLASS_INIT virtual nsresult DoInitOnMainThread() override; virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; void GetResponse(UsageRequestResponse& aResponse) override; }; class QuotaRequestBase : public NormalOriginOperationBase , public PQuotaRequestParent { public: // May be overridden by subclasses if they need to perform work on the // background thread before being run. virtual bool Init(Quota* aQuota); protected: explicit QuotaRequestBase(bool aExclusive) : NormalOriginOperationBase(Nullable(), OriginScope::FromNull(), aExclusive) { } // Subclasses use this override to set the IPDL response value. virtual void GetResponse(RequestResponse& aResponse) = 0; private: virtual void SendResults() override; // IPDL methods. virtual void ActorDestroy(ActorDestroyReason aWhy) override; }; class ResetOrClearOp final : public QuotaRequestBase { const bool mClear; public: explicit ResetOrClearOp(bool aClear) : QuotaRequestBase(/* aExclusive */ true) , mClear(aClear) { AssertIsOnOwningThread(); } private: ~ResetOrClearOp() { } void DeleteFiles(QuotaManager* aQuotaManager); virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; virtual void GetResponse(RequestResponse& aResponse) override; }; class OriginClearOp final : public QuotaRequestBase { const RequestParams mParams; const bool mMultiple; public: explicit OriginClearOp(const RequestParams& aParams); virtual bool Init(Quota* aQuota) override; private: ~OriginClearOp() { } virtual nsresult DoInitOnMainThread() override; void DeleteFiles(QuotaManager* aQuotaManager, PersistenceType aPersistenceType); virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override; virtual void GetResponse(RequestResponse& aResponse) override; }; /******************************************************************************* * Helper Functions ******************************************************************************/ template ::value> struct IntChecker { static void Assert(T aInt) { static_assert(mozilla::IsIntegral::value, "Not an integer!"); MOZ_ASSERT(aInt >= 0); } }; template struct IntChecker { static void Assert(T aInt) { static_assert(mozilla::IsIntegral::value, "Not an integer!"); } }; template void AssertNoOverflow(uint64_t aDest, T aArg) { IntChecker::Assert(aDest); IntChecker::Assert(aArg); MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg)); } template void AssertNoUnderflow(T aDest, U aArg) { IntChecker::Assert(aDest); IntChecker::Assert(aArg); MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg)); } } // namespace BackgroundThreadObject::BackgroundThreadObject() : mOwningThread(NS_GetCurrentThread()) { AssertIsOnOwningThread(); } BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread) : mOwningThread(aOwningThread) { } #ifdef DEBUG void BackgroundThreadObject::AssertIsOnOwningThread() const { AssertIsOnBackgroundThread(); MOZ_ASSERT(mOwningThread); bool current; MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(¤t))); MOZ_ASSERT(current); } #endif // DEBUG nsIEventTarget* BackgroundThreadObject::OwningThread() const { MOZ_ASSERT(mOwningThread); return mOwningThread; } bool IsOnIOThread() { QuotaManager* quotaManager = QuotaManager::Get(); NS_ASSERTION(quotaManager, "Must have a manager here!"); bool currentThread; return NS_SUCCEEDED(quotaManager->IOThread()-> IsOnCurrentThread(¤tThread)) && currentThread; } void AssertIsOnIOThread() { NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!"); } void AssertCurrentThreadOwnsQuotaMutex() { #ifdef DEBUG QuotaManager* quotaManager = QuotaManager::Get(); NS_ASSERTION(quotaManager, "Must have a manager here!"); quotaManager->AssertCurrentThreadOwnsQuotaMutex(); #endif } void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) { // Get leaf of file path for (const char* p = aFile; *p; ++p) { if (*p == '/' && *(p + 1)) { aFile = p + 1; } } nsContentUtils::LogSimpleConsoleError( NS_ConvertUTF8toUTF16(nsPrintfCString( "Quota %s: %s:%lu", aStr, aFile, aLine)), "quota"); } namespace { StaticRefPtr gInstance; bool gCreateFailed = false; StaticRefPtr gCreateRunnable; mozilla::Atomic gShutdown(false); // Constants for temporary storage limit computing. static const int32_t kDefaultFixedLimitKB = -1; static const uint32_t kDefaultChunkSizeKB = 10 * 1024; int32_t gFixedLimitKB = kDefaultFixedLimitKB; uint32_t gChunkSizeKB = kDefaultChunkSizeKB; bool gTestingEnabled = false; class StorageDirectoryHelper : public Runnable { mozilla::Mutex mMutex; mozilla::CondVar mCondVar; nsresult mMainThreadResultCode; bool mWaiting; protected: struct OriginProps; nsTArray mOriginProps; nsCOMPtr mDirectory; public: StorageDirectoryHelper(nsIFile* aDirectory) : mMutex("StorageDirectoryHelper::mMutex") , mCondVar(mMutex, "StorageDirectoryHelper::mCondVar") , mMainThreadResultCode(NS_OK) , mWaiting(true) , mDirectory(aDirectory) { AssertIsOnIOThread(); } protected: ~StorageDirectoryHelper() { } nsresult AddOriginDirectory(nsIFile* aDirectory, OriginProps** aOriginProps); nsresult ProcessOriginDirectories(); virtual nsresult DoProcessOriginDirectories() = 0; private: nsresult RunOnMainThread(); NS_IMETHOD Run() override; }; struct StorageDirectoryHelper::OriginProps { enum Type { eChrome, eContent }; nsCOMPtr mDirectory; nsCString mSpec; PrincipalOriginAttributes mAttrs; int64_t mTimestamp; nsCString mSuffix; nsCString mGroup; nsCString mOrigin; Type mType; bool mIsApp; bool mNeedsRestore; bool mIgnore; public: explicit OriginProps() : mTimestamp(0) , mType(eContent) , mIsApp(false) , mNeedsRestore(false) , mIgnore(false) { } }; class MOZ_STACK_CLASS OriginParser final { static bool IgnoreWhitespace(char16_t /* aChar */) { return false; } typedef nsCCharSeparatedTokenizerTemplate Tokenizer; enum SchemaType { eNone, eFile, eAbout }; enum State { eExpectingAppIdOrSchema, eExpectingInMozBrowser, eExpectingSchema, eExpectingEmptyToken1, eExpectingEmptyToken2, eExpectingEmptyToken3, eExpectingHost, eExpectingPort, eExpectingEmptyTokenOrDriveLetterOrPathnameComponent, eExpectingEmptyTokenOrPathnameComponent, eComplete, eHandledTrailingSeparator }; const nsCString mOrigin; const PrincipalOriginAttributes mOriginAttributes; Tokenizer mTokenizer; uint32_t mAppId; nsCString mSchema; nsCString mHost; Nullable mPort; nsTArray mPathnameComponents; nsCString mHandledTokens; SchemaType mSchemaType; State mState; bool mInIsolatedMozBrowser; bool mMaybeDriveLetter; bool mError; public: OriginParser(const nsACString& aOrigin, const PrincipalOriginAttributes& aOriginAttributes) : mOrigin(aOrigin) , mOriginAttributes(aOriginAttributes) , mTokenizer(aOrigin, '+') , mAppId(kNoAppId) , mPort() , mSchemaType(eNone) , mState(eExpectingAppIdOrSchema) , mInIsolatedMozBrowser(false) , mMaybeDriveLetter(false) , mError(false) { } static bool ParseOrigin(const nsACString& aOrigin, nsCString& aSpec, PrincipalOriginAttributes* aAttrs); bool Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs); private: void HandleSchema(const nsDependentCSubstring& aSchema); void HandlePathnameComponent(const nsDependentCSubstring& aSchema); void HandleToken(const nsDependentCSubstring& aToken); void HandleTrailingSeparator(); }; class CreateOrUpgradeDirectoryMetadataHelper final : public StorageDirectoryHelper { const bool mPersistent; public: CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory, bool aPersistent) : StorageDirectoryHelper(aDirectory) , mPersistent(aPersistent) { } nsresult CreateOrUpgradeMetadataFiles(); private: nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory); nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp, nsACString& aGroup, nsACString& aOrigin, bool* aHasIsApp); virtual nsresult DoProcessOriginDirectories(); }; class UpgradeDirectoryMetadataFrom1To2Helper final : public StorageDirectoryHelper { const bool mPersistent; public: UpgradeDirectoryMetadataFrom1To2Helper(nsIFile* aDirectory, bool aPersistent) : StorageDirectoryHelper(aDirectory) , mPersistent(aPersistent) { } nsresult UpgradeMetadataFiles(); private: nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp, nsACString& aGroup, nsACString& aOrigin, bool* aIsApp); virtual nsresult DoProcessOriginDirectories() override; }; class RestoreDirectoryMetadata2Helper final : public StorageDirectoryHelper { const bool mPersistent; public: RestoreDirectoryMetadata2Helper(nsIFile* aDirectory, bool aPersistent) : StorageDirectoryHelper(aDirectory) , mPersistent(aPersistent) { } nsresult RestoreMetadata2File(); private: virtual nsresult DoProcessOriginDirectories(); }; class OriginKey : public nsAutoCString { public: OriginKey(PersistenceType aPersistenceType, const nsACString& aOrigin) { PersistenceTypeToText(aPersistenceType, *this); Append(':'); Append(aOrigin); } }; void SanitizeOriginString(nsCString& aOrigin) { #ifdef XP_WIN NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars, FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR), "Illegal file characters have changed!"); #endif aOrigin.ReplaceChar(QuotaManager::kReplaceChars, '+'); } bool IsTreatedAsPersistent(PersistenceType aPersistenceType, bool aIsApp) { if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT || (aPersistenceType == PERSISTENCE_TYPE_DEFAULT && aIsApp)) { return true; } return false; } bool IsTreatedAsTemporary(PersistenceType aPersistenceType, bool aIsApp) { return !IsTreatedAsPersistent(aPersistenceType, aIsApp); } nsresult CloneStoragePath(nsIFile* aBaseDir, const nsAString& aStorageName, nsAString& aStoragePath) { nsresult rv; nsCOMPtr storageDir; rv = aBaseDir->Clone(getter_AddRefs(storageDir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = storageDir->Append(aStorageName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = storageDir->GetPath(aStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) { AssertIsOnIOThread(); MOZ_ASSERT(aFile); MOZ_ASSERT(aTimestamp); class MOZ_STACK_CLASS Helper final { public: static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) { AssertIsOnIOThread(); MOZ_ASSERT(aFile); MOZ_ASSERT(aTimestamp); bool isDirectory; nsresult rv = aFile->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { nsString leafName; rv = aFile->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (leafName.EqualsLiteral(METADATA_FILE_NAME) || leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { return NS_OK; } int64_t timestamp; rv = aFile->GetLastModifiedTime(×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Need to convert from milliseconds to microseconds. MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp); timestamp *= int64_t(PR_USEC_PER_MSEC); if (timestamp > *aTimestamp) { *aTimestamp = timestamp; } return NS_OK; } nsCOMPtr entries; rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr file = do_QueryInterface(entry); MOZ_ASSERT(file); rv = GetLastModifiedTime(file, aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } }; int64_t timestamp = INT64_MIN; nsresult rv = Helper::GetLastModifiedTime(aFile, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aTimestamp = timestamp; return NS_OK; } nsresult EnsureDirectory(nsIFile* aDirectory, bool* aCreated) { AssertIsOnIOThread(); nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { bool isDirectory; rv = aDirectory->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); *aCreated = false; } else { NS_ENSURE_SUCCESS(rv, rv); *aCreated = true; } return NS_OK; } enum FileFlag { kTruncateFileFlag, kUpdateFileFlag, kAppendFileFlag }; nsresult GetOutputStream(nsIFile* aDirectory, const nsAString& aFilename, FileFlag aFileFlag, nsIOutputStream** aStream) { AssertIsOnIOThread(); nsCOMPtr file; nsresult rv = aDirectory->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Append(aFilename); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr outputStream; switch (aFileFlag) { case kTruncateFileFlag: { rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } break; } case kUpdateFileFlag: { bool exists; rv = file->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { *aStream = nullptr; return NS_OK; } nsCOMPtr stream; rv = NS_NewLocalFileStream(getter_AddRefs(stream), file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } outputStream = do_QueryInterface(stream); if (NS_WARN_IF(!outputStream)) { return NS_ERROR_FAILURE; } break; } case kAppendFileFlag: { rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file, PR_WRONLY | PR_CREATE_FILE | PR_APPEND); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } break; } default: MOZ_CRASH("Should never get here!"); } outputStream.forget(aStream); return NS_OK; } nsresult GetBinaryOutputStream(nsIFile* aDirectory, const nsAString& aFilename, FileFlag aFileFlag, nsIBinaryOutputStream** aStream) { nsCOMPtr outputStream; nsresult rv = GetOutputStream(aDirectory, aFilename, aFileFlag, getter_AddRefs(outputStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr binaryStream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); if (NS_WARN_IF(!binaryStream)) { return NS_ERROR_FAILURE; } rv = binaryStream->SetOutputStream(outputStream); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } binaryStream.forget(aStream); return NS_OK; } void GetJarPrefix(uint32_t aAppId, bool aInIsolatedMozBrowser, nsACString& aJarPrefix) { MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { aAppId = nsIScriptSecurityManager::NO_APP_ID; } aJarPrefix.Truncate(); // Fallback. if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInIsolatedMozBrowser) { return; } // aJarPrefix = appId + "+" + { 't', 'f' } + "+"; aJarPrefix.AppendInt(aAppId); aJarPrefix.Append('+'); aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f'); aJarPrefix.Append('+'); } nsresult CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp, const nsACString& aSuffix, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp) { AssertIsOnIOThread(); PrincipalOriginAttributes groupAttributes; nsCString groupNoSuffix; bool ok = groupAttributes.PopulateFromOrigin(aGroup, groupNoSuffix); if (!ok) { return NS_ERROR_FAILURE; } nsCString groupPrefix; GetJarPrefix(groupAttributes.mAppId, groupAttributes.mInIsolatedMozBrowser, groupPrefix); nsCString group = groupPrefix + groupNoSuffix; PrincipalOriginAttributes originAttributes; nsCString originNoSuffix; ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix); if (!ok) { return NS_ERROR_FAILURE; } nsCString originPrefix; GetJarPrefix(originAttributes.mAppId, originAttributes.mInIsolatedMozBrowser, originPrefix); nsCString origin = originPrefix + originNoSuffix; MOZ_ASSERT(groupPrefix == originPrefix); nsCOMPtr stream; nsresult rv = GetBinaryOutputStream(aDirectory, NS_LITERAL_STRING(METADATA_FILE_NAME), kTruncateFileFlag, getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(stream); rv = stream->Write64(aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteStringZ(group.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteStringZ(origin.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteBoolean(aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult CreateDirectoryMetadata2(nsIFile* aDirectory, int64_t aTimestamp, const nsACString& aSuffix, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp) { AssertIsOnIOThread(); nsCOMPtr stream; nsresult rv = GetBinaryOutputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), kTruncateFileFlag, getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(stream); rv = stream->Write64(aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Reserved for navigator.persist() rv = stream->WriteBoolean(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Reserved data 1 rv = stream->Write32(0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Reserved data 2 rv = stream->Write32(0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteStringZ(PromiseFlatCString(aSuffix).get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stream->WriteBoolean(aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult GetBinaryInputStream(nsIFile* aDirectory, const nsAString& aFilename, nsIBinaryInputStream** aStream) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aStream); nsCOMPtr file; nsresult rv = aDirectory->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Append(aFilename); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stream; rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr bufferedStream; rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr binaryStream = do_CreateInstance("@mozilla.org/binaryinputstream;1"); if (NS_WARN_IF(!binaryStream)) { return NS_ERROR_FAILURE; } rv = binaryStream->SetInputStream(bufferedStream); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } binaryStream.forget(aStream); return NS_OK; } // This method computes and returns our best guess for the temporary storage // limit (in bytes), based on the amount of space users have free on their hard // drive and on given temporary storage usage (also in bytes). nsresult GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage, uint64_t* aLimit) { // Check for free space on device where temporary storage directory lives. int64_t bytesAvailable; nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!"); uint64_t availableKB = static_cast((bytesAvailable + aCurrentUsage) / 1024); // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case // we don't shrink temporary storage and evict origin data every time we // initialize. availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB; // Allow temporary storage to consume up to half the available space. uint64_t resultKB = availableKB * .50; *aLimit = resultKB * 1024; return NS_OK; } } // namespace /******************************************************************************* * Exported functions ******************************************************************************/ PQuotaParent* AllocPQuotaParent() { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaManager::IsShuttingDown())) { return nullptr; } RefPtr actor = new Quota(); return actor.forget().take(); } bool DeallocPQuotaParent(PQuotaParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } /******************************************************************************* * Directory lock ******************************************************************************/ DirectoryLockImpl::DirectoryLockImpl(QuotaManager* aQuotaManager, Nullable aPersistenceType, const nsACString& aGroup, const OriginScope& aOriginScope, Nullable aIsApp, Nullable aClientType, bool aExclusive, bool aInternal, OpenDirectoryListener* aOpenListener) : mQuotaManager(aQuotaManager) , mPersistenceType(aPersistenceType) , mGroup(aGroup) , mOriginScope(aOriginScope) , mIsApp(aIsApp) , mClientType(aClientType) , mOpenListener(aOpenListener) , mExclusive(aExclusive) , mInternal(aInternal) , mInvalidated(false) { AssertIsOnOwningThread(); MOZ_ASSERT(aQuotaManager); MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty()); MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull()); MOZ_ASSERT_IF(!aInternal, aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID); MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty()); MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin()); MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull()); MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull()); MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX); MOZ_ASSERT_IF(!aInternal, aOpenListener); } DirectoryLockImpl::~DirectoryLockImpl() { AssertIsOnOwningThread(); MOZ_ASSERT(mQuotaManager); for (DirectoryLockImpl* blockingLock : mBlocking) { blockingLock->MaybeUnblock(this); } mBlocking.Clear(); mQuotaManager->UnregisterDirectoryLock(this); } #ifdef DEBUG void DirectoryLockImpl::AssertIsOnOwningThread() const { MOZ_ASSERT(mQuotaManager); mQuotaManager->AssertIsOnOwningThread(); } #endif // DEBUG bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aExistingLock) { AssertIsOnOwningThread(); // Waiting is never required if the ops in comparison represent shared locks. if (!aExistingLock.mExclusive && !mExclusive) { return false; } // If the persistence types don't overlap, the op can proceed. if (!aExistingLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() && aExistingLock.mPersistenceType.Value() != mPersistenceType.Value()) { return false; } // If the origin scopes don't overlap, the op can proceed. bool match = aExistingLock.mOriginScope.Matches(mOriginScope); if (!match) { return false; } // If the client types don't overlap, the op can proceed. if (!aExistingLock.mClientType.IsNull() && !mClientType.IsNull() && aExistingLock.mClientType.Value() != mClientType.Value()) { return false; } // Otherwise, when all attributes overlap (persistence type, origin scope and // client type) the op must wait. return true; } void DirectoryLockImpl::NotifyOpenListener() { AssertIsOnOwningThread(); MOZ_ASSERT(mQuotaManager); MOZ_ASSERT(mOpenListener); if (mInvalidated) { mOpenListener->DirectoryLockFailed(); } else { mOpenListener->DirectoryLockAcquired(this); } mOpenListener = nullptr; mQuotaManager->RemovePendingDirectoryLock(this); } nsresult QuotaManager:: CreateRunnable::Init() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::Initial); nsresult rv; nsCOMPtr baseDir; rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, getter_AddRefs(baseDir)); if (NS_FAILED(rv)) { rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(baseDir)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = baseDir->GetPath(mBaseDirPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult QuotaManager:: CreateRunnable::CreateManager() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::CreatingManager); mManager = new QuotaManager(); nsresult rv = mManager->Init(mBaseDirPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult QuotaManager:: CreateRunnable::RegisterObserver() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::RegisteringObserver); if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT, kDefaultFixedLimitKB)) || NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB, PREF_CHUNK_SIZE, kDefaultChunkSizeKB))) { NS_WARNING("Unable to respond to temp storage pref changes!"); } if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled, PREF_TESTING_FEATURES, false))) { NS_WARNING("Unable to respond to testing pref changes!"); } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (NS_WARN_IF(!observerService)) { return NS_ERROR_FAILURE; } nsCOMPtr observer = new ShutdownObserver(mOwningThread); nsresult rv = observerService->AddObserver(observer, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // This service has to be started on the main thread currently. nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } QuotaManagerService* qms = QuotaManagerService::GetOrCreate(); if (NS_WARN_IF(!qms)) { return rv; } qms->NoteLiveManager(mManager); for (RefPtr& client : mManager->mClients) { client->DidInitialize(mManager); } return NS_OK; } void QuotaManager:: CreateRunnable::CallCallbacks() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::CallingCallbacks); gCreateRunnable = nullptr; if (NS_FAILED(mResultCode)) { gCreateFailed = true; } else { gInstance = mManager; } mManager = nullptr; nsTArray> callbacks; mCallbacks.SwapElements(callbacks); for (nsCOMPtr& callback : callbacks) { Unused << callback->Run(); } } auto QuotaManager:: CreateRunnable::GetNextState(nsCOMPtr& aThread) -> State { switch (mState) { case State::Initial: aThread = mOwningThread; return State::CreatingManager; case State::CreatingManager: aThread = do_GetMainThread(); return State::RegisteringObserver; case State::RegisteringObserver: aThread = mOwningThread; return State::CallingCallbacks; case State::CallingCallbacks: aThread = nullptr; return State::Completed; default: MOZ_CRASH("Bad state!"); } } NS_IMETHODIMP QuotaManager:: CreateRunnable::Run() { nsresult rv; switch (mState) { case State::Initial: rv = Init(); break; case State::CreatingManager: rv = CreateManager(); break; case State::RegisteringObserver: rv = RegisterObserver(); break; case State::CallingCallbacks: CallCallbacks(); rv = NS_OK; break; case State::Completed: default: MOZ_CRASH("Bad state!"); } nsCOMPtr thread; if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_SUCCEEDED(mResultCode)) { mResultCode = rv; } mState = State::CallingCallbacks; thread = mOwningThread; } else { mState = GetNextState(thread); } if (thread) { MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(this, NS_DISPATCH_NORMAL)); } return NS_OK; } NS_IMETHODIMP QuotaManager:: ShutdownRunnable::Run() { if (NS_IsMainThread()) { mDone = true; return NS_OK; } AssertIsOnBackgroundThread(); RefPtr quotaManager = gInstance.get(); if (quotaManager) { quotaManager->Shutdown(); gInstance = nullptr; } MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); return NS_OK; } NS_IMPL_ISUPPORTS(QuotaManager::ShutdownObserver, nsIObserver) NS_IMETHODIMP QuotaManager:: ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)); MOZ_ASSERT(gInstance); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (NS_WARN_IF(!observerService)) { return NS_ERROR_FAILURE; } // Unregister ourselves from the observer service first to make sure the // nested event loop below will not cause re-entrancy issues. Unused << observerService->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID); QuotaManagerService* qms = QuotaManagerService::Get(); MOZ_ASSERT(qms); qms->NoteShuttingDownManager(); for (RefPtr& client : gInstance->mClients) { client->WillShutdown(); } bool done = false; RefPtr shutdownRunnable = new ShutdownRunnable(done); MOZ_ALWAYS_SUCCEEDS( mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL)); nsIThread* currentThread = NS_GetCurrentThread(); MOZ_ASSERT(currentThread); while (!done) { MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); } return NS_OK; } /******************************************************************************* * Quota object ******************************************************************************/ void QuotaObject::AddRef() { QuotaManager* quotaManager = QuotaManager::Get(); if (!quotaManager) { NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); ++mRefCnt; return; } MutexAutoLock lock(quotaManager->mQuotaMutex); ++mRefCnt; } void QuotaObject::Release() { QuotaManager* quotaManager = QuotaManager::Get(); if (!quotaManager) { NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); nsrefcnt count = --mRefCnt; if (count == 0) { mRefCnt = 1; delete this; } return; } { MutexAutoLock lock(quotaManager->mQuotaMutex); --mRefCnt; if (mRefCnt > 0) { return; } if (mOriginInfo) { mOriginInfo->mQuotaObjects.Remove(mPath); } } delete this; } bool QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) { QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); MutexAutoLock lock(quotaManager->mQuotaMutex); if (mQuotaCheckDisabled) { return true; } if (mSize == aSize) { return true; } if (!mOriginInfo) { mSize = aSize; return true; } GroupInfo* groupInfo = mOriginInfo->mGroupInfo; MOZ_ASSERT(groupInfo); if (mSize > aSize) { if (aTruncate) { const int64_t delta = mSize - aSize; AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta); quotaManager->mTemporaryStorageUsage -= delta; AssertNoUnderflow(groupInfo->mUsage, delta); groupInfo->mUsage -= delta; AssertNoUnderflow(mOriginInfo->mUsage, delta); mOriginInfo->mUsage -= delta; mSize = aSize; } return true; } MOZ_ASSERT(mSize < aSize); RefPtr complementaryGroupInfo = groupInfo->mGroupInfoPair->LockedGetGroupInfo( ComplementaryPersistenceType(groupInfo->mPersistenceType)); uint64_t delta = aSize - mSize; AssertNoOverflow(mOriginInfo->mUsage, delta); uint64_t newUsage = mOriginInfo->mUsage + delta; // Temporary storage has no limit for origin usage (there's a group and the // global limit though). AssertNoOverflow(groupInfo->mUsage, delta); uint64_t newGroupUsage = groupInfo->mUsage + delta; uint64_t groupUsage = groupInfo->mUsage; if (complementaryGroupInfo) { AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage); groupUsage += complementaryGroupInfo->mUsage; } // Temporary storage has a hard limit for group usage (20 % of the global // limit). AssertNoOverflow(groupUsage, delta); if (groupUsage + delta > quotaManager->GetGroupLimit()) { return false; } AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta); uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta; if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) { // This will block the thread without holding the lock while waitting. AutoTArray, 10> locks; uint64_t sizeToBeFreed = quotaManager->LockedCollectOriginsForEviction(delta, locks); if (!sizeToBeFreed) { return false; } NS_ASSERTION(sizeToBeFreed >= delta, "Huh?"); { MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); for (RefPtr& lock : locks) { MOZ_ASSERT(!lock->GetPersistenceType().IsNull()); MOZ_ASSERT(lock->GetOriginScope().IsOrigin()); MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty()); quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType().Value(), lock->GetOriginScope().GetOrigin()); } } // Relocked. NS_ASSERTION(mOriginInfo, "How come?!"); for (DirectoryLockImpl* lock : locks) { MOZ_ASSERT(!lock->GetPersistenceType().IsNull()); MOZ_ASSERT(!lock->GetGroup().IsEmpty()); MOZ_ASSERT(lock->GetOriginScope().IsOrigin()); MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty()); MOZ_ASSERT(lock->GetOriginScope().GetOrigin() != mOriginInfo->mOrigin, "Deleted itself!"); quotaManager->LockedRemoveQuotaForOrigin( lock->GetPersistenceType().Value(), lock->GetGroup(), lock->GetOriginScope().GetOrigin()); } // We unlocked and relocked several times so we need to recompute all the // essential variables and recheck the group limit. AssertNoUnderflow(aSize, mSize); delta = aSize - mSize; AssertNoOverflow(mOriginInfo->mUsage, delta); newUsage = mOriginInfo->mUsage + delta; AssertNoOverflow(groupInfo->mUsage, delta); newGroupUsage = groupInfo->mUsage + delta; groupUsage = groupInfo->mUsage; if (complementaryGroupInfo) { AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage); groupUsage += complementaryGroupInfo->mUsage; } AssertNoOverflow(groupUsage, delta); if (groupUsage + delta > quotaManager->GetGroupLimit()) { // Unfortunately some other thread increased the group usage in the // meantime and we are not below the group limit anymore. // However, the origin eviction must be finalized in this case too. MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); quotaManager->FinalizeOriginEviction(locks); return false; } AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta); newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta; NS_ASSERTION(newTemporaryStorageUsage <= quotaManager->mTemporaryStorageLimit, "How come?!"); // Ok, we successfully freed enough space and the operation can continue // without throwing the quota error. mOriginInfo->mUsage = newUsage; groupInfo->mUsage = newGroupUsage; quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;; // Some other thread could increase the size in the meantime, but no more // than this one. MOZ_ASSERT(mSize < aSize); mSize = aSize; // Finally, release IO thread only objects and allow next synchronized // ops for the evicted origins. MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); quotaManager->FinalizeOriginEviction(locks); return true; } mOriginInfo->mUsage = newUsage; groupInfo->mUsage = newGroupUsage; quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage; mSize = aSize; return true; } void QuotaObject::DisableQuotaCheck() { QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); MutexAutoLock lock(quotaManager->mQuotaMutex); mQuotaCheckDisabled = true; } void QuotaObject::EnableQuotaCheck() { QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); MutexAutoLock lock(quotaManager->mQuotaMutex); mQuotaCheckDisabled = false; } /******************************************************************************* * Quota manager ******************************************************************************/ QuotaManager::QuotaManager() : mQuotaMutex("QuotaManager.mQuotaMutex"), mTemporaryStorageLimit(0), mTemporaryStorageUsage(0), mTemporaryStorageInitialized(false), mStorageInitialized(false) { AssertIsOnOwningThread(); MOZ_ASSERT(!gInstance); } QuotaManager::~QuotaManager() { AssertIsOnOwningThread(); MOZ_ASSERT(!gInstance || gInstance == this); } void QuotaManager::GetOrCreate(nsIRunnable* aCallback) { AssertIsOnBackgroundThread(); if (IsShuttingDown()) { MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!"); return; } if (gInstance || gCreateFailed) { MOZ_ASSERT(!gCreateRunnable); MOZ_ASSERT_IF(gCreateFailed, !gInstance); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback)); } else { if (!gCreateRunnable) { gCreateRunnable = new CreateRunnable(); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable)); } gCreateRunnable->AddCallback(aCallback); } } // static QuotaManager* QuotaManager::Get() { // Does not return an owning reference. return gInstance; } // static bool QuotaManager::IsShuttingDown() { return gShutdown; } auto QuotaManager::CreateDirectoryLock(Nullable aPersistenceType, const nsACString& aGroup, const OriginScope& aOriginScope, Nullable aIsApp, Nullable aClientType, bool aExclusive, bool aInternal, OpenDirectoryListener* aOpenListener) -> already_AddRefed { AssertIsOnOwningThread(); MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty()); MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull()); MOZ_ASSERT_IF(!aInternal, aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID); MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty()); MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin()); MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull()); MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull()); MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX); MOZ_ASSERT_IF(!aInternal, aOpenListener); RefPtr lock = new DirectoryLockImpl(this, aPersistenceType, aGroup, aOriginScope, aIsApp, aClientType, aExclusive, aInternal, aOpenListener); mPendingDirectoryLocks.AppendElement(lock); // See if this lock needs to wait. bool blocked = false; for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) { DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1]; if (lock->MustWaitFor(*existingLock)) { existingLock->AddBlockingLock(lock); lock->AddBlockedOnLock(existingLock); blocked = true; } } RegisterDirectoryLock(lock); // Otherwise, notify the open listener immediately. if (!blocked) { lock->NotifyOpenListener(); } return lock.forget(); } auto QuotaManager::CreateDirectoryLockForEviction(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp) -> already_AddRefed { AssertIsOnOwningThread(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID); MOZ_ASSERT(!aOrigin.IsEmpty()); RefPtr lock = new DirectoryLockImpl(this, Nullable(aPersistenceType), aGroup, OriginScope::FromOrigin(aOrigin), Nullable(aIsApp), Nullable(), /* aExclusive */ true, /* aInternal */ true, nullptr); #ifdef DEBUG for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) { DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1]; MOZ_ASSERT(!lock->MustWaitFor(*existingLock)); } #endif RegisterDirectoryLock(lock); return lock.forget(); } void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); MOZ_ASSERT(aLock); mDirectoryLocks.AppendElement(aLock); if (aLock->ShouldUpdateLockTable()) { const Nullable& persistenceType = aLock->GetPersistenceType(); const OriginScope& originScope = aLock->GetOriginScope(); MOZ_ASSERT(!persistenceType.IsNull()); MOZ_ASSERT(!aLock->GetGroup().IsEmpty()); MOZ_ASSERT(originScope.IsOrigin()); MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(persistenceType.Value()); nsTArray* array; if (!directoryLockTable.Get(originScope.GetOrigin(), &array)) { array = new nsTArray(); directoryLockTable.Put(originScope.GetOrigin(), array); if (!IsShuttingDown()) { UpdateOriginAccessTime(persistenceType.Value(), aLock->GetGroup(), originScope.GetOrigin()); } } array->AppendElement(aLock); } } void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(aLock)); if (aLock->ShouldUpdateLockTable()) { const Nullable& persistenceType = aLock->GetPersistenceType(); const OriginScope& originScope = aLock->GetOriginScope(); MOZ_ASSERT(!persistenceType.IsNull()); MOZ_ASSERT(!aLock->GetGroup().IsEmpty()); MOZ_ASSERT(originScope.IsOrigin()); MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); DirectoryLockTable& directoryLockTable = GetDirectoryLockTable(persistenceType.Value()); nsTArray* array; MOZ_ALWAYS_TRUE(directoryLockTable.Get(originScope.GetOrigin(), &array)); MOZ_ALWAYS_TRUE(array->RemoveElement(aLock)); if (array->IsEmpty()) { directoryLockTable.Remove(originScope.GetOrigin()); if (!IsShuttingDown()) { UpdateOriginAccessTime(persistenceType.Value(), aLock->GetGroup(), originScope.GetOrigin()); } } } } void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl* aLock) { AssertIsOnOwningThread(); MOZ_ASSERT(aLock); MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(aLock)); } uint64_t QuotaManager::CollectOriginsForEviction( uint64_t aMinSizeToBeFreed, nsTArray>& aLocks) { AssertIsOnOwningThread(); MOZ_ASSERT(aLocks.IsEmpty()); struct MOZ_STACK_CLASS Helper final { static void GetInactiveOriginInfos(nsTArray>& aOriginInfos, nsTArray& aLocks, nsTArray& aInactiveOriginInfos) { for (OriginInfo* originInfo : aOriginInfos) { MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType, originInfo->mIsApp)); OriginScope originScope = OriginScope::FromOrigin(originInfo->mOrigin); bool match = false; for (uint32_t j = aLocks.Length(); j > 0; j--) { DirectoryLockImpl* lock = aLocks[j - 1]; if (originScope.Matches(lock->GetOriginScope())) { match = true; break; } } if (!match) { MOZ_ASSERT(!originInfo->mQuotaObjects.Count(), "Inactive origin shouldn't have open files!"); aInactiveOriginInfos.InsertElementSorted(originInfo, OriginInfoLRUComparator()); } } } }; // Split locks into separate arrays and filter out locks for persistent // storage, they can't block us. nsTArray temporaryStorageLocks; nsTArray defaultStorageLocks; for (DirectoryLockImpl* lock : mDirectoryLocks) { const Nullable& persistenceType = lock->GetPersistenceType(); if (persistenceType.IsNull()) { temporaryStorageLocks.AppendElement(lock); defaultStorageLocks.AppendElement(lock); } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) { temporaryStorageLocks.AppendElement(lock); } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) { defaultStorageLocks.AppendElement(lock); } else { MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT); // Do nothing here, persistent origins don't need to be collected ever. } } nsTArray inactiveOrigins; // Enumerate and process inactive origins. This must be protected by the // mutex. MutexAutoLock lock(mQuotaMutex); for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { GroupInfoPair* pair = iter.UserData(); MOZ_ASSERT(!iter.Key().IsEmpty()); MOZ_ASSERT(pair); RefPtr groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); if (groupInfo) { Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos, temporaryStorageLocks, inactiveOrigins); } groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); if (groupInfo) { Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins); } } #ifdef DEBUG // Make sure the array is sorted correctly. for (uint32_t index = inactiveOrigins.Length(); index > 1; index--) { MOZ_ASSERT(inactiveOrigins[index - 1]->mAccessTime >= inactiveOrigins[index - 2]->mAccessTime); } #endif // Create a list of inactive and the least recently used origins // whose aggregate size is greater or equals the minimal size to be freed. uint64_t sizeToBeFreed = 0; for(uint32_t count = inactiveOrigins.Length(), index = 0; index < count; index++) { if (sizeToBeFreed >= aMinSizeToBeFreed) { inactiveOrigins.TruncateLength(index); break; } sizeToBeFreed += inactiveOrigins[index]->mUsage; } if (sizeToBeFreed >= aMinSizeToBeFreed) { // Success, add directory locks for these origins, so any other // operations for them will be delayed (until origin eviction is finalized). for (OriginInfo* originInfo : inactiveOrigins) { RefPtr lock = CreateDirectoryLockForEviction(originInfo->mGroupInfo->mPersistenceType, originInfo->mGroupInfo->mGroup, originInfo->mOrigin, originInfo->mIsApp); aLocks.AppendElement(lock.forget()); } return sizeToBeFreed; } return 0; } nsresult QuotaManager::Init(const nsAString& aBasePath) { nsresult rv; mBasePath = aBasePath; nsCOMPtr baseDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = baseDir->InitWithPath(aBasePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(INDEXEDDB_DIRECTORY_NAME), mIndexedDBPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = baseDir->Append(NS_LITERAL_STRING(STORAGE_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = baseDir->GetPath(mStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(PERMANENT_DIRECTORY_NAME), mPermanentStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(TEMPORARY_DIRECTORY_NAME), mTemporaryStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CloneStoragePath(baseDir, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME), mDefaultStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Make a lazy thread for any IO we need (like clearing or enumerating the // contents of storage directories). mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, NS_LITERAL_CSTRING("Storage I/O"), LazyIdleThread::ManualShutdown); // Make a timer here to avoid potential failures later. We don't actually // initialize the timer until shutdown. mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); if (NS_WARN_IF(!mShutdownTimer)) { return NS_ERROR_FAILURE; } static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::DOMCACHE == 2 && Client::TYPE_MAX == 3, "Fix the registration!"); MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX, "Should be using an auto array with correct capacity!"); // Register clients. mClients.AppendElement(indexedDB::CreateQuotaClient()); mClients.AppendElement(asmjscache::CreateClient()); mClients.AppendElement(cache::CreateQuotaClient()); return NS_OK; } void QuotaManager::Shutdown() { AssertIsOnOwningThread(); // Setting this flag prevents the service from being recreated and prevents // further storagess from being created. if (gShutdown.exchange(true)) { NS_ERROR("Shutdown more than once?!"); } StopIdleMaintenance(); // Kick off the shutdown timer. MOZ_ALWAYS_SUCCEEDS( mShutdownTimer->InitWithFuncCallback(&ShutdownTimerCallback, this, DEFAULT_SHUTDOWN_TIMER_MS, nsITimer::TYPE_ONE_SHOT)); // Each client will spin the event loop while we wait on all the threads // to close. Our timer may fire during that loop. for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { mClients[index]->ShutdownWorkThreads(); } // Cancel the timer regardless of whether it actually fired. if (NS_FAILED(mShutdownTimer->Cancel())) { NS_WARNING("Failed to cancel shutdown timer!"); } // NB: It's very important that runnable is destroyed on this thread // (i.e. after we join the IO thread) because we can't release the // QuotaManager on the IO thread. This should probably use // NewNonOwningRunnableMethod ... RefPtr runnable = NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects); MOZ_ASSERT(runnable); // Give clients a chance to cleanup IO thread only objects. if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { NS_WARNING("Failed to dispatch runnable!"); } // Make sure to join with our IO thread. if (NS_FAILED(mIOThread->Shutdown())) { NS_WARNING("Failed to shutdown IO thread!"); } for (RefPtr& lock : mPendingDirectoryLocks) { lock->Invalidate(); } } void QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, uint64_t aUsageBytes, int64_t aAccessTime) { AssertIsOnIOThread(); MOZ_ASSERT(IsTreatedAsTemporary(aPersistenceType, aIsApp)); MutexAutoLock lock(mQuotaMutex); GroupInfoPair* pair; if (!mGroupInfoPairs.Get(aGroup, &pair)) { pair = new GroupInfoPair(); mGroupInfoPairs.Put(aGroup, pair); // The hashtable is now responsible to delete the GroupInfoPair. } RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); if (!groupInfo) { groupInfo = new GroupInfo(pair, aPersistenceType, aGroup); pair->LockedSetGroupInfo(aPersistenceType, groupInfo); } RefPtr originInfo = new OriginInfo(groupInfo, aOrigin, aIsApp, aUsageBytes, aAccessTime); groupInfo->LockedAddOriginInfo(originInfo); } void QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, int64_t aSize) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); MutexAutoLock lock(mQuotaMutex); GroupInfoPair* pair; if (!mGroupInfoPairs.Get(aGroup, &pair)) { return; } RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); if (!groupInfo) { return; } RefPtr originInfo = groupInfo->LockedGetOriginInfo(aOrigin); if (originInfo) { originInfo->LockedDecreaseUsage(aSize); } } void QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin) { AssertIsOnOwningThread(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); MutexAutoLock lock(mQuotaMutex); GroupInfoPair* pair; if (!mGroupInfoPairs.Get(aGroup, &pair)) { return; } RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); if (!groupInfo) { return; } RefPtr originInfo = groupInfo->LockedGetOriginInfo(aOrigin); if (originInfo) { int64_t timestamp = PR_Now(); originInfo->LockedUpdateAccessTime(timestamp); MutexAutoUnlock autoUnlock(mQuotaMutex); RefPtr op = new SaveOriginAccessTimeOp(aPersistenceType, aOrigin, timestamp); op->RunImmediately(); } } void QuotaManager::RemoveQuota() { MutexAutoLock lock(mQuotaMutex); for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr& pair = iter.Data(); MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); MOZ_ASSERT(pair, "Null pointer!"); RefPtr groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); if (groupInfo) { groupInfo->LockedRemoveOriginInfos(); } groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); if (groupInfo) { groupInfo->LockedRemoveOriginInfos(); } iter.Remove(); } NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!"); } already_AddRefed QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, nsIFile* aFile) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { return nullptr; } nsString path; nsresult rv = aFile->GetPath(path); NS_ENSURE_SUCCESS(rv, nullptr); int64_t fileSize; bool exists; rv = aFile->Exists(&exists); NS_ENSURE_SUCCESS(rv, nullptr); if (exists) { rv = aFile->GetFileSize(&fileSize); NS_ENSURE_SUCCESS(rv, nullptr); } else { fileSize = 0; } // Re-escape our parameters above to make sure we get the right quota group. nsAutoCString group; rv = NS_EscapeURL(aGroup, esc_Query, group, fallible); NS_ENSURE_SUCCESS(rv, nullptr); nsAutoCString origin; rv = NS_EscapeURL(aOrigin, esc_Query, origin, fallible); NS_ENSURE_SUCCESS(rv, nullptr); RefPtr result; { MutexAutoLock lock(mQuotaMutex); GroupInfoPair* pair; if (!mGroupInfoPairs.Get(group, &pair)) { return nullptr; } RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); if (!groupInfo) { return nullptr; } RefPtr originInfo = groupInfo->LockedGetOriginInfo(origin); if (!originInfo) { return nullptr; } // We need this extra raw pointer because we can't assign to the smart // pointer directly since QuotaObject::AddRef would try to acquire the same // mutex. QuotaObject* quotaObject; if (!originInfo->mQuotaObjects.Get(path, "aObject)) { // Create a new QuotaObject. quotaObject = new QuotaObject(originInfo, path, fileSize); // Put it to the hashtable. The hashtable is not responsible to delete // the QuotaObject. originInfo->mQuotaObjects.Put(path, quotaObject); } // Addref the QuotaObject and move the ownership to the result. This must // happen before we unlock! result = quotaObject->LockedAddRef(); } // The caller becomes the owner of the QuotaObject, that is, the caller is // is responsible to delete it when the last reference is removed. return result.forget(); } already_AddRefed QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const nsAString& aPath) { nsresult rv; nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, nullptr); rv = file->InitWithPath(aPath); NS_ENSURE_SUCCESS(rv, nullptr); return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file); } void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) { AssertIsOnOwningThread(); for (RefPtr& client : mClients) { client->AbortOperationsForProcess(aContentParentId); } } nsresult QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType, const nsACString& aASCIIOrigin, nsIFile** aDirectory) const { nsresult rv; nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = directory->InitWithPath(GetStoragePath(aPersistenceType)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString originSanitized(aASCIIOrigin); SanitizeOriginString(originSanitized); rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized)); NS_ENSURE_SUCCESS(rv, rv); directory.forget(aDirectory); return NS_OK; } nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); MOZ_ASSERT(mStorageInitialized); RefPtr helper = new RestoreDirectoryMetadata2Helper(aDirectory, aPersistent); nsresult rv = helper->RestoreMetadata2File(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin, bool* aIsApp) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aTimestamp); MOZ_ASSERT(aIsApp); MOZ_ASSERT(mStorageInitialized); nsCOMPtr binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), getter_AddRefs(binaryStream)); NS_ENSURE_SUCCESS(rv, rv); uint64_t timestamp; rv = binaryStream->Read64(×tamp); NS_ENSURE_SUCCESS(rv, rv); bool persisted; rv = binaryStream->ReadBoolean(&persisted); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint32_t reservedData1; rv = binaryStream->Read32(&reservedData1); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint32_t reservedData2; rv = binaryStream->Read32(&reservedData2); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString suffix; rv = binaryStream->ReadCString(suffix); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString group; rv = binaryStream->ReadCString(group); NS_ENSURE_SUCCESS(rv, rv); nsCString origin; rv = binaryStream->ReadCString(origin); NS_ENSURE_SUCCESS(rv, rv); bool isApp; rv = binaryStream->ReadBoolean(&isApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aTimestamp = timestamp; aSuffix = suffix; aGroup = group; aOrigin = origin; *aIsApp = isApp; return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, int64_t* aTimestamp, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin, bool* aIsApp) { nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aSuffix, aGroup, aOrigin, aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aSuffix, aGroup, aOrigin, aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aTimestamp); MOZ_ASSERT(mStorageInitialized); nsCOMPtr binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), getter_AddRefs(binaryStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint64_t timestamp; rv = binaryStream->Read64(×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aTimestamp = timestamp; return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, int64_t* aTimestamp) { nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = GetDirectoryMetadata2(aDirectory, aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType) { MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY || aPersistenceType == PERSISTENCE_TYPE_DEFAULT); nsresult rv; nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directory->InitWithPath(GetStoragePath(aPersistenceType)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool created; rv = EnsureDirectory(directory, &created); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr entries; rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr childDirectory = do_QueryInterface(entry); MOZ_ASSERT(childDirectory); bool isDirectory; rv = childDirectory->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { nsString leafName; rv = childDirectory->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { continue; } QM_WARNING("Something (%s) in the repository that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); return NS_ERROR_UNEXPECTED; } int64_t timestamp; nsCString suffix; nsCString group; nsCString origin; bool isApp; rv = GetDirectoryMetadata2WithRestore(childDirectory, /* aPersistent */ false, ×tamp, suffix, group, origin, &isApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (IsTreatedAsPersistent(aPersistenceType, isApp)) { continue; } rv = InitializeOrigin(aPersistenceType, group, origin, isApp, timestamp, childDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } namespace { // The Cache API was creating top level morgue directories by accident for // a short time in nightly. This unfortunately prevents all storage from // working. So recover these profiles by removing these corrupt directories. // This should be removed at some point in the future. bool MaybeRemoveCorruptDirectory(const nsAString& aLeafName, nsIFile* aDir) { #ifdef NIGHTLY_BUILD MOZ_ASSERT(aDir); if (aLeafName != NS_LITERAL_STRING("morgue")) { return false; } NS_WARNING("QuotaManager removing corrupt morgue directory!"); nsresult rv = aDir->Remove(true /* recursive */); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return true; #else return false; #endif // NIGHTLY_BUILD } } // namespace nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, int64_t aAccessTime, nsIFile* aDirectory) { AssertIsOnIOThread(); nsresult rv; bool trackQuota = IsQuotaEnforced(aPersistenceType, aOrigin, aIsApp); // We need to initialize directories of all clients if they exists and also // get the total usage to initialize the quota. nsAutoPtr usageInfo; if (trackQuota) { usageInfo = new UsageInfo(); } nsCOMPtr entries; rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr file = do_QueryInterface(entry); NS_ENSURE_TRUE(file, NS_NOINTERFACE); nsString leafName; rv = file->GetLeafName(leafName); NS_ENSURE_SUCCESS(rv, rv); if (leafName.EqualsLiteral(METADATA_FILE_NAME) || leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { continue; } bool isDirectory; rv = file->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); if (!isDirectory) { NS_WARNING("Unknown file found!"); return NS_ERROR_UNEXPECTED; } if (MaybeRemoveCorruptDirectory(leafName, file)) { continue; } Client::Type clientType; rv = Client::TypeFromText(leafName, clientType); if (NS_FAILED(rv)) { NS_WARNING("Unknown directory found!"); return NS_ERROR_UNEXPECTED; } Atomic dummy(false); rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin, /* aCanceled */ dummy, usageInfo); NS_ENSURE_SUCCESS(rv, rv); } if (trackQuota) { InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, usageInfo->TotalUsage(), aAccessTime); } return NS_OK; } nsresult QuotaManager::MaybeUpgradeIndexedDBDirectory() { AssertIsOnIOThread(); nsresult rv; nsCOMPtr indexedDBDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = indexedDBDir->InitWithPath(mIndexedDBPath); NS_ENSURE_SUCCESS(rv, rv); bool exists; rv = indexedDBDir->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { // Nothing to upgrade. return NS_OK; } bool isDirectory; rv = indexedDBDir->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); if (!isDirectory) { NS_WARNING("indexedDB entry is not a directory!"); return NS_OK; } nsCOMPtr persistentStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = persistentStorageDir->InitWithPath(mStoragePath); NS_ENSURE_SUCCESS(rv, rv); rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); NS_ENSURE_SUCCESS(rv, rv); rv = persistentStorageDir->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { NS_WARNING("indexedDB directory shouldn't exist after the upgrade!"); return NS_OK; } nsCOMPtr storageDir; rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir)); NS_ENSURE_SUCCESS(rv, rv); // MoveTo() is atomic if the move happens on the same volume which should // be our case, so even if we crash in the middle of the operation nothing // breaks next time we try to initialize. // However there's a theoretical possibility that the indexedDB directory // is on different volume, but it should be rare enough that we don't have // to worry about it. rv = indexedDBDir->MoveTo(storageDir, NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult QuotaManager::MaybeUpgradePersistentStorageDirectory() { AssertIsOnIOThread(); nsresult rv; nsCOMPtr persistentStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = persistentStorageDir->InitWithPath(mStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = persistentStorageDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { // Nothing to upgrade. return NS_OK; } bool isDirectory; rv = persistentStorageDir->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { NS_WARNING("persistent entry is not a directory!"); return NS_OK; } nsCOMPtr defaultStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = defaultStorageDir->InitWithPath(mDefaultStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = defaultStorageDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { NS_WARNING("storage/persistent shouldn't exist after the upgrade!"); return NS_OK; } // Create real metadata files for origin directories in persistent storage. RefPtr helper = new CreateOrUpgradeDirectoryMetadataHelper(persistentStorageDir, /* aPersistent */ true); rv = helper->CreateOrUpgradeMetadataFiles(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Upgrade metadata files for origin directories in temporary storage. nsCOMPtr temporaryStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = temporaryStorageDir->InitWithPath(mTemporaryStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = temporaryStorageDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { rv = temporaryStorageDir->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { NS_WARNING("temporary entry is not a directory!"); return NS_OK; } helper = new CreateOrUpgradeDirectoryMetadataHelper(temporaryStorageDir, /* aPersistent */ false); rv = helper->CreateOrUpgradeMetadataFiles(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // And finally rename persistent to default. rv = persistentStorageDir->RenameTo(nullptr, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult QuotaManager::MaybeRemoveOldDirectories() { AssertIsOnIOThread(); nsresult rv; nsCOMPtr indexedDBDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = indexedDBDir->InitWithPath(mIndexedDBPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = indexedDBDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { QM_WARNING("Deleting old /indexedDB directory!"); rv = indexedDBDir->Remove(/* aRecursive */ true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } nsCOMPtr persistentStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = persistentStorageDir->InitWithPath(mStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = persistentStorageDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { QM_WARNING("Deleting old /storage/persistent directory!"); rv = persistentStorageDir->Remove(/* aRecursive */ true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult QuotaManager::UpgradeStorageFrom0ToCurrent(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); nsresult rv; for (const PersistenceType persistenceType : kAllPersistenceTypes) { nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directory->InitWithPath(GetStoragePath(persistenceType)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT; RefPtr helper = new UpgradeDirectoryMetadataFrom1To2Helper(directory, persistent); rv = helper->UpgradeMetadataFiles(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } #ifdef DEBUG { int32_t storageVersion; rv = aConnection->GetSchemaVersion(&storageVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(storageVersion == 0); } #endif rv = aConnection->SetSchemaVersion(kStorageVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } #if 0 nsresult QuotaManager::UpgradeStorageFrom1To2(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); nsresult rv = aConnection->SetSchemaVersion(MakeStorageVersion(2, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } #endif nsresult QuotaManager::EnsureStorageIsInitialized() { AssertIsOnIOThread(); if (mStorageInitialized) { return NS_OK; } nsresult rv; nsCOMPtr storageFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = storageFile->InitWithPath(mBasePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr connection; rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { // Nuke the database file. rv = storageFile->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // We want extra durability for this important file. rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA synchronous = EXTRA;" )); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Check to make sure that the storage version is correct. int32_t storageVersion; rv = connection->GetSchemaVersion(&storageVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (GetMajorStorageVersion(storageVersion) > kMajorStorageVersion) { NS_WARNING("Unable to initialize storage, version is too high!"); return NS_ERROR_FAILURE; } if (storageVersion < kStorageVersion) { const bool newDatabase = !storageVersion; if (newDatabase) { // Set the page size first. if (kSQLitePageSizeOverride) { rv = connection->ExecuteSimpleSQL( nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride) ); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } mozStorageTransaction transaction(connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); if (newDatabase) { rv = MaybeUpgradeIndexedDBDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = MaybeUpgradePersistentStorageDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = MaybeRemoveOldDirectories(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = UpgradeStorageFrom0ToCurrent(connection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&storageVersion))); MOZ_ASSERT(storageVersion == kStorageVersion); } else { // This logic needs to change next time we change the storage! static_assert(kStorageVersion == int32_t((1 << 16) + 0), "Upgrade function needed due to storage version increase."); while (storageVersion != kStorageVersion) { /* if (storageVersion == MakeStorageVersion(1, 0)) { rv = UpgradeStorageFrom1To2(connection); } else */ { NS_WARNING("Unable to initialize storage, no upgrade path is " "available!"); return NS_ERROR_FAILURE; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = connection->GetSchemaVersion(&storageVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } MOZ_ASSERT(storageVersion == kStorageVersion); } rv = transaction.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mStorageInitialized = true; return NS_OK; } void QuotaManager::OpenDirectory(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, Client::Type aClientType, bool aExclusive, OpenDirectoryListener* aOpenListener) { AssertIsOnOwningThread(); RefPtr lock = CreateDirectoryLock(Nullable(aPersistenceType), aGroup, OriginScope::FromOrigin(aOrigin), Nullable(aIsApp), Nullable(aClientType), aExclusive, false, aOpenListener); MOZ_ASSERT(lock); } void QuotaManager::OpenDirectoryInternal(Nullable aPersistenceType, const OriginScope& aOriginScope, Nullable aClientType, bool aExclusive, OpenDirectoryListener* aOpenListener) { AssertIsOnOwningThread(); RefPtr lock = CreateDirectoryLock(aPersistenceType, EmptyCString(), aOriginScope, Nullable(), Nullable(aClientType), aExclusive, true, aOpenListener); MOZ_ASSERT(lock); if (!aExclusive) { return; } // All the locks that block this new exclusive lock need to be invalidated. // We also need to notify clients to abort operations for them. AutoTArray>, Client::TYPE_MAX> origins; origins.SetLength(Client::TYPE_MAX); const nsTArray& blockedOnLocks = lock->GetBlockedOnLocks(); for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) { blockedOnLock->Invalidate(); if (!blockedOnLock->IsInternal()) { MOZ_ASSERT(!blockedOnLock->GetClientType().IsNull()); Client::Type clientType = blockedOnLock->GetClientType().Value(); MOZ_ASSERT(clientType < Client::TYPE_MAX); const OriginScope& originScope = blockedOnLock->GetOriginScope(); MOZ_ASSERT(originScope.IsOrigin()); MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); nsAutoPtr>& origin = origins[clientType]; if (!origin) { origin = new nsTHashtable(); } origin->PutEntry(originScope.GetOrigin()); } } for (uint32_t index : MakeRange(uint32_t(Client::TYPE_MAX))) { if (origins[index]) { for (auto iter = origins[index]->Iter(); !iter.Done(); iter.Next()) { MOZ_ASSERT(mClients[index]); mClients[index]->AbortOperations(iter.Get()->GetKey()); } } } } nsresult QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType, const nsACString& aSuffix, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, nsIFile** aDirectory) { AssertIsOnIOThread(); nsresult rv = EnsureStorageIsInitialized(); NS_ENSURE_SUCCESS(rv, rv); // Get directory for this origin and persistence type. nsCOMPtr directory; rv = GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, rv); if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { if (mInitializedOrigins.Contains(OriginKey(aPersistenceType, aOrigin))) { directory.forget(aDirectory); return NS_OK; } } else if (!mTemporaryStorageInitialized) { rv = InitializeRepository(aPersistenceType); if (NS_WARN_IF(NS_FAILED(rv))) { // We have to cleanup partially initialized quota. RemoveQuota(); return rv; } rv = InitializeRepository(ComplementaryPersistenceType(aPersistenceType)); if (NS_WARN_IF(NS_FAILED(rv))) { // We have to cleanup partially initialized quota. RemoveQuota(); return rv; } if (gFixedLimitKB >= 0) { mTemporaryStorageLimit = static_cast(gFixedLimitKB) * 1024; } else { nsCOMPtr storageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = storageDir->InitWithPath(GetStoragePath()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = GetTemporaryStorageLimit(storageDir, mTemporaryStorageUsage, &mTemporaryStorageLimit); NS_ENSURE_SUCCESS(rv, rv); } mTemporaryStorageInitialized = true; CheckTemporaryStorageLimits(); } int64_t timestamp; bool created; rv = EnsureDirectory(directory, &created); NS_ENSURE_SUCCESS(rv, rv); if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { if (created) { timestamp = PR_Now(); rv = CreateDirectoryMetadata(directory, timestamp, aSuffix, aGroup, aOrigin, aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CreateDirectoryMetadata2(directory, timestamp, aSuffix, aGroup, aOrigin, aIsApp); NS_ENSURE_SUCCESS(rv, rv); } else { bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; rv = GetDirectoryMetadata2WithRestore(directory, persistent, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(timestamp <= PR_Now()); } rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp, directory); NS_ENSURE_SUCCESS(rv, rv); mInitializedOrigins.AppendElement(OriginKey(aPersistenceType, aOrigin)); } else if (created) { timestamp = PR_Now(); rv = CreateDirectoryMetadata(directory, timestamp, aSuffix, aGroup, aOrigin, aIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CreateDirectoryMetadata2(directory, timestamp, aSuffix, aGroup, aOrigin, aIsApp); NS_ENSURE_SUCCESS(rv, rv); rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp, directory); NS_ENSURE_SUCCESS(rv, rv); } directory.forget(aDirectory); return NS_OK; } void QuotaManager::OriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin, bool aIsApp) { AssertIsOnIOThread(); if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { mInitializedOrigins.RemoveElement(OriginKey(aPersistenceType, aOrigin)); } for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { mClients[index]->OnOriginClearCompleted(aPersistenceType, aOrigin); } } void QuotaManager::ResetOrClearCompleted() { AssertIsOnIOThread(); mInitializedOrigins.Clear(); mTemporaryStorageInitialized = false; mStorageInitialized = false; ReleaseIOThreadObjects(); } Client* QuotaManager::GetClient(Client::Type aClientType) { MOZ_ASSERT(aClientType >= Client::IDB); MOZ_ASSERT(aClientType < Client::TYPE_MAX); return mClients.ElementAt(aClientType); } uint64_t QuotaManager::GetGroupLimit() const { MOZ_ASSERT(mTemporaryStorageInitialized); // To avoid one group evicting all the rest, limit the amount any one group // can use to 20%. To prevent individual sites from using exorbitant amounts // of storage where there is a lot of free space, cap the group limit to 2GB. uint64_t x = std::min(mTemporaryStorageLimit * .20, 2 GB); // In low-storage situations, make an exception (while not exceeding the total // storage limit). return std::min(mTemporaryStorageLimit, std::max(x, 10 MB)); } void QuotaManager::GetGroupUsageAndLimit(const nsACString& aGroup, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); MOZ_ASSERT(aUsageInfo); { MutexAutoLock lock(mQuotaMutex); aUsageInfo->SetLimit(GetGroupLimit()); aUsageInfo->ResetUsage(); GroupInfoPair* pair; if (!mGroupInfoPairs.Get(aGroup, &pair)) { return; } // Calculate temporary group usage RefPtr temporaryGroupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); if (temporaryGroupInfo) { aUsageInfo->AppendToDatabaseUsage(temporaryGroupInfo->mUsage); } // Calculate default group usage RefPtr defaultGroupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); if (defaultGroupInfo) { aUsageInfo->AppendToDatabaseUsage(defaultGroupInfo->mUsage); } } } // static void QuotaManager::GetStorageId(PersistenceType aPersistenceType, const nsACString& aOrigin, Client::Type aClientType, nsACString& aDatabaseId) { nsAutoCString str; str.AppendInt(aPersistenceType); str.Append('*'); str.Append(aOrigin); str.Append('*'); str.AppendInt(aClientType); aDatabaseId = str; } // static nsresult QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal, nsACString* aSuffix, nsACString* aGroup, nsACString* aOrigin, bool* aIsApp) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { GetInfoForChrome(aSuffix, aGroup, aOrigin, aIsApp); return NS_OK; } if (aPrincipal->GetIsNullPrincipal()) { NS_WARNING("IndexedDB not supported from this principal!"); return NS_ERROR_FAILURE; } nsCString origin; nsresult rv = aPrincipal->GetOrigin(origin); NS_ENSURE_SUCCESS(rv, rv); if (origin.EqualsLiteral(kChromeOrigin)) { NS_WARNING("Non-chrome principal can't use chrome origin!"); return NS_ERROR_FAILURE; } nsCString suffix; BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix); if (aSuffix) { aSuffix->Assign(suffix); } if (aGroup) { nsCString baseDomain; rv = aPrincipal->GetBaseDomain(baseDomain); if (NS_FAILED(rv)) { // A hack for JetPack. nsCOMPtr uri; rv = aPrincipal->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); bool isIndexedDBURI = false; rv = uri->SchemeIs("indexedDB", &isIndexedDBURI); NS_ENSURE_SUCCESS(rv, rv); if (isIndexedDBURI) { rv = NS_OK; } } NS_ENSURE_SUCCESS(rv, rv); if (baseDomain.IsEmpty()) { aGroup->Assign(origin); } else { aGroup->Assign(baseDomain + suffix); } } if (aOrigin) { aOrigin->Assign(origin); } if (aIsApp) { *aIsApp = aPrincipal->GetAppStatus() != nsIPrincipal::APP_STATUS_NOT_INSTALLED; } return NS_OK; } // static nsresult QuotaManager::GetInfoFromWindow(nsPIDOMWindowOuter* aWindow, nsACString* aSuffix, nsACString* aGroup, nsACString* aOrigin, bool* aIsApp) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aWindow); nsCOMPtr sop = do_QueryInterface(aWindow); NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); nsCOMPtr principal = sop->GetPrincipal(); NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); nsresult rv = GetInfoFromPrincipal(principal, aSuffix, aGroup, aOrigin, aIsApp); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // static void QuotaManager::GetInfoForChrome(nsACString* aSuffix, nsACString* aGroup, nsACString* aOrigin, bool* aIsApp) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode()); if (aSuffix) { aSuffix->Assign(EmptyCString()); } if (aGroup) { ChromeOrigin(*aGroup); } if (aOrigin) { ChromeOrigin(*aOrigin); } if (aIsApp) { *aIsApp = false; } } // static bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) { // The first prompt is not required for these origins. if (aOrigin.EqualsLiteral(kChromeOrigin) || StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) || StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) || StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) { return true; } return false; } // static bool QuotaManager::IsFirstPromptRequired(PersistenceType aPersistenceType, const nsACString& aOrigin, bool aIsApp) { if (IsTreatedAsTemporary(aPersistenceType, aIsApp)) { return false; } return !IsOriginInternal(aOrigin); } // static bool QuotaManager::IsQuotaEnforced(PersistenceType aPersistenceType, const nsACString& aOrigin, bool aIsApp) { return IsTreatedAsTemporary(aPersistenceType, aIsApp); } // static void QuotaManager::ChromeOrigin(nsACString& aOrigin) { aOrigin.AssignLiteral(kChromeOrigin); } uint64_t QuotaManager::LockedCollectOriginsForEviction( uint64_t aMinSizeToBeFreed, nsTArray>& aLocks) { mQuotaMutex.AssertCurrentThreadOwns(); RefPtr helper = new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed); // Unlock while calling out to XPCOM (code behind the dispatch method needs // to acquire its own lock which can potentially lead to a deadlock and it // also calls an observer that can do various stuff like IO, so it's better // to not hold our mutex while that happens). { MutexAutoUnlock autoUnlock(mQuotaMutex); MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL)); } return helper->BlockAndReturnOriginsForEviction(aLocks); } void QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin) { mQuotaMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); GroupInfoPair* pair; mGroupInfoPairs.Get(aGroup, &pair); if (!pair) { return; } RefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); if (groupInfo) { groupInfo->LockedRemoveOriginInfo(aOrigin); if (!groupInfo->LockedHasOriginInfos()) { pair->LockedClearGroupInfo(aPersistenceType); if (!pair->LockedHasGroupInfos()) { mGroupInfoPairs.Remove(aGroup); } } } } void QuotaManager::CheckTemporaryStorageLimits() { AssertIsOnIOThread(); nsTArray doomedOriginInfos; { MutexAutoLock lock(mQuotaMutex); for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { GroupInfoPair* pair = iter.UserData(); MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); MOZ_ASSERT(pair, "Null pointer!"); uint64_t groupUsage = 0; RefPtr temporaryGroupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); if (temporaryGroupInfo) { groupUsage += temporaryGroupInfo->mUsage; } RefPtr defaultGroupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); if (defaultGroupInfo) { groupUsage += defaultGroupInfo->mUsage; } if (groupUsage > 0) { QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager, "Shouldn't be null!"); if (groupUsage > quotaManager->GetGroupLimit()) { nsTArray originInfos; if (temporaryGroupInfo) { originInfos.AppendElements(temporaryGroupInfo->mOriginInfos); } if (defaultGroupInfo) { originInfos.AppendElements(defaultGroupInfo->mOriginInfos); } originInfos.Sort(OriginInfoLRUComparator()); for (uint32_t i = 0; i < originInfos.Length(); i++) { OriginInfo* originInfo = originInfos[i]; doomedOriginInfos.AppendElement(originInfo); groupUsage -= originInfo->mUsage; if (groupUsage <= quotaManager->GetGroupLimit()) { break; } } } } } uint64_t usage = 0; for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { usage += doomedOriginInfos[index]->mUsage; } if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) { nsTArray originInfos; for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { GroupInfoPair* pair = iter.UserData(); MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); MOZ_ASSERT(pair, "Null pointer!"); RefPtr groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); if (groupInfo) { originInfos.AppendElements(groupInfo->mOriginInfos); } groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); if (groupInfo) { originInfos.AppendElements(groupInfo->mOriginInfos); } } for (uint32_t index = originInfos.Length(); index > 0; index--) { if (doomedOriginInfos.Contains(originInfos[index - 1])) { originInfos.RemoveElementAt(index - 1); } } originInfos.Sort(OriginInfoLRUComparator()); for (uint32_t i = 0; i < originInfos.Length(); i++) { if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) { originInfos.TruncateLength(i); break; } usage += originInfos[i]->mUsage; } doomedOriginInfos.AppendElements(originInfos); } } for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { OriginInfo* doomedOriginInfo = doomedOriginInfos[index]; DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType, doomedOriginInfo->mOrigin); } nsTArray doomedOrigins; { MutexAutoLock lock(mQuotaMutex); for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { OriginInfo* doomedOriginInfo = doomedOriginInfos[index]; PersistenceType persistenceType = doomedOriginInfo->mGroupInfo->mPersistenceType; nsCString group = doomedOriginInfo->mGroupInfo->mGroup; nsCString origin = doomedOriginInfo->mOrigin; bool isApp = doomedOriginInfo->mIsApp; LockedRemoveQuotaForOrigin(persistenceType, group, origin); #ifdef DEBUG doomedOriginInfos[index] = nullptr; #endif doomedOrigins.AppendElement(OriginParams(persistenceType, origin, isApp)); } } for (const OriginParams& doomedOrigin : doomedOrigins) { OriginClearCompleted(doomedOrigin.mPersistenceType, doomedOrigin.mOrigin, doomedOrigin.mIsApp); } } void QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType, const nsACString& aOrigin) { nsCOMPtr directory; nsresult rv = GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(directory)); NS_ENSURE_SUCCESS_VOID(rv); rv = directory->Remove(true); if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { // This should never fail if we've closed all storage connections // correctly... NS_ERROR("Failed to remove directory!"); } } void QuotaManager::FinalizeOriginEviction( nsTArray>& aLocks) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); RefPtr op = new FinalizeOriginEvictionOp(mOwningThread, aLocks); if (IsOnIOThread()) { op->RunOnIOThreadImmediately(); } else { op->Dispatch(); } } void QuotaManager::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) { AssertIsOnBackgroundThread(); auto quotaManager = static_cast(aClosure); MOZ_ASSERT(quotaManager); NS_WARNING("Some storage operations are taking longer than expected " "during shutdown and will be aborted!"); // Abort all operations. for (RefPtr& client : quotaManager->mClients) { client->AbortOperations(NullCString()); } } auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType) -> DirectoryLockTable& { switch (aPersistenceType) { case PERSISTENCE_TYPE_TEMPORARY: return mTemporaryDirectoryLockTable; case PERSISTENCE_TYPE_DEFAULT: return mDefaultDirectoryLockTable; case PERSISTENCE_TYPE_PERSISTENT: case PERSISTENCE_TYPE_INVALID: default: MOZ_CRASH("Bad persistence type value!"); } } /******************************************************************************* * Local class implementations ******************************************************************************/ void OriginInfo::LockedDecreaseUsage(int64_t aSize) { AssertCurrentThreadOwnsQuotaMutex(); AssertNoUnderflow(mUsage, aSize); mUsage -= aSize; AssertNoUnderflow(mGroupInfo->mUsage, aSize); mGroupInfo->mUsage -= aSize; QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize); quotaManager->mTemporaryStorageUsage -= aSize; } already_AddRefed GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin) { AssertCurrentThreadOwnsQuotaMutex(); for (RefPtr& originInfo : mOriginInfos) { if (originInfo->mOrigin == aOrigin) { RefPtr result = originInfo; return result.forget(); } } return nullptr; } void GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo) { AssertCurrentThreadOwnsQuotaMutex(); NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo), "Replacing an existing entry!"); mOriginInfos.AppendElement(aOriginInfo); AssertNoOverflow(mUsage, aOriginInfo->mUsage); mUsage += aOriginInfo->mUsage; QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage); quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage; } void GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) { AssertCurrentThreadOwnsQuotaMutex(); for (uint32_t index = 0; index < mOriginInfos.Length(); index++) { if (mOriginInfos[index]->mOrigin == aOrigin) { AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage); mUsage -= mOriginInfos[index]->mUsage; QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, mOriginInfos[index]->mUsage); quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage; mOriginInfos.RemoveElementAt(index); return; } } } void GroupInfo::LockedRemoveOriginInfos() { AssertCurrentThreadOwnsQuotaMutex(); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); for (uint32_t index = mOriginInfos.Length(); index > 0; index--) { OriginInfo* originInfo = mOriginInfos[index - 1]; AssertNoUnderflow(mUsage, originInfo->mUsage); mUsage -= originInfo->mUsage; AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage); quotaManager->mTemporaryStorageUsage -= originInfo->mUsage; mOriginInfos.RemoveElementAt(index - 1); } } RefPtr& GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType) { switch (aPersistenceType) { case PERSISTENCE_TYPE_TEMPORARY: return mTemporaryStorageGroupInfo; case PERSISTENCE_TYPE_DEFAULT: return mDefaultStorageGroupInfo; case PERSISTENCE_TYPE_PERSISTENT: case PERSISTENCE_TYPE_INVALID: default: MOZ_CRASH("Bad persistence type value!"); } } CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed) : mMinSizeToBeFreed(aMinSizeToBeFreed), mMutex(aMutex), mCondVar(aMutex, "CollectOriginsHelper::mCondVar"), mSizeToBeFreed(0), mWaiting(true) { MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); mMutex.AssertCurrentThreadOwns(); } int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction( nsTArray>& aLocks) { MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); mMutex.AssertCurrentThreadOwns(); while (mWaiting) { mCondVar.Wait(); } mLocks.SwapElements(aLocks); return mSizeToBeFreed; } NS_IMETHODIMP CollectOriginsHelper::Run() { AssertIsOnBackgroundThread(); QuotaManager* quotaManager = QuotaManager::Get(); NS_ASSERTION(quotaManager, "Shouldn't be null!"); // We use extra stack vars here to avoid race detector warnings (the same // memory accessed with and without the lock held). nsTArray> locks; uint64_t sizeToBeFreed = quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks); MutexAutoLock lock(mMutex); NS_ASSERTION(mWaiting, "Huh?!"); mLocks.SwapElements(locks); mSizeToBeFreed = sizeToBeFreed; mWaiting = false; mCondVar.Notify(); return NS_OK; } /******************************************************************************* * OriginOperationBase ******************************************************************************/ NS_IMETHODIMP OriginOperationBase::Run() { nsresult rv; switch (mState) { case State_Initial: { rv = Init(); break; } case State_Initializing: { rv = InitOnMainThread(); break; } case State_FinishingInit: { rv = FinishInit(); break; } case State_CreatingQuotaManager: { rv = QuotaManagerOpen(); break; } case State_DirectoryOpenPending: { rv = DirectoryOpen(); break; } case State_DirectoryWorkOpen: { rv = DirectoryWork(); break; } case State_UnblockingOpen: { UnblockOpen(); return NS_OK; } default: MOZ_CRASH("Bad state!"); } if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) { Finish(rv); } return NS_OK; } nsresult OriginOperationBase::DirectoryOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State_DirectoryOpenPending); QuotaManager* quotaManager = QuotaManager::Get(); if (NS_WARN_IF(!quotaManager)) { return NS_ERROR_FAILURE; } // Must set this before dispatching otherwise we will race with the IO thread. AdvanceState(); nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_FAILURE; } return NS_OK; } void OriginOperationBase::Finish(nsresult aResult) { if (NS_SUCCEEDED(mResultCode)) { mResultCode = aResult; } // Must set mState before dispatching otherwise we will race with the main // thread. mState = State_UnblockingOpen; MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); } nsresult OriginOperationBase::Init() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State_Initial); AdvanceState(); if (mNeedsMainThreadInit) { MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); } else { AdvanceState(); MOZ_ALWAYS_SUCCEEDS(Run()); } return NS_OK; } nsresult OriginOperationBase::InitOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State_Initializing); nsresult rv = DoInitOnMainThread(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } AdvanceState(); MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } nsresult OriginOperationBase::FinishInit() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State_FinishingInit); if (QuotaManager::IsShuttingDown()) { return NS_ERROR_FAILURE; } AdvanceState(); if (mNeedsQuotaManagerInit && !QuotaManager::Get()) { QuotaManager::GetOrCreate(this); } else { Open(); } return NS_OK; } nsresult OriginOperationBase::QuotaManagerOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State_CreatingQuotaManager); if (NS_WARN_IF(!QuotaManager::Get())) { return NS_ERROR_FAILURE; } Open(); return NS_OK; } nsresult OriginOperationBase::DirectoryWork() { AssertIsOnIOThread(); MOZ_ASSERT(mState == State_DirectoryWorkOpen); QuotaManager* quotaManager = QuotaManager::Get(); if (NS_WARN_IF(!quotaManager)) { return NS_ERROR_FAILURE; } nsresult rv; if (mNeedsQuotaManagerInit) { rv = quotaManager->EnsureStorageIsInitialized(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = DoDirectoryWork(quotaManager); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Must set mState before dispatching otherwise we will race with the owning // thread. AdvanceState(); MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } void FinalizeOriginEvictionOp::Dispatch() { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(GetState() == State_Initial); SetState(State_DirectoryOpenPending); MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); } void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() { AssertIsOnIOThread(); MOZ_ASSERT(GetState() == State_Initial); SetState(State_DirectoryWorkOpen); MOZ_ALWAYS_SUCCEEDS(this->Run()); } void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); } nsresult FinalizeOriginEvictionOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); PROFILER_LABEL("Quota", "FinalizeOriginEvictionOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); for (RefPtr& lock : mLocks) { aQuotaManager->OriginClearCompleted(lock->GetPersistenceType().Value(), lock->GetOriginScope().GetOrigin(), lock->GetIsApp().Value()); } return NS_OK; } void FinalizeOriginEvictionOp::UnblockOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(GetState() == State_UnblockingOpen); #ifdef DEBUG NoteActorDestroyed(); #endif mLocks.Clear(); AdvanceState(); } NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable) void NormalOriginOperationBase::Open() { AssertIsOnOwningThread(); MOZ_ASSERT(GetState() == State_CreatingQuotaManager); MOZ_ASSERT(QuotaManager::Get()); AdvanceState(); QuotaManager::Get()->OpenDirectoryInternal(mPersistenceType, mOriginScope, Nullable(), mExclusive, this); } void NormalOriginOperationBase::UnblockOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(GetState() == State_UnblockingOpen); SendResults(); mDirectoryLock = nullptr; AdvanceState(); } void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) { AssertIsOnOwningThread(); MOZ_ASSERT(aLock); MOZ_ASSERT(GetState() == State_DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); mDirectoryLock = aLock; nsresult rv = DirectoryOpen(); if (NS_WARN_IF(NS_FAILED(rv))) { Finish(rv); return; } } void NormalOriginOperationBase::DirectoryLockFailed() { AssertIsOnOwningThread(); MOZ_ASSERT(GetState() == State_DirectoryOpenPending); MOZ_ASSERT(!mDirectoryLock); Finish(NS_ERROR_FAILURE); } nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); MOZ_ASSERT(!mPersistenceType.IsNull()); MOZ_ASSERT(mOriginScope.IsOrigin()); PROFILER_LABEL("Quota", "SaveOriginAccessTimeOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); nsCOMPtr directory; nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(), mOriginScope.GetOrigin(), getter_AddRefs(directory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stream; rv = GetBinaryOutputStream(directory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), kUpdateFileFlag, getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // The origin directory may not exist anymore. if (stream) { rv = stream->Write64(mTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } void SaveOriginAccessTimeOp::SendResults() { #ifdef DEBUG NoteActorDestroyed(); #endif } /******************************************************************************* * Quota ******************************************************************************/ Quota::Quota() #ifdef DEBUG : mActorDestroyed(false) #endif { } Quota::~Quota() { MOZ_ASSERT(mActorDestroyed); } void Quota::StartIdleMaintenance() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!QuotaManager::IsShuttingDown()); QuotaManager* quotaManager = QuotaManager::Get(); if (NS_WARN_IF(!quotaManager)) { return; } quotaManager->StartIdleMaintenance(); } void Quota::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnBackgroundThread(); #ifdef DEBUG MOZ_ASSERT(!mActorDestroyed); mActorDestroyed = true; #endif } PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None); RefPtr actor; switch (aParams.type()) { case UsageRequestParams::TAllUsageParams: actor = new GetUsageOp(aParams); break; case UsageRequestParams::TOriginUsageParams: actor = new GetOriginUsageOp(aParams); break; default: MOZ_CRASH("Should never get here!"); } MOZ_ASSERT(actor); // Transfer ownership to IPDL. return actor.forget().take(); } bool Quota::RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None); auto* op = static_cast(aActor); if (NS_WARN_IF(!op->Init(this))) { return false; } op->RunImmediately(); return true; } bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } PQuotaRequestParent* Quota::AllocPQuotaRequestParent(const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aParams.type() != RequestParams::T__None); if (aParams.type() == RequestParams::TClearOriginsParams) { PBackgroundParent* actor = Manager(); MOZ_ASSERT(actor); if (BackgroundParent::IsOtherProcessActor(actor)) { ASSERT_UNLESS_FUZZING(); return nullptr; } } RefPtr actor; switch (aParams.type()) { case RequestParams::TClearOriginParams: case RequestParams::TClearOriginsParams: actor = new OriginClearOp(aParams); break; case RequestParams::TClearAllParams: actor = new ResetOrClearOp(/* aClear */ true); break; case RequestParams::TResetAllParams: actor = new ResetOrClearOp(/* aClear */ false); break; default: MOZ_CRASH("Should never get here!"); } MOZ_ASSERT(actor); // Transfer ownership to IPDL. return actor.forget().take(); } bool Quota::RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor, const RequestParams& aParams) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(aParams.type() != RequestParams::T__None); auto* op = static_cast(aActor); if (NS_WARN_IF(!op->Init(this))) { return false; } op->RunImmediately(); return true; } bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); // Transfer ownership back from IPDL. RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } bool Quota::RecvStartIdleMaintenance() { AssertIsOnBackgroundThread(); PBackgroundParent* actor = Manager(); MOZ_ASSERT(actor); if (BackgroundParent::IsOtherProcessActor(actor)) { ASSERT_UNLESS_FUZZING(); return false; } if (QuotaManager::IsShuttingDown()) { return true; } QuotaManager* quotaManager = QuotaManager::Get(); if (!quotaManager) { nsCOMPtr callback = NewRunnableMethod(this, &Quota::StartIdleMaintenance); QuotaManager::GetOrCreate(callback); return true; } quotaManager->StartIdleMaintenance(); return true; } bool Quota::RecvStopIdleMaintenance() { AssertIsOnBackgroundThread(); PBackgroundParent* actor = Manager(); MOZ_ASSERT(actor); if (BackgroundParent::IsOtherProcessActor(actor)) { ASSERT_UNLESS_FUZZING(); return false; } if (QuotaManager::IsShuttingDown()) { return true; } QuotaManager* quotaManager = QuotaManager::Get(); if (!quotaManager) { return true; } quotaManager->StopIdleMaintenance(); return true; } bool QuotaUsageRequestBase::Init(Quota* aQuota) { AssertIsOnOwningThread(); MOZ_ASSERT(aQuota); mNeedsQuotaManagerInit = true; return true; } nsresult QuotaUsageRequestBase::GetUsageForOrigin(QuotaManager* aQuotaManager, PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, bool aIsApp, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); MOZ_ASSERT(aQuotaManager); MOZ_ASSERT(aUsageInfo); MOZ_ASSERT(aUsageInfo->TotalUsage() == 0); nsCOMPtr directory; nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, rv); bool exists; rv = directory->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); // If the directory exists then enumerate all the files inside, adding up // the sizes to get the final usage statistic. if (exists && !mCanceled) { bool initialized; if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { nsCString originKey = OriginKey(aPersistenceType, aOrigin); initialized = aQuotaManager->IsOriginInitialized(originKey); } else { initialized = aQuotaManager->IsTemporaryStorageInitialized(); } nsCOMPtr entries; rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore && !mCanceled) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr file = do_QueryInterface(entry); NS_ENSURE_TRUE(file, NS_NOINTERFACE); nsString leafName; rv = file->GetLeafName(leafName); NS_ENSURE_SUCCESS(rv, rv); if (leafName.EqualsLiteral(METADATA_FILE_NAME) || leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { continue; } if (!initialized) { bool isDirectory; rv = file->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); if (!isDirectory) { NS_WARNING("Unknown file found!"); return NS_ERROR_UNEXPECTED; } } if (MaybeRemoveCorruptDirectory(leafName, file)) { continue; } Client::Type clientType; rv = Client::TypeFromText(leafName, clientType); if (NS_FAILED(rv)) { NS_WARNING("Unknown directory found!"); if (!initialized) { return NS_ERROR_UNEXPECTED; } continue; } Client* client = aQuotaManager->GetClient(clientType); MOZ_ASSERT(client); if (initialized) { rv = client->GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, mCanceled, aUsageInfo); } else { rv = client->InitOrigin(aPersistenceType, aGroup, aOrigin, mCanceled, aUsageInfo); } NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } void QuotaUsageRequestBase::SendResults() { AssertIsOnOwningThread(); if (IsActorDestroyed()) { if (NS_SUCCEEDED(mResultCode)) { mResultCode = NS_ERROR_FAILURE; } } else { if (mCanceled) { mResultCode = NS_ERROR_FAILURE; } UsageRequestResponse response; if (NS_SUCCEEDED(mResultCode)) { GetResponse(response); } else { response = mResultCode; } Unused << PQuotaUsageRequestParent::Send__delete__(this, response); } } void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); NoteActorDestroyed(); } bool QuotaUsageRequestBase::RecvCancel() { AssertIsOnOwningThread(); if (mCanceled.exchange(true)) { NS_WARNING("Canceled more than once?!"); return false; } return true; } GetUsageOp::GetUsageOp(const UsageRequestParams& aParams) : mGetAll(aParams.get_AllUsageParams().getAll()) { AssertIsOnOwningThread(); MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams); } nsresult GetUsageOp::TraverseRepository(QuotaManager* aQuotaManager, PersistenceType aPersistenceType) { AssertIsOnIOThread(); MOZ_ASSERT(aQuotaManager); nsresult rv; nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = directory->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { return NS_OK; } nsCOMPtr entries; rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore && !mCanceled) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr originDir = do_QueryInterface(entry); MOZ_ASSERT(originDir); bool isDirectory; rv = originDir->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { nsString leafName; rv = originDir->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { QM_WARNING("Something (%s) in the repository that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); } continue; } int64_t timestamp; nsCString suffix; nsCString group; nsCString origin; bool isApp; rv = aQuotaManager->GetDirectoryMetadata2WithRestore(originDir, persistent, ×tamp, suffix, group, origin, &isApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mGetAll && aQuotaManager->IsOriginInternal(origin)) { continue; } OriginUsage* originUsage; // We can't store pointers to OriginUsage objects in the hashtable // since AppendElement() reallocates its internal array buffer as number // of elements grows. uint32_t index; if (mOriginUsagesIndex.Get(origin, &index)) { originUsage = &mOriginUsages[index]; } else { index = mOriginUsages.Length(); originUsage = mOriginUsages.AppendElement(); originUsage->origin() = origin; originUsage->persisted() = false; originUsage->usage() = 0; mOriginUsagesIndex.Put(origin, index); } UsageInfo usageInfo; rv = GetUsageForOrigin(aQuotaManager, aPersistenceType, group, origin, isApp, &usageInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } originUsage->usage() = originUsage->usage() + usageInfo.TotalUsage(); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); PROFILER_LABEL("Quota", "GetUsageOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); nsresult rv; for (const PersistenceType type : kAllPersistenceTypes) { rv = TraverseRepository(aQuotaManager, type); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) { AssertIsOnOwningThread(); aResponse = AllUsageResponse(); if (!mOriginUsages.IsEmpty()) { nsTArray& originUsages = aResponse.get_AllUsageResponse().originUsages(); mOriginUsages.SwapElements(originUsages); } } GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams) : mParams(aParams.get_OriginUsageParams()) , mGetGroupUsage(aParams.get_OriginUsageParams().getGroupUsage()) { AssertIsOnOwningThread(); MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams); } bool GetOriginUsageOp::Init(Quota* aQuota) { AssertIsOnOwningThread(); MOZ_ASSERT(aQuota); if (NS_WARN_IF(!QuotaUsageRequestBase::Init(aQuota))) { return false; } mNeedsMainThreadInit = true; return true; } nsresult GetOriginUsageOp::DoInitOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(GetState() == State_Initializing); MOZ_ASSERT(mNeedsMainThreadInit); const PrincipalInfo& principalInfo = mParams.principalInfo(); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Figure out which origin we're dealing with. nsCString origin; rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin, &mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mOriginScope.SetFromOrigin(origin); return NS_OK; } nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); MOZ_ASSERT(mUsageInfo.TotalUsage() == 0); PROFILER_LABEL("Quota", "GetOriginUsageOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); nsresult rv; if (mGetGroupUsage) { nsCOMPtr directory; // Ensure origin is initialized first. It will initialize all origins for // temporary storage including origins belonging to our group. rv = aQuotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_TEMPORARY, mSuffix, mGroup, mOriginScope.GetOrigin(), mIsApp, getter_AddRefs(directory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Get cached usage and limit (the method doesn't have to stat any files). aQuotaManager->GetGroupUsageAndLimit(mGroup, &mUsageInfo); return NS_OK; } // Add all the persistent/temporary/default storage files we care about. for (const PersistenceType type : kAllPersistenceTypes) { UsageInfo usageInfo; rv = GetUsageForOrigin(aQuotaManager, type, mGroup, mOriginScope.GetOrigin(), mIsApp, &usageInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mUsageInfo.Append(usageInfo); } return NS_OK; } void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) { AssertIsOnOwningThread(); OriginUsageResponse usageResponse; // We'll get the group usage when mGetGroupUsage is true and get the // origin usage when mGetGroupUsage is false. usageResponse.usage() = mUsageInfo.TotalUsage(); if (mGetGroupUsage) { usageResponse.limit() = mUsageInfo.Limit(); } else { usageResponse.fileUsage() = mUsageInfo.FileUsage(); } aResponse = usageResponse; } bool QuotaRequestBase::Init(Quota* aQuota) { AssertIsOnOwningThread(); MOZ_ASSERT(aQuota); mNeedsQuotaManagerInit = true; return true; } void QuotaRequestBase::SendResults() { AssertIsOnOwningThread(); if (IsActorDestroyed()) { if (NS_SUCCEEDED(mResultCode)) { mResultCode = NS_ERROR_FAILURE; } } else { RequestResponse response; if (NS_SUCCEEDED(mResultCode)) { GetResponse(response); } else { response = mResultCode; } Unused << PQuotaRequestParent::Send__delete__(this, response); } } void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) { AssertIsOnOwningThread(); NoteActorDestroyed(); } void ResetOrClearOp::DeleteFiles(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); MOZ_ASSERT(aQuotaManager); nsresult rv; nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = directory->InitWithPath(aQuotaManager->GetStoragePath()); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = directory->Remove(true); if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { // This should never fail if we've closed all storage connections // correctly... MOZ_ASSERT(false, "Failed to remove storage directory!"); } nsCOMPtr storageFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = storageFile->InitWithPath(aQuotaManager->GetBasePath()); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = storageFile->Remove(true); if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { // This should never fail if we've closed the storage connection // correctly... MOZ_ASSERT(false, "Failed to remove storage file!"); } } nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); PROFILER_LABEL("Quota", "ResetOrClearOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); if (mClear) { DeleteFiles(aQuotaManager); } aQuotaManager->RemoveQuota(); aQuotaManager->ResetOrClearCompleted(); return NS_OK; } void ResetOrClearOp::GetResponse(RequestResponse& aResponse) { AssertIsOnOwningThread(); if (mClear) { aResponse = ClearAllResponse(); } else { aResponse = ResetAllResponse(); } } OriginClearOp::OriginClearOp(const RequestParams& aParams) : QuotaRequestBase(/* aExclusive */ true) , mParams(aParams) , mMultiple(aParams.type() == RequestParams::TClearOriginsParams) { MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams || aParams.type() == RequestParams::TClearOriginsParams); } bool OriginClearOp::Init(Quota* aQuota) { AssertIsOnOwningThread(); MOZ_ASSERT(aQuota); if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) { return false; } if (!mMultiple) { const ClearOriginParams& params = mParams.get_ClearOriginParams(); if (params.persistenceTypeIsExplicit()) { MOZ_ASSERT(params.persistenceType() != PERSISTENCE_TYPE_INVALID); mPersistenceType.SetValue(params.persistenceType()); } } mNeedsMainThreadInit = true; return true; } nsresult OriginClearOp::DoInitOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(GetState() == State_Initializing); MOZ_ASSERT(mNeedsMainThreadInit); if (mMultiple) { const ClearOriginsParams& params = mParams.get_ClearOriginsParams(); mOriginScope.SetFromJSONPattern(params.pattern()); } else { const ClearOriginParams& params = mParams.get_ClearOriginParams(); const PrincipalInfo& principalInfo = params.principalInfo(); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Figure out which origin we're dealing with. nsCString origin; rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (params.clearAll()) { mOriginScope.SetFromPrefix(origin); } else { mOriginScope.SetFromOrigin(origin); } } return NS_OK; } void OriginClearOp::DeleteFiles(QuotaManager* aQuotaManager, PersistenceType aPersistenceType) { AssertIsOnIOThread(); MOZ_ASSERT(aQuotaManager); nsresult rv; nsCOMPtr directory = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return; } rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr entries; if (NS_WARN_IF(NS_FAILED( directory->GetDirectoryEntries(getter_AddRefs(entries)))) || !entries) { return; } OriginScope originScope = mOriginScope.Clone(); if (originScope.IsOrigin()) { nsCString originSanitized(originScope.GetOrigin()); SanitizeOriginString(originSanitized); originScope.SetOrigin(originSanitized); } else if (originScope.IsPrefix()) { nsCString prefixSanitized(originScope.GetPrefix()); SanitizeOriginString(prefixSanitized); originScope.SetPrefix(prefixSanitized); } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr file = do_QueryInterface(entry); MOZ_ASSERT(file); bool isDirectory; rv = file->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsString leafName; rv = file->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (!isDirectory) { if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { QM_WARNING("Something (%s) in the repository that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); } continue; } // Skip the origin directory if it doesn't match the pattern. if (!originScope.MatchesOrigin(OriginScope::FromOrigin( NS_ConvertUTF16toUTF8(leafName)))) { continue; } bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; int64_t timestamp; nsCString suffix; nsCString group; nsCString origin; bool isApp; rv = aQuotaManager->GetDirectoryMetadata2WithRestore(file, persistent, ×tamp, suffix, group, origin, &isApp); if (NS_WARN_IF(NS_FAILED(rv))) { return; } for (uint32_t index = 0; index < 10; index++) { // We can't guarantee that this will always succeed on Windows... if (NS_SUCCEEDED((rv = file->Remove(true)))) { break; } NS_WARNING("Failed to remove directory, retrying after a short delay."); PR_Sleep(PR_MillisecondsToInterval(200)); } if (NS_FAILED(rv)) { NS_WARNING("Failed to remove directory, giving up!"); } if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) { aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin); } aQuotaManager->OriginClearCompleted(aPersistenceType, origin, isApp); } } nsresult OriginClearOp::DoDirectoryWork(QuotaManager* aQuotaManager) { AssertIsOnIOThread(); PROFILER_LABEL("Quota", "OriginClearOp::DoDirectoryWork", js::ProfileEntry::Category::OTHER); if (mPersistenceType.IsNull()) { for (const PersistenceType type : kAllPersistenceTypes) { DeleteFiles(aQuotaManager, type); } } else { DeleteFiles(aQuotaManager, mPersistenceType.Value()); } return NS_OK; } void OriginClearOp::GetResponse(RequestResponse& aResponse) { AssertIsOnOwningThread(); if (mMultiple) { aResponse = ClearOriginsResponse(); } else { aResponse = ClearOriginResponse(); } } nsresult StorageDirectoryHelper::AddOriginDirectory(nsIFile* aDirectory, OriginProps** aOriginProps) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); OriginProps* originProps; nsString leafName; nsresult rv = aDirectory->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (leafName.EqualsLiteral(kChromeOrigin)) { originProps = mOriginProps.AppendElement(); originProps->mDirectory = aDirectory; originProps->mSpec = kChromeOrigin; originProps->mType = OriginProps::eChrome; } else { nsCString spec; PrincipalOriginAttributes attrs; bool result = OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName), spec, &attrs); if (NS_WARN_IF(!result)) { return NS_ERROR_FAILURE; } originProps = mOriginProps.AppendElement(); originProps->mDirectory = aDirectory; originProps->mSpec = spec; originProps->mAttrs = attrs; originProps->mType = OriginProps::eContent; } if (aOriginProps) { *aOriginProps = originProps; } return NS_OK; } nsresult StorageDirectoryHelper::ProcessOriginDirectories() { AssertIsOnIOThread(); MOZ_ASSERT(!mOriginProps.IsEmpty()); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); { mozilla::MutexAutoLock autolock(mMutex); while (mWaiting) { mCondVar.Wait(); } } if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) { return mMainThreadResultCode; } // Verify that the bounce to the main thread didn't start the shutdown // sequence. if (NS_WARN_IF(QuotaManager::IsShuttingDown())) { return NS_ERROR_FAILURE; } nsresult rv = DoProcessOriginDirectories(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult StorageDirectoryHelper::RunOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mOriginProps.IsEmpty()); nsresult rv; nsCOMPtr secMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } for (uint32_t count = mOriginProps.Length(), index = 0; index < count; index++) { OriginProps& originProps = mOriginProps[index]; switch (originProps.mType) { case OriginProps::eChrome: { QuotaManager::GetInfoForChrome(&originProps.mSuffix, &originProps.mGroup, &originProps.mOrigin, &originProps.mIsApp); break; } case OriginProps::eContent: { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), originProps.mSpec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr principal = BasePrincipal::CreateCodebasePrincipal(uri, originProps.mAttrs); if (NS_WARN_IF(!principal)) { return NS_ERROR_FAILURE; } rv = QuotaManager::GetInfoFromPrincipal(principal, &originProps.mSuffix, &originProps.mGroup, &originProps.mOrigin, &originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } break; } default: MOZ_CRASH("Bad type!"); } } return NS_OK; } NS_IMETHODIMP StorageDirectoryHelper::Run() { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = RunOnMainThread(); if (NS_WARN_IF(NS_FAILED(rv))) { mMainThreadResultCode = rv; } MutexAutoLock lock(mMutex); MOZ_ASSERT(mWaiting); mWaiting = false; mCondVar.Notify(); return NS_OK; } // static bool OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec, PrincipalOriginAttributes* aAttrs) { MOZ_ASSERT(!aOrigin.IsEmpty()); MOZ_ASSERT(aAttrs); PrincipalOriginAttributes originAttributes; nsCString originNoSuffix; bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix); if (!ok) { return false; } OriginParser parser(originNoSuffix, originAttributes); return parser.Parse(aSpec, aAttrs); } bool OriginParser::Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs) { MOZ_ASSERT(aAttrs); while (mTokenizer.hasMoreTokens()) { const nsDependentCSubstring& token = mTokenizer.nextToken(); HandleToken(token); if (mError) { break; } if (!mHandledTokens.IsEmpty()) { mHandledTokens.Append(NS_LITERAL_CSTRING(", ")); } mHandledTokens.Append('\''); mHandledTokens.Append(token); mHandledTokens.Append('\''); } if (!mError && mTokenizer.separatorAfterCurrentToken()) { HandleTrailingSeparator(); } if (mError) { QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(), mHandledTokens.get()); return false; } MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator); if (mAppId == kNoAppId) { *aAttrs = mOriginAttributes; } else { MOZ_ASSERT(mOriginAttributes.mAppId == kNoAppId); *aAttrs = PrincipalOriginAttributes(mAppId, mInIsolatedMozBrowser); } nsAutoCString spec(mSchema); if (mSchemaType == eFile) { spec.AppendLiteral("://"); for (uint32_t count = mPathnameComponents.Length(), index = 0; index < count; index++) { spec.Append('/'); spec.Append(mPathnameComponents[index]); } aSpec = spec; return true; } if (mSchemaType == eAbout) { spec.Append(':'); } else { spec.AppendLiteral("://"); } spec.Append(mHost); if (!mPort.IsNull()) { spec.Append(':'); spec.AppendInt(mPort.Value()); } aSpec = spec; return true; } void OriginParser::HandleSchema(const nsDependentCSubstring& aToken) { MOZ_ASSERT(!aToken.IsEmpty()); MOZ_ASSERT(mState == eExpectingAppIdOrSchema || mState == eExpectingSchema); bool isAbout = false; bool isFile = false; if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") || (isAbout = aToken.EqualsLiteral("about") || aToken.EqualsLiteral("moz-safe-about")) || aToken.EqualsLiteral("indexeddb") || (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") || aToken.EqualsLiteral("resource")) { mSchema = aToken; if (isAbout) { mSchemaType = eAbout; mState = eExpectingHost; } else { if (isFile) { mSchemaType = eFile; } mState = eExpectingEmptyToken1; } return; } QM_WARNING("'%s' is not a valid schema!", nsCString(aToken).get()); mError = true; } void OriginParser::HandlePathnameComponent(const nsDependentCSubstring& aToken) { MOZ_ASSERT(!aToken.IsEmpty()); MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent || mState == eExpectingEmptyTokenOrPathnameComponent); MOZ_ASSERT(mSchemaType == eFile); mPathnameComponents.AppendElement(aToken); mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent : eComplete; } void OriginParser::HandleToken(const nsDependentCSubstring& aToken) { switch (mState) { case eExpectingAppIdOrSchema: { if (aToken.IsEmpty()) { QM_WARNING("Expected an app id or schema (not an empty string)!"); mError = true; return; } if (NS_IsAsciiDigit(aToken.First())) { // nsDependentCSubstring doesn't provice ToInteger() nsCString token(aToken); nsresult rv; uint32_t appId = token.ToInteger(&rv); if (NS_SUCCEEDED(rv)) { mAppId = appId; mState = eExpectingInMozBrowser; return; } } HandleSchema(aToken); return; } case eExpectingInMozBrowser: { if (aToken.Length() != 1) { QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!", aToken.Length()); mError = true; return; } if (aToken.First() == 't') { mInIsolatedMozBrowser = true; } else if (aToken.First() == 'f') { mInIsolatedMozBrowser = false; } else { QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!", nsCString(aToken).get()); mError = true; return; } mState = eExpectingSchema; return; } case eExpectingSchema: { if (aToken.IsEmpty()) { QM_WARNING("Expected a schema (not an empty string)!"); mError = true; return; } HandleSchema(aToken); return; } case eExpectingEmptyToken1: { if (!aToken.IsEmpty()) { QM_WARNING("Expected the first empty token!"); mError = true; return; } mState = eExpectingEmptyToken2; return; } case eExpectingEmptyToken2: { if (!aToken.IsEmpty()) { QM_WARNING("Expected the second empty token!"); mError = true; return; } if (mSchemaType == eFile) { mState = eExpectingEmptyToken3; } else { mState = eExpectingHost; } return; } case eExpectingEmptyToken3: { MOZ_ASSERT(mSchemaType == eFile); if (!aToken.IsEmpty()) { QM_WARNING("Expected the third empty token!"); mError = true; return; } mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent : eComplete; return; } case eExpectingHost: { if (aToken.IsEmpty()) { QM_WARNING("Expected a host (not an empty string)!"); mError = true; return; } mHost = aToken; mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete; return; } case eExpectingPort: { MOZ_ASSERT(mSchemaType == eNone); if (aToken.IsEmpty()) { QM_WARNING("Expected a port (not an empty string)!"); mError = true; return; } // nsDependentCSubstring doesn't provice ToInteger() nsCString token(aToken); nsresult rv; uint32_t port = token.ToInteger(&rv); if (NS_SUCCEEDED(rv)) { mPort.SetValue() = port; } else { QM_WARNING("'%s' is not a valid port number!", token.get()); mError = true; return; } mState = eComplete; return; } case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: { MOZ_ASSERT(mSchemaType == eFile); if (aToken.IsEmpty()) { mPathnameComponents.AppendElement(EmptyCString()); mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent : eComplete; return; } if (aToken.Length() == 1 && NS_IsAsciiAlpha(aToken.First())) { mMaybeDriveLetter = true; mPathnameComponents.AppendElement(aToken); mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent : eComplete; return; } HandlePathnameComponent(aToken); return; } case eExpectingEmptyTokenOrPathnameComponent: { MOZ_ASSERT(mSchemaType == eFile); if (aToken.IsEmpty()) { if (mMaybeDriveLetter) { MOZ_ASSERT(mPathnameComponents.Length() == 1); nsCString& pathnameComponent = mPathnameComponents[0]; pathnameComponent.Append(':'); mMaybeDriveLetter = false; } else { mPathnameComponents.AppendElement(EmptyCString()); } mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent : eComplete; return; } HandlePathnameComponent(aToken); return; } default: MOZ_CRASH("Should never get here!"); } } void OriginParser::HandleTrailingSeparator() { MOZ_ASSERT(mState == eComplete); MOZ_ASSERT(mSchemaType == eFile); mPathnameComponents.AppendElement(EmptyCString()); mState = eHandledTrailingSeparator; } nsresult CreateOrUpgradeDirectoryMetadataHelper::CreateOrUpgradeMetadataFiles() { AssertIsOnIOThread(); bool exists; nsresult rv = mDirectory->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { return NS_OK; } nsCOMPtr entries; rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr originDir = do_QueryInterface(entry); MOZ_ASSERT(originDir); nsString leafName; rv = originDir->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool isDirectory; rv = originDir->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (isDirectory) { if (leafName.EqualsLiteral("moz-safe-about+++home")) { // This directory was accidentally created by a buggy nightly and can // be safely removed. QM_WARNING("Deleting accidental moz-safe-about+++home directory!"); rv = originDir->Remove(/* aRecursive */ true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } continue; } } else { if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { QM_WARNING("Something (%s) in the storage directory that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); } continue; } if (mPersistent) { rv = MaybeUpgradeOriginDirectory(originDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } OriginProps* originProps; rv = AddOriginDirectory(originDir, &originProps); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mPersistent) { int64_t timestamp; nsCString group; nsCString origin; bool hasIsApp; rv = GetDirectoryMetadata(originDir, ×tamp, group, origin, &hasIsApp); if (NS_FAILED(rv)) { timestamp = INT64_MIN; rv = GetLastModifiedTime(originDir, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } originProps->mTimestamp = timestamp; originProps->mNeedsRestore = true; } else if (hasIsApp) { originProps->mIgnore = true; } } else if (!QuotaManager::IsOriginInternal(originProps->mSpec)) { int64_t timestamp = INT64_MIN; rv = GetLastModifiedTime(originDir, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } originProps->mTimestamp = timestamp; } } if (mOriginProps.IsEmpty()) { return NS_OK; } rv = ProcessOriginDirectories(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory( nsIFile* aDirectory) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); nsCOMPtr metadataFile; nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = metadataFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { // Directory structure upgrade needed. // Move all files to IDB specific directory. nsString idbDirectoryName; rv = Client::TypeToText(Client::IDB, idbDirectoryName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr idbDirectory; rv = aDirectory->Clone(getter_AddRefs(idbDirectory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = idbDirectory->Append(idbDirectoryName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { NS_WARNING("IDB directory already exists!"); bool isDirectory; rv = idbDirectory->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!isDirectory)) { return NS_ERROR_UNEXPECTED; } } else { if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } nsCOMPtr entries; rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr file = do_QueryInterface(entry); if (NS_WARN_IF(!file)) { return rv; } nsString leafName; rv = file->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!leafName.Equals(idbDirectoryName)) { rv = file->MoveTo(idbDirectory, EmptyString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult CreateOrUpgradeDirectoryMetadataHelper::GetDirectoryMetadata( nsIFile* aDirectory, int64_t* aTimestamp, nsACString& aGroup, nsACString& aOrigin, bool* aHasIsApp) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aTimestamp); MOZ_ASSERT(aHasIsApp); MOZ_ASSERT(!mPersistent); nsCOMPtr binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_FILE_NAME), getter_AddRefs(binaryStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint64_t timestamp; rv = binaryStream->Read64(×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString group; rv = binaryStream->ReadCString(group); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString origin; rv = binaryStream->ReadCString(origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool dummyIsApp; bool hasIsApp = NS_SUCCEEDED(binaryStream->ReadBoolean(&dummyIsApp)); *aTimestamp = timestamp; aGroup = group; aOrigin = origin; *aHasIsApp = hasIsApp; return NS_OK; } nsresult CreateOrUpgradeDirectoryMetadataHelper::DoProcessOriginDirectories() { AssertIsOnIOThread(); nsresult rv; nsCOMPtr permanentStorageDir; for (uint32_t count = mOriginProps.Length(), index = 0; index < count; index++) { OriginProps& originProps = mOriginProps[index]; if (mPersistent) { rv = CreateDirectoryMetadata(originProps.mDirectory, originProps.mTimestamp, originProps.mSuffix, originProps.mGroup, originProps.mOrigin, originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Move internal origins to new persistent storage. if (QuotaManager::IsOriginInternal(originProps.mSpec)) { if (!permanentStorageDir) { permanentStorageDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); const nsString& permanentStoragePath = quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT); rv = permanentStorageDir->InitWithPath(permanentStoragePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } nsString leafName; rv = originProps.mDirectory->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr newDirectory; rv = permanentStorageDir->Clone(getter_AddRefs(newDirectory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = newDirectory->Append(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = newDirectory->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { QM_WARNING("Found %s in storage/persistent and storage/permanent !", NS_ConvertUTF16toUTF8(leafName).get()); rv = originProps.mDirectory->Remove(/* recursive */ true); } else { rv = originProps.mDirectory->MoveTo(permanentStorageDir, EmptyString()); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } else if (originProps.mNeedsRestore) { rv = CreateDirectoryMetadata(originProps.mDirectory, originProps.mTimestamp, originProps.mSuffix, originProps.mGroup, originProps.mOrigin, originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else if (!originProps.mIgnore) { nsCOMPtr stream; rv = GetBinaryOutputStream(originProps.mDirectory, NS_LITERAL_STRING(METADATA_FILE_NAME), kAppendFileFlag, getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(stream); rv = stream->WriteBoolean(originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } return NS_OK; } nsresult UpgradeDirectoryMetadataFrom1To2Helper::UpgradeMetadataFiles() { AssertIsOnIOThread(); bool exists; nsresult rv = mDirectory->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { return NS_OK; } nsCOMPtr entries; rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr originDir = do_QueryInterface(entry); MOZ_ASSERT(originDir); bool isDirectory; rv = originDir->IsDirectory(&isDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { nsString leafName; rv = originDir->GetLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { QM_WARNING("Something (%s) in the storage directory that doesn't belong!", NS_ConvertUTF16toUTF8(leafName).get()); } continue; } OriginProps* originProps; rv = AddOriginDirectory(originDir, &originProps); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t timestamp; nsCString group; nsCString origin; bool isApp; nsresult rv = GetDirectoryMetadata(originDir, ×tamp, group, origin, &isApp); if (NS_FAILED(rv)) { if (!mPersistent) { rv = GetLastModifiedTime(originDir, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } originProps->mTimestamp = timestamp; } originProps->mNeedsRestore = true; } else { originProps->mTimestamp = timestamp; } } if (mOriginProps.IsEmpty()) { return NS_OK; } rv = ProcessOriginDirectories(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeDirectoryMetadataFrom1To2Helper::GetDirectoryMetadata( nsIFile* aDirectory, int64_t* aTimestamp, nsACString& aGroup, nsACString& aOrigin, bool* aIsApp) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aTimestamp); MOZ_ASSERT(aIsApp); nsCOMPtr binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_FILE_NAME), getter_AddRefs(binaryStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint64_t timestamp; rv = binaryStream->Read64(×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString group; rv = binaryStream->ReadCString(group); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString origin; rv = binaryStream->ReadCString(origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool isApp; rv = binaryStream->ReadBoolean(&isApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aTimestamp = timestamp; aGroup = group; aOrigin = origin; *aIsApp = isApp; return NS_OK; } nsresult UpgradeDirectoryMetadataFrom1To2Helper::DoProcessOriginDirectories() { AssertIsOnIOThread(); for (uint32_t count = mOriginProps.Length(), index = 0; index < count; index++) { OriginProps& originProps = mOriginProps[index]; nsresult rv; if (originProps.mNeedsRestore) { rv = CreateDirectoryMetadata(originProps.mDirectory, originProps.mTimestamp, originProps.mSuffix, originProps.mGroup, originProps.mOrigin, originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = CreateDirectoryMetadata2(originProps.mDirectory, originProps.mTimestamp, originProps.mSuffix, originProps.mGroup, originProps.mOrigin, originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString oldName; rv = originProps.mDirectory->GetLeafName(oldName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoCString originSanitized(originProps.mOrigin); SanitizeOriginString(originSanitized); NS_ConvertASCIItoUTF16 newName(originSanitized); if (!oldName.Equals(newName)) { rv = originProps.mDirectory->RenameTo(nullptr, newName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } return NS_OK; } nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() { AssertIsOnIOThread(); nsresult rv; OriginProps* originProps; rv = AddOriginDirectory(mDirectory, &originProps); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mPersistent) { int64_t timestamp = INT64_MIN; rv = GetLastModifiedTime(mDirectory, ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } originProps->mTimestamp = timestamp; } rv = ProcessOriginDirectories(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult RestoreDirectoryMetadata2Helper::DoProcessOriginDirectories() { AssertIsOnIOThread(); MOZ_ASSERT(mOriginProps.Length() == 1); OriginProps& originProps = mOriginProps[0]; nsresult rv = CreateDirectoryMetadata2(originProps.mDirectory, originProps.mTimestamp, originProps.mSuffix, originProps.mGroup, originProps.mOrigin, originProps.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } } // namespace quota } // namespace dom } // namespace mozilla