/* 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 CacheFileIOManager__h__ #define CacheFileIOManager__h__ #include "CacheIOThread.h" #include "CacheStorageService.h" #include "CacheHashUtils.h" #include "nsIEventTarget.h" #include "nsINamed.h" #include "nsITimer.h" #include "nsCOMPtr.h" #include "mozilla/Atomics.h" #include "mozilla/SHA1.h" #include "mozilla/StaticPtr.h" #include "mozilla/TimeStamp.h" #include "nsTArray.h" #include "nsString.h" #include "nsTHashtable.h" #include "prio.h" //#define DEBUG_HANDLES 1 class nsIFile; class nsITimer; class nsIDirectoryEnumerator; class nsILoadContextInfo; namespace mozilla { namespace net { class CacheFile; class CacheFileIOListener; #ifdef DEBUG_HANDLES class CacheFileHandlesEntry; #endif #define ENTRIES_DIR "entries" #define DOOMED_DIR "doomed" #define TRASH_DIR "trash" class CacheFileHandle final : public nsISupports { public: enum class PinningStatus : uint32_t { UNKNOWN, NON_PINNED, PINNED }; NS_DECL_THREADSAFE_ISUPPORTS bool DispatchRelease(); CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority, PinningStatus aPinning); CacheFileHandle(const nsACString& aKey, bool aPriority, PinningStatus aPinning); void Log(); bool IsDoomed() const { return mIsDoomed; } const SHA1Sum::Hash* Hash() const { return mHash; } int64_t FileSize() const { return mFileSize; } uint32_t FileSizeInK() const; bool IsPriority() const { return mPriority; } bool FileExists() const { return mFileExists; } bool IsClosed() const { return mClosed; } bool IsSpecialFile() const { return mSpecialFile; } nsCString& Key() { return mKey; } // Returns false when this handle has been doomed based on the pinning state // update. bool SetPinned(bool aPinned); void SetInvalid() { mInvalid = true; } // Memory reporting size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; private: friend class CacheFileIOManager; friend class CacheFileHandles; friend class ReleaseNSPRHandleEvent; virtual ~CacheFileHandle(); const SHA1Sum::Hash* mHash; mozilla::Atomic mIsDoomed; mozilla::Atomic mClosed; // mPriority and mSpecialFile are plain "bool", not "bool:1", so as to // avoid bitfield races with the byte containing mInvalid et al. See // bug 1278502. bool const mPriority; bool const mSpecialFile; mozilla::Atomic mInvalid; // These bit flags are all accessed only on the IO thread bool mFileExists : 1; // This means that the file should exists, // but it can be still deleted by OS/user // and then a subsequent OpenNSPRFileDesc() // will fail. // Both initially false. Can be raised to true only when this handle is to be // doomed during the period when the pinning status is unknown. After the // pinning status determination we check these flags and possibly doom. These // flags are only accessed on the IO thread. bool mDoomWhenFoundPinned : 1; bool mDoomWhenFoundNonPinned : 1; // Set when after shutdown AND: // - when writing: writing data (not metadata) OR the physical file handle is // not currently open // - when truncating: the physical file handle is not currently open // When set it prevents any further writes or truncates on such handles to // happen immediately after shutdown and gives a chance to write metadata of // already open files quickly as possible (only that renders them actually // usable by the cache.) bool mKilled : 1; // For existing files this is always pre-set to UNKNOWN. The status is // udpated accordingly after the matadata has been parsed. For new files the // flag is set according to which storage kind is opening the cache entry and // remains so for the handle's lifetime. The status can only change from // UNKNOWN (if set so initially) to one of PINNED or NON_PINNED and it stays // unchanged afterwards. This status is only accessed on the IO thread. PinningStatus mPinning; nsCOMPtr mFile; // file size is atomic because it is used on main thread by // nsHttpChannel::ReportNetVSCacheTelemetry() Atomic mFileSize; PRFileDesc* mFD; // if null then the file doesn't exists on the disk nsCString mKey; }; class CacheFileHandles { public: CacheFileHandles(); ~CacheFileHandles(); nsresult GetHandle(const SHA1Sum::Hash* aHash, CacheFileHandle** _retval); already_AddRefed NewHandle(const SHA1Sum::Hash*, bool aPriority, CacheFileHandle::PinningStatus); void RemoveHandle(CacheFileHandle* aHandle); void GetAllHandles(nsTArray >* _retval); void GetActiveHandles(nsTArray >* _retval); void ClearAll(); uint32_t HandleCount(); #ifdef DEBUG_HANDLES void Log(CacheFileHandlesEntry* entry); #endif // Memory reporting size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; class HandleHashKey : public PLDHashEntryHdr { public: using KeyType = const SHA1Sum::Hash&; using KeyTypePointer = const SHA1Sum::Hash*; explicit HandleHashKey(KeyTypePointer aKey) { MOZ_COUNT_CTOR(HandleHashKey); mHash = MakeUnique(SHA1Sum::kHashSize); memcpy(mHash.get(), aKey, sizeof(SHA1Sum::Hash)); } HandleHashKey(const HandleHashKey& aOther) { MOZ_ASSERT_UNREACHABLE("HandleHashKey copy constructor is forbidden!"); } MOZ_COUNTED_DTOR(HandleHashKey) bool KeyEquals(KeyTypePointer aKey) const { return memcmp(mHash.get(), aKey, sizeof(SHA1Sum::Hash)) == 0; } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { return (reinterpret_cast(aKey))[0]; } void AddHandle(CacheFileHandle* aHandle); void RemoveHandle(CacheFileHandle* aHandle); already_AddRefed GetNewestHandle(); void GetHandles(nsTArray >& aResult); SHA1Sum::Hash* Hash() const { return reinterpret_cast(mHash.get()); } bool IsEmpty() const { return mHandles.Length() == 0; } enum { ALLOW_MEMMOVE = true }; #ifdef DEBUG void AssertHandlesState(); #endif // Memory reporting size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; private: // We can't make this UniquePtr, because you can't have // UniquePtrs with known bounds. So we settle for this representation // and using appropriate casts when we need to access it as a // SHA1Sum::Hash. UniquePtr mHash; // Use weak pointers since the hash table access is on a single thread // only and CacheFileHandle removes itself from this table in its dtor // that may only be called on the same thread as we work with the hashtable // since we dispatch its Release() to this thread. nsTArray mHandles; }; private: nsTHashtable mTable; }; //////////////////////////////////////////////////////////////////////////////// class OpenFileEvent; class ReadEvent; class WriteEvent; class MetadataWriteScheduleEvent; class CacheFileContextEvictor; #define CACHEFILEIOLISTENER_IID \ { /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */ \ 0xdcaf2ddc, 0x17cf, 0x4242, { \ 0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5 \ } \ } class CacheFileIOListener : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID) NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) = 0; NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf, nsresult aResult) = 0; NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf, nsresult aResult) = 0; NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) = 0; NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) = 0; NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) = 0; virtual bool IsKilled() { return false; } }; NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID) class CacheFileIOManager final : public nsITimerCallback, public nsINamed { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITIMERCALLBACK NS_DECL_NSINAMED enum { OPEN = 0U, CREATE = 1U, CREATE_NEW = 2U, PRIORITY = 4U, SPECIAL_FILE = 8U, PINNED = 16U }; CacheFileIOManager(); static nsresult Init(); static nsresult Shutdown(); static nsresult OnProfile(); static nsresult OnDelayedStartupFinished(); static already_AddRefed IOTarget(); static already_AddRefed IOThread(); static bool IsOnIOThread(); static bool IsOnIOThreadOrCeased(); static bool IsShutdown(); // Make aFile's WriteMetadataIfNeeded be called automatically after // a short interval. static nsresult ScheduleMetadataWrite(CacheFile* aFile); // Remove aFile from the scheduling registry array. // WriteMetadataIfNeeded will not be automatically called. static nsresult UnscheduleMetadataWrite(CacheFile* aFile); // Shuts the scheduling off and flushes all pending metadata writes. static nsresult ShutdownMetadataWriteScheduling(); static nsresult OpenFile(const nsACString& aKey, uint32_t aFlags, CacheFileIOListener* aCallback); static nsresult Read(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf, int32_t aCount, CacheFileIOListener* aCallback); static nsresult Write(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf, int32_t aCount, bool aValidate, bool aTruncate, CacheFileIOListener* aCallback); // PinningDoomRestriction: // NO_RESTRICTION // no restriction is checked, the file is simply always doomed // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the // handle: // UNKNOWN: the handle is marked to be doomed when later found (non)pinned // PINNED/NON_PINNED: doom only when the restriction matches the pin status // and the handle has not yet been required to doom during the UNKNOWN // period enum PinningDoomRestriction { NO_RESTRICTION, DOOM_WHEN_NON_PINNED, DOOM_WHEN_PINNED }; static nsresult DoomFile(CacheFileHandle* aHandle, CacheFileIOListener* aCallback); static nsresult DoomFileByKey(const nsACString& aKey, CacheFileIOListener* aCallback); static nsresult ReleaseNSPRHandle(CacheFileHandle* aHandle); static nsresult TruncateSeekSetEOF(CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos, CacheFileIOListener* aCallback); static nsresult RenameFile(CacheFileHandle* aHandle, const nsACString& aNewName, CacheFileIOListener* aCallback); static nsresult EvictIfOverLimit(); static nsresult EvictAll(); static nsresult EvictByContext(nsILoadContextInfo* aLoadContextInfo, bool aPinned, const nsAString& aOrigin, const nsAString& aBaseDomain = u""_ns); static nsresult InitIndexEntry(CacheFileHandle* aHandle, OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinning); static nsresult UpdateIndexEntry(CacheFileHandle* aHandle, const uint32_t* aFrecency, const bool* aHasAltData, const uint16_t* aOnStartTime, const uint16_t* aOnStopTime, const uint8_t* aContentType); static nsresult UpdateIndexEntry(); enum EEnumerateMode { ENTRIES, DOOMED }; static void GetCacheDirectory(nsIFile** result); #if defined(MOZ_WIDGET_ANDROID) static void GetProfilelessCacheDirectory(nsIFile** result); #endif // Calls synchronously OnEntryInfo for an entry with the given hash. // Tries to find an existing entry in the service hashtables first, if not // found, loads synchronously from disk file. // Callable on the IO thread only. static nsresult GetEntryInfo( const SHA1Sum::Hash* aHash, CacheStorageService::EntryInfoCallback* aCallback); // Memory reporting static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); private: friend class CacheFileHandle; friend class CacheFileChunk; friend class CacheFile; friend class ShutdownEvent; friend class OpenFileEvent; friend class CloseHandleEvent; friend class ReadEvent; friend class WriteEvent; friend class DoomFileEvent; friend class DoomFileByKeyEvent; friend class ReleaseNSPRHandleEvent; friend class TruncateSeekSetEOFEvent; friend class RenameFileEvent; friend class CacheIndex; friend class MetadataWriteScheduleEvent; friend class CacheFileContextEvictor; virtual ~CacheFileIOManager(); nsresult InitInternal(); void ShutdownInternal(); nsresult OpenFileInternal(const SHA1Sum::Hash* aHash, const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval); nsresult OpenSpecialFileInternal(const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval); void CloseHandleInternal(CacheFileHandle* aHandle); nsresult ReadInternal(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf, int32_t aCount); nsresult WriteInternal(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf, int32_t aCount, bool aValidate, bool aTruncate); nsresult DoomFileInternal( CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction = NO_RESTRICTION); nsresult DoomFileByKeyInternal(const SHA1Sum::Hash* aHash); nsresult MaybeReleaseNSPRHandleInternal(CacheFileHandle* aHandle, bool aIgnoreShutdownLag = false); nsresult TruncateSeekSetEOFInternal(CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos); nsresult RenameFileInternal(CacheFileHandle* aHandle, const nsACString& aNewName); nsresult EvictIfOverLimitInternal(); nsresult OverLimitEvictionInternal(); nsresult EvictAllInternal(); nsresult EvictByContextInternal(nsILoadContextInfo* aLoadContextInfo, bool aPinned, const nsAString& aOrigin, const nsAString& aBaseDomain = u""_ns); nsresult TrashDirectory(nsIFile* aFile); static void OnTrashTimer(nsITimer* aTimer, void* aClosure); nsresult StartRemovingTrash(); nsresult RemoveTrashInternal(); nsresult FindTrashDirToRemove(); nsresult CreateFile(CacheFileHandle* aHandle); static void HashToStr(const SHA1Sum::Hash* aHash, nsACString& _retval); static nsresult StrToHash(const nsACString& aHash, SHA1Sum::Hash* _retval); nsresult GetFile(const SHA1Sum::Hash* aHash, nsIFile** _retval); nsresult GetSpecialFile(const nsACString& aKey, nsIFile** _retval); nsresult GetDoomedFile(nsIFile** _retval); nsresult IsEmptyDirectory(nsIFile* aFile, bool* _retval); nsresult CheckAndCreateDir(nsIFile* aFile, const char* aDir, bool aEnsureEmptyDir); nsresult CreateCacheTree(); nsresult OpenNSPRHandle(CacheFileHandle* aHandle, bool aCreate = false); void NSPRHandleUsed(CacheFileHandle* aHandle); // Removing all cache files during shutdown nsresult SyncRemoveDir(nsIFile* aFile, const char* aDir); void SyncRemoveAllCacheFiles(); nsresult ScheduleMetadataWriteInternal(CacheFile* aFile); void UnscheduleMetadataWriteInternal(CacheFile* aFile); void ShutdownMetadataWriteSchedulingInternal(); static nsresult CacheIndexStateChanged(); void CacheIndexStateChangedInternal(); // Dispatches a purgeHTTP background task to delete the cache directoy // indicated by aCacheDirName. // When this feature is enabled, a task will be dispatched at shutdown // or after browser startup (to cleanup potential left-over directories) nsresult DispatchPurgeTask(const nsCString& aCacheDirName, const nsCString& aSecondsToWait, const nsCString& aPurgeExtension); // Smart size calculation. UpdateSmartCacheSize() must be called on IO thread. // It is called in EvictIfOverLimitInternal() just before we decide whether to // start overlimit eviction or not and also in OverLimitEvictionInternal() // before we start an eviction loop. nsresult UpdateSmartCacheSize(int64_t aFreeSpace); // Memory reporting (private part) size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const; static StaticRefPtr gInstance; TimeStamp mStartTime; // Set true on the IO thread, CLOSE level as part of the internal shutdown // procedure. bool mShuttingDown{false}; RefPtr mIOThread; nsCOMPtr mCacheDirectory; #if defined(MOZ_WIDGET_ANDROID) // On Android we add the active profile directory name between the path // and the 'cache2' leaf name. However, to delete any leftover data from // times before we were doing it, we still need to access the directory // w/o the profile name in the path. Here it is stored. nsCOMPtr mCacheProfilelessDirectory; #endif bool mTreeCreated{false}; bool mTreeCreationFailed{false}; CacheFileHandles mHandles; nsTArray mHandlesByLastUsed; nsTArray mSpecialHandles; nsTArray > mScheduledMetadataWrites; nsCOMPtr mMetadataWritesTimer; bool mOverLimitEvicting{false}; // When overlimit eviction is too slow and cache size reaches 105% of the // limit, this flag is set and no other content is cached to prevent // uncontrolled cache growing. bool mCacheSizeOnHardLimit{false}; bool mRemovingTrashDirs{false}; nsCOMPtr mTrashTimer; nsCOMPtr mTrashDir; nsCOMPtr mTrashDirEnumerator; nsTArray mFailedTrashDirs; RefPtr mContextEvictor; TimeStamp mLastSmartSizeTime; }; } // namespace net } // namespace mozilla #endif