/* -*- 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/. */ #ifndef mozilla_dom_quota_quotamanager_h__ #define mozilla_dom_quota_quotamanager_h__ #include #include #include "Client.h" #include "ErrorList.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Assertions.h" #include "mozilla/InitializedOnce.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "mozilla/Result.h" #include "mozilla/dom/Nullable.h" #include "mozilla/dom/ipc/IdType.h" #include "mozilla/dom/quota/InitializationTypes.h" #include "mozilla/dom/quota/PersistenceType.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/QuotaInfo.h" #include "nsCOMPtr.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" #include "nsDebug.h" #include "nsHashKeys.h" #include "nsISupports.h" #include "nsStringFwd.h" #include "nsTArray.h" #include "nsTStringRepr.h" #include "nscore.h" #include "prenv.h" #define QUOTA_MANAGER_CONTRACTID "@mozilla.org/dom/quota/manager;1" class mozIStorageConnection; class nsIEventTarget; class nsIFile; class nsIPrincipal; class nsIRunnable; class nsIThread; class nsITimer; class nsPIDOMWindowOuter; namespace mozilla { class OriginAttributes; namespace ipc { class PrincipalInfo; } // namespace ipc } // namespace mozilla namespace mozilla::dom::quota { class ClientUsageArray; class DirectoryLockImpl; class GroupInfo; class GroupInfoPair; class OriginInfo; class OriginScope; class QuotaObject; class NS_NO_VTABLE RefCountedObject { public: NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING }; class DirectoryLock : public RefCountedObject { friend class DirectoryLockImpl; public: int64_t Id() const; // 'Get' prefix is to avoid name collisions with the enum PersistenceType GetPersistenceType() const; quota::GroupAndOrigin GroupAndOrigin() const; const nsACString& Origin() const; Client::Type ClientType() const; already_AddRefed Specialize( PersistenceType aPersistenceType, const quota::GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType) const; void Log() const; private: DirectoryLock() = default; ~DirectoryLock() = default; }; class NS_NO_VTABLE OpenDirectoryListener : public RefCountedObject { public: virtual void DirectoryLockAcquired(DirectoryLock* aLock) = 0; virtual void DirectoryLockFailed() = 0; protected: virtual ~OpenDirectoryListener() = default; }; struct OriginParams { OriginParams(PersistenceType aPersistenceType, const nsACString& aOrigin) : mOrigin(aOrigin), mPersistenceType(aPersistenceType) {} nsCString mOrigin; PersistenceType mPersistenceType; }; class QuotaManager final : public BackgroundThreadObject { friend class DirectoryLockImpl; friend class GroupInfo; friend class OriginInfo; friend class QuotaObject; typedef mozilla::ipc::PrincipalInfo PrincipalInfo; typedef nsClassHashtable>> DirectoryLockTable; class Observer; public: NS_INLINE_DECL_REFCOUNTING(QuotaManager) static nsresult Initialize(); static bool IsRunningXPCShellTests() { static bool kRunningXPCShellTests = !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR"); return kRunningXPCShellTests; } static bool IsRunningGTests() { static bool kRunningGTests = !!PR_GetEnv("MOZ_RUN_GTEST"); return kRunningGTests; } static const char kReplaceChars[]; static void GetOrCreate(nsIRunnable* aCallback, nsIEventTarget* aMainEventTarget = nullptr); // Returns a non-owning reference. static QuotaManager* Get(); static QuotaManager& GetRef(); // Returns true if we've begun the shutdown process. static bool IsShuttingDown(); static void ShutdownInstance(); static bool IsOSMetadata(const nsAString& aFileName); static bool IsDotFile(const nsAString& aFileName); bool IsOriginInitialized(const nsACString& aOrigin) const { AssertIsOnIOThread(); return mInitializedOrigins.Contains(aOrigin); } bool IsTemporaryStorageInitialized() const { AssertIsOnIOThread(); return mTemporaryStorageInitialized; } /** * For initialization of an origin where the directory already exists. This is * used by EnsureTemporaryStorageIsInitialized/InitializeRepository once it * has tallied origin usage by calling each of the QuotaClient InitOrigin * methods. */ void InitQuotaForOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, const ClientUsageArray& aClientUsages, uint64_t aUsageBytes, int64_t aAccessTime, bool aPersisted); /** * For use in special-cases like LSNG where we need to be able to know that * there is no data stored for an origin. LSNG knows that there is 0 usage for * its storage of an origin and wants to make sure there is a QuotaObject * tracking this. This method will create a non-persisted, 0-usage, * mDirectoryExists=false OriginInfo if there isn't already an OriginInfo. If * an OriginInfo already exists, it will be left as-is, because that implies a * different client has usages for the origin (and there's no need to add * LSNG's 0 usage to the QuotaObject). */ void EnsureQuotaForOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin); /** * For use when creating an origin directory. It's possible that origin usage * is already being tracked due to a call to EnsureQuotaForOrigin, and in that * case we need to update the existing OriginInfo rather than create a new * one. */ void NoteOriginDirectoryCreated(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, bool aPersisted, int64_t& aTimestamp); // XXX clients can use QuotaObject instead of calling this method directly. void DecreaseUsageForOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType, int64_t aSize); void ResetUsageForClient(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType); UsageInfo GetUsageForClient(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType); void UpdateOriginAccessTime(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin); void RemoveQuota(); void RemoveQuotaForOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin) { MutexAutoLock lock(mQuotaMutex); LockedRemoveQuotaForOrigin(aPersistenceType, aGroupAndOrigin); } nsresult LoadQuota(); void UnloadQuota(); already_AddRefed GetQuotaObject( PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType, nsIFile* aFile, int64_t aFileSize = -1, int64_t* aFileSizeOut = nullptr); already_AddRefed GetQuotaObject( PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType, const nsAString& aPath, int64_t aFileSize = -1, int64_t* aFileSizeOut = nullptr); already_AddRefed GetQuotaObject(const int64_t aDirectoryLockId, const nsAString& aPath); Nullable OriginPersisted(const GroupAndOrigin& aGroupAndOrigin); void PersistOrigin(const GroupAndOrigin& aGroupAndOrigin); // Called when a process is being shot down. Aborts any running operations // for the given process. void AbortOperationsForProcess(ContentParentId aContentParentId); Result, nsresult> GetDirectoryForOrigin( PersistenceType aPersistenceType, const nsACString& aASCIIOrigin) const; nsresult RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent); struct GetDirectoryResult { int64_t mTimestamp; bool mPersisted; }; struct GetDirectoryResultWithQuotaInfo : GetDirectoryResult { QuotaInfo mQuotaInfo; }; Result GetDirectoryMetadataWithQuotaInfo2(nsIFile* aDirectory); Result GetDirectoryMetadataWithQuotaInfo2WithRestore(nsIFile* aDirectory, bool aPersistent); Result GetDirectoryMetadata2( nsIFile* aDirectory); Result GetDirectoryMetadata2WithRestore( nsIFile* aDirectory, bool aPersistent); // This is the main entry point into the QuotaManager API. // Any storage API implementation (quota client) that participates in // centralized quota and storage handling should call this method to get // a directory lock which will protect client's files from being deleted // while they are still in use. // After a lock is acquired, client is notified via the open listener's // method DirectoryLockAcquired. If the lock couldn't be acquired, client // gets DirectoryLockFailed notification. // A lock is a reference counted object and at the time DirectoryLockAcquired // is called, quota manager holds just one strong reference to it which is // then immediatelly cleared by quota manager. So it's up to client to add // a new reference in order to keep the lock alive. // Unlocking is simply done by dropping all references to the lock object. // In other words, protection which the lock represents dies with the lock // object itself. already_AddRefed OpenDirectory( PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, Client::Type aClientType, bool aExclusive, RefPtr aOpenListener); // XXX RemoveMe once bug 1170279 gets fixed. already_AddRefed OpenDirectoryInternal( const Nullable& aPersistenceType, const OriginScope& aOriginScope, const Nullable& aClientType, bool aExclusive, OpenDirectoryListener* aOpenListener); // Collect inactive and the least recently used origins. uint64_t CollectOriginsForEviction( uint64_t aMinSizeToBeFreed, nsTArray>& aLocks); /** * Helper method to invoke the provided predicate on all "pending" OriginInfo * instances. These are origins for which the origin directory has not yet * been created but for which quota is already being tracked. This happens, * for example, for the LocalStorage client where an origin that previously * was not using LocalStorage can start issuing writes which it buffers until * eventually flushing them. We defer creating the origin directory for as * long as possible in that case, so the directory won't exist. Logic that * would otherwise only consult the filesystem also needs to use this method. */ template void CollectPendingOriginsForListing(P aPredicate); bool IsStorageInitialized() const { AssertIsOnIOThread(); return static_cast(mStorageConnection); } void AssertStorageIsInitialized() const #ifdef DEBUG ; #else { } #endif nsresult EnsureStorageIsInitialized(); // Returns a pair of an nsIFile object referring to the directory, and a bool // indicating whether the directory was newly created. Result, bool>, nsresult> EnsurePersistentOriginIsInitialized(const QuotaInfo& aQuotaInfo); // Returns a pair of an nsIFile object referring to the directory, and a bool // indicating whether the directory was newly created. Result, bool>, nsresult> EnsureTemporaryOriginIsInitialized(PersistenceType aPersistenceType, const QuotaInfo& aQuotaInfo); nsresult EnsureTemporaryStorageIsInitialized(); void ShutdownStorage(); // Returns a bool indicating whether the directory was newly created. Result EnsureOriginDirectory(nsIFile& aDirectory); nsresult AboutToClearOrigins( const Nullable& aPersistenceType, const OriginScope& aOriginScope, const Nullable& aClientType); void OriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin, const Nullable& aClientType); void StartIdleMaintenance() { AssertIsOnOwningThread(); for (const auto& client : *mClients) { client->StartIdleMaintenance(); } } void StopIdleMaintenance() { AssertIsOnOwningThread(); for (const auto& client : *mClients) { client->StopIdleMaintenance(); } } void AssertCurrentThreadOwnsQuotaMutex() { mQuotaMutex.AssertCurrentThreadOwns(); } nsIThread* IOThread() { return mIOThread->get(); } Client* GetClient(Client::Type aClientType); const AutoTArray& AllClientTypes(); const nsString& GetBasePath() const { return mBasePath; } const nsString& GetStorageName() const { return mStorageName; } const nsString& GetStoragePath() const { return *mStoragePath; } const nsString& GetStoragePath(PersistenceType aPersistenceType) const { if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { return *mPermanentStoragePath; } if (aPersistenceType == PERSISTENCE_TYPE_TEMPORARY) { return *mTemporaryStoragePath; } MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); return *mDefaultStoragePath; } uint64_t GetGroupLimit() const; uint64_t GetGroupUsage(const nsACString& aGroup); uint64_t GetOriginUsage(const GroupAndOrigin& aGroupAndOrigin); void NotifyStoragePressure(uint64_t aUsage); // Record a quota client shutdown step, if shutting down. void MaybeRecordShutdownStep(Client::Type aClientType, const nsACString& aStepDescription); // Record a quota manager shutdown step, if shutting down. void MaybeRecordQuotaManagerShutdownStep(const nsACString& aStepDescription); static void GetStorageId(PersistenceType aPersistenceType, const nsACString& aOrigin, Client::Type aClientType, nsACString& aDatabaseId); static bool IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo); static QuotaInfo GetInfoFromValidatedPrincipalInfo( const PrincipalInfo& aPrincipalInfo); static nsAutoCString GetOriginFromValidatedPrincipalInfo( const PrincipalInfo& aPrincipalInfo); static Result GetInfoFromPrincipal( nsIPrincipal* aPrincipal); static Result GetOriginFromPrincipal( nsIPrincipal* aPrincipal); static Result GetOriginFromWindow( nsPIDOMWindowOuter* aWindow); static nsLiteralCString GetOriginForChrome(); static QuotaInfo GetInfoForChrome(); static bool IsOriginInternal(const nsACString& aOrigin); static bool AreOriginsEqualOnDisk(const nsACString& aOrigin1, const nsACString& aOrigin2); static Result ParseOrigin(const nsACString& aOrigin); static void InvalidateQuotaCache(); private: QuotaManager(const nsAString& aBasePath, const nsAString& aStorageName); virtual ~QuotaManager(); nsresult Init(); void Shutdown(); already_AddRefed CreateDirectoryLock( const Nullable& aPersistenceType, const nsACString& aGroup, const OriginScope& aOriginScope, const Nullable& aClientType, bool aExclusive, bool aInternal, RefPtr aOpenListener, bool& aBlockedOut); already_AddRefed CreateDirectoryLockForEviction( PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin); void RegisterDirectoryLock(DirectoryLockImpl& aLock); void UnregisterDirectoryLock(DirectoryLockImpl& aLock); void RemovePendingDirectoryLock(DirectoryLockImpl& aLock); uint64_t LockedCollectOriginsForEviction( uint64_t aMinSizeToBeFreed, nsTArray>& aLocks); void LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin); already_AddRefed LockedGetOrCreateGroupInfo( PersistenceType aPersistenceType, const nsACString& aGroup); already_AddRefed LockedGetOriginInfo( PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin); nsresult UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory( nsIFile* aIndexedDBDir); nsresult UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory( nsIFile* aPersistentStorageDir); template nsresult UpgradeStorage(const int32_t aOldVersion, const int32_t aNewVersion, mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom0_0To1_0(mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom1_0To2_0(mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom2_0To2_1(mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom2_1To2_2(mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom2_2To2_3(mozIStorageConnection* aConnection); nsresult MaybeRemoveLocalStorageData(); nsresult MaybeRemoveLocalStorageDirectories(); Result, nsresult> CreateLocalStorageArchiveConnectionFromWebAppsStore(); // The second object in the pair is used to signal if the localStorage // archive database was newly created or recreated. Result, bool>, nsresult> CreateLocalStorageArchiveConnection(); nsresult RecreateLocalStorageArchive( nsCOMPtr& aConnection); nsresult DowngradeLocalStorageArchive( nsCOMPtr& aConnection); nsresult UpgradeLocalStorageArchiveFromLessThan4To4( nsCOMPtr& aConnection); /* nsresult UpgradeLocalStorageArchiveFrom4To5(); */ nsresult InitializeRepository(PersistenceType aPersistenceType); nsresult InitializeOrigin(PersistenceType aPersistenceType, const GroupAndOrigin& aGroupAndOrigin, int64_t aAccessTime, bool aPersisted, nsIFile* aDirectory); void CheckTemporaryStorageLimits(); void DeleteFilesForOrigin(PersistenceType aPersistenceType, const nsACString& aOrigin); void FinalizeOriginEviction(nsTArray>&& aLocks); void ReleaseIOThreadObjects() { AssertIsOnIOThread(); for (Client::Type type : AllClientTypes()) { (*mClients)[type]->ReleaseIOThreadObjects(); } } DirectoryLockTable& GetDirectoryLockTable(PersistenceType aPersistenceType); bool IsSanitizedOriginValid(const nsACString& aSanitizedOrigin); int64_t GenerateDirectoryLockId(); void MaybeRecordShutdownStep(Maybe aClientType, const nsACString& aStepDescription); // Thread on which IO is performed. LazyInitializedOnceNotNull> mIOThread; nsCOMPtr mStorageConnection; // A timer that gets activated at shutdown to ensure we close all storages. LazyInitializedOnceNotNull> mShutdownTimer; EnumeratedArray mShutdownSteps; LazyInitializedOnce mShutdownStartedAt; Atomic mShutdownStarted; // Accesses to mQuotaManagerShutdownSteps must be protected by mQuotaMutex. nsCString mQuotaManagerShutdownSteps; mozilla::Mutex mQuotaMutex; nsClassHashtable mGroupInfoPairs; // Maintains a list of directory locks that are queued. nsTArray> mPendingDirectoryLocks; // Maintains a list of directory locks that are acquired or queued. It can be // accessed on the owning (PBackground) thread only. nsTArray mDirectoryLocks; // Only modifed on the owning thread, but read on multiple threads. Therefore // all modifications (including those on the owning thread) and all reads off // the owning thread must be protected by mQuotaMutex. In other words, only // reads on the owning thread don't have to be protected by mQuotaMutex. nsDataHashtable mDirectoryLockIdTable; // Directory lock tables that are used to update origin access time. DirectoryLockTable mTemporaryDirectoryLockTable; DirectoryLockTable mDefaultDirectoryLockTable; // A list of all successfully initialized persistent origins. This list isn't // protected by any mutex but it is only ever touched on the IO thread. nsTArray mInitializedOrigins; // A hash table that is used to cache origin parser results for given // sanitized origin strings. This hash table isn't protected by any mutex but // it is only ever touched on the IO thread. nsDataHashtable mValidOrigins; struct OriginInitializationInfo { bool mPersistentOriginAttempted : 1; bool mTemporaryOriginAttempted : 1; }; // A hash table that is currently used to track origin initialization // attempts. This hash table isn't protected by any mutex but it is only ever // touched on the IO thread. nsDataHashtable mOriginInitializationInfos; // This array is populated at initialization time and then never modified, so // it can be iterated on any thread. LazyInitializedOnce, Client::TYPE_MAX>> mClients; using ClientTypesArray = AutoTArray; LazyInitializedOnce mAllClientTypes; LazyInitializedOnce mAllClientTypesExceptLS; InitializationInfo mInitializationInfo; const nsString mBasePath; const nsString mStorageName; LazyInitializedOnce mIndexedDBPath; LazyInitializedOnce mStoragePath; LazyInitializedOnce mPermanentStoragePath; LazyInitializedOnce mTemporaryStoragePath; LazyInitializedOnce mDefaultStoragePath; uint64_t mTemporaryStorageLimit; uint64_t mTemporaryStorageUsage; int64_t mNextDirectoryLockId; bool mTemporaryStorageInitialized; bool mCacheUsable; }; } // namespace mozilla::dom::quota #endif /* mozilla_dom_quota_quotamanager_h__ */