summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheFileIOManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache2/CacheFileIOManager.cpp')
-rw-r--r--netwerk/cache2/CacheFileIOManager.cpp4499
1 files changed, 4499 insertions, 0 deletions
diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp
new file mode 100644
index 0000000000..8cff1c1cd9
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,4499 @@
+/* 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 <limits>
+#include "CacheLog.h"
+#include "CacheFileIOManager.h"
+
+#include "CacheHashUtils.h"
+#include "CacheStorageService.h"
+#include "CacheIndex.h"
+#include "CacheFileUtils.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "CacheFile.h"
+#include "CacheObserver.h"
+#include "nsIFile.h"
+#include "CacheFileContextEvictor.h"
+#include "nsITimer.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsEffectiveTLDService.h"
+#include "nsIObserverService.h"
+#include "nsISizeOf.h"
+#include "mozilla/net/MozURL.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "private/pprio.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasksRunner.h"
+# include "nsIBackgroundTasks.h"
+#endif
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <windows.h>
+# undef CreateFile
+# undef CREATE_NEW
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+namespace mozilla::net {
+
+#define kOpenHandlesLimit 128
+#define kMetadataWriteDelay 5000
+#define kRemoveTrashStartDelay 60000 // in milliseconds
+#define kSmartSizeUpdateInterval 60000 // in milliseconds
+
+#ifdef ANDROID
+const uint32_t kMaxCacheSizeKB = 512 * 1024; // 512 MB
+#else
+const uint32_t kMaxCacheSizeKB = 1024 * 1024; // 1 GB
+#endif
+const uint32_t kMaxClearOnShutdownCacheSizeKB = 150 * 1024; // 150 MB
+const auto kPurgeExtension = ".purge.bg_rm"_ns;
+
+bool CacheFileHandle::DispatchRelease() {
+ if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
+ if (!ioTarget) {
+ return false;
+ }
+
+ nsresult rv = ioTarget->Dispatch(
+ NewNonOwningRunnableMethod("net::CacheFileHandle::Release", this,
+ &CacheFileHandle::Release),
+ nsIEventTarget::DISPATCH_NORMAL);
+ return NS_SUCCEEDED(rv);
+}
+
+NS_IMPL_ADDREF(CacheFileHandle)
+NS_IMETHODIMP_(MozExternalRefCountType)
+CacheFileHandle::Release() {
+ nsrefcnt count = mRefCnt - 1;
+ if (DispatchRelease()) {
+ // Redispatched to the IO thread.
+ return count;
+ }
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR "]", this,
+ mRefCnt.get()));
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "CacheFileHandle");
+
+ if (0 == count) {
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash* aHash, bool aPriority,
+ PinningStatus aPinning)
+ : mHash(aHash),
+ mIsDoomed(false),
+ mClosed(false),
+ mPriority(aPriority),
+ mSpecialFile(false),
+ mInvalid(false),
+ mFileExists(false),
+ mDoomWhenFoundPinned(false),
+ mDoomWhenFoundNonPinned(false),
+ mKilled(false),
+ mPinning(aPinning),
+ mFileSize(-1),
+ mFD(nullptr) {
+ // If we initialize mDoomed in the initialization list, that initialization is
+ // not guaranteeded to be atomic. Whereas this assignment here is guaranteed
+ // to be atomic. TSan will see this (atomic) assignment and be satisfied
+ // that cross-thread accesses to mIsDoomed are properly synchronized.
+ mIsDoomed = false;
+ LOG((
+ "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",
+ this, LOGSHA1(aHash)));
+}
+
+CacheFileHandle::CacheFileHandle(const nsACString& aKey, bool aPriority,
+ PinningStatus aPinning)
+ : mHash(nullptr),
+ mIsDoomed(false),
+ mClosed(false),
+ mPriority(aPriority),
+ mSpecialFile(true),
+ mInvalid(false),
+ mFileExists(false),
+ mDoomWhenFoundPinned(false),
+ mDoomWhenFoundNonPinned(false),
+ mKilled(false),
+ mPinning(aPinning),
+ mFileSize(-1),
+ mFD(nullptr),
+ mKey(aKey) {
+ // See comment above about the initialization of mIsDoomed.
+ mIsDoomed = false;
+ LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
+ PromiseFlatCString(aKey).get()));
+}
+
+CacheFileHandle::~CacheFileHandle() {
+ LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
+ if (!IsClosed() && ioMan) {
+ ioMan->CloseHandleInternal(this);
+ }
+}
+
+void CacheFileHandle::Log() {
+ nsAutoCString leafName;
+ if (mFile) {
+ mFile->GetNativeLeafName(leafName);
+ }
+
+ if (mSpecialFile) {
+ LOG(
+ ("CacheFileHandle::Log() - special file [this=%p, "
+ "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
+ "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
+ ", leafName=%s, key=%s]",
+ this, bool(mIsDoomed), bool(mPriority), bool(mClosed), bool(mInvalid),
+ static_cast<uint32_t>(mPinning), bool(mFileExists), int64_t(mFileSize),
+ leafName.get(), mKey.get()));
+ } else {
+ LOG(
+ ("CacheFileHandle::Log() - entry file [this=%p, "
+ "hash=%08x%08x%08x%08x%08x, "
+ "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
+ "pinning=%" PRIu32 ", fileExists=%d, fileSize=%" PRId64
+ ", leafName=%s, key=%s]",
+ this, LOGSHA1(mHash), bool(mIsDoomed), bool(mPriority), bool(mClosed),
+ bool(mInvalid), static_cast<uint32_t>(mPinning), bool(mFileExists),
+ int64_t(mFileSize), leafName.get(), mKey.get()));
+ }
+}
+
+uint32_t CacheFileHandle::FileSizeInK() const {
+ MOZ_ASSERT(mFileSize != -1);
+ uint64_t size64 = mFileSize;
+
+ size64 += 0x3FF;
+ size64 >>= 10;
+
+ uint32_t size;
+ if (size64 >> 32) {
+ NS_WARNING(
+ "CacheFileHandle::FileSizeInK() - FileSize is too large, "
+ "truncating to PR_UINT32_MAX");
+ size = PR_UINT32_MAX;
+ } else {
+ size = static_cast<uint32_t>(size64);
+ }
+
+ return size;
+}
+
+bool CacheFileHandle::SetPinned(bool aPinned) {
+ LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ mPinning = aPinned ? PinningStatus::PINNED : PinningStatus::NON_PINNED;
+
+ if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
+ (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
+ LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
+ bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
+
+ mDoomWhenFoundPinned = false;
+ mDoomWhenFoundNonPinned = false;
+
+ return false;
+ }
+
+ return true;
+}
+
+// Memory reporting
+
+size_t CacheFileHandle::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ nsCOMPtr<nsISizeOf> sizeOf;
+
+ sizeOf = do_QueryInterface(mFile);
+ if (sizeOf) {
+ n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ n += mallocSizeOf(mFD);
+ n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ return n;
+}
+
+size_t CacheFileHandle::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+/******************************************************************************
+ * CacheFileHandles::HandleHashKey
+ *****************************************************************************/
+
+void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ mHandles.InsertElementAt(0, aHandle);
+}
+
+void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ DebugOnly<bool> found{};
+ found = mHandles.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+}
+
+already_AddRefed<CacheFileHandle>
+CacheFileHandles::HandleHashKey::GetNewestHandle() {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ RefPtr<CacheFileHandle> handle;
+ if (mHandles.Length()) {
+ handle = mHandles[0];
+ }
+
+ return handle.forget();
+}
+
+void CacheFileHandles::HandleHashKey::GetHandles(
+ nsTArray<RefPtr<CacheFileHandle>>& aResult) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ CacheFileHandle* handle = mHandles[i];
+ aResult.AppendElement(handle);
+ }
+}
+
+#ifdef DEBUG
+
+void CacheFileHandles::HandleHashKey::AssertHandlesState() {
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ CacheFileHandle* handle = mHandles[i];
+ MOZ_ASSERT(handle->IsDoomed());
+ }
+}
+
+#endif
+
+size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ size_t n = 0;
+ n += mallocSizeOf(mHash.get());
+ for (uint32_t i = 0; i < mHandles.Length(); ++i) {
+ n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ return n;
+}
+
+/******************************************************************************
+ * CacheFileHandles
+ *****************************************************************************/
+
+CacheFileHandles::CacheFileHandles() {
+ LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
+ MOZ_COUNT_CTOR(CacheFileHandles);
+}
+
+CacheFileHandles::~CacheFileHandles() {
+ LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
+ MOZ_COUNT_DTOR(CacheFileHandles);
+}
+
+nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash* aHash,
+ CacheFileHandle** _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHash);
+
+#ifdef DEBUG_HANDLES
+ LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.GetEntry(*aHash);
+ if (!entry) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "no handle entries found",
+ LOGSHA1(aHash)));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+ // Check if the entry is doomed
+ RefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
+ if (!handle) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "no handle found %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (handle->IsDoomed()) {
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "found doomed handle %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
+ "found handle %p, entry %p",
+ LOGSHA1(aHash), handle.get(), entry));
+
+ handle.forget(_retval);
+ return NS_OK;
+}
+
+already_AddRefed<CacheFileHandle> CacheFileHandles::NewHandle(
+ const SHA1Sum::Hash* aHash, bool aPriority,
+ CacheFileHandle::PinningStatus aPinning) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHash);
+
+#ifdef DEBUG_HANDLES
+ LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.PutEntry(*aHash);
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+#ifdef DEBUG
+ entry->AssertHandlesState();
+#endif
+
+ RefPtr<CacheFileHandle> handle =
+ new CacheFileHandle(entry->Hash(), aPriority, aPinning);
+ entry->AddHandle(handle);
+
+ LOG(
+ ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
+ "created new handle %p, entry=%p",
+ LOGSHA1(aHash), handle.get(), entry));
+ return handle.forget();
+}
+
+void CacheFileHandles::RemoveHandle(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHandle);
+
+ if (!aHandle) {
+ return;
+ }
+
+#ifdef DEBUG_HANDLES
+ LOG((
+ "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",
+ aHandle, LOGSHA1(aHandle->Hash())));
+#endif
+
+ // find hash entry for key
+ HandleHashKey* entry = mTable.GetEntry(*aHandle->Hash());
+ if (!entry) {
+ MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
+ "Should find entry when removing a handle before shutdown");
+
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "no entries found",
+ LOGSHA1(aHandle->Hash())));
+ return;
+ }
+
+#ifdef DEBUG_HANDLES
+ Log(entry);
+#endif
+
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "removing handle %p",
+ LOGSHA1(entry->Hash()), aHandle));
+ entry->RemoveHandle(aHandle);
+
+ if (entry->IsEmpty()) {
+ LOG(
+ ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
+ "list is empty, removing entry %p",
+ LOGSHA1(entry->Hash()), entry));
+ mTable.RemoveEntry(entry);
+ }
+}
+
+void CacheFileHandles::GetAllHandles(
+ nsTArray<RefPtr<CacheFileHandle>>* _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetHandles(*_retval);
+ }
+}
+
+void CacheFileHandles::GetActiveHandles(
+ nsTArray<RefPtr<CacheFileHandle>>* _retval) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle();
+ MOZ_ASSERT(handle);
+
+ if (!handle->IsDoomed()) {
+ _retval->AppendElement(handle);
+ }
+ }
+}
+
+void CacheFileHandles::ClearAll() {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ mTable.Clear();
+}
+
+uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); }
+
+#ifdef DEBUG_HANDLES
+void CacheFileHandles::Log(CacheFileHandlesEntry* entry) {
+ LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));
+
+ nsTArray<RefPtr<CacheFileHandle>> array;
+ aEntry->GetHandles(array);
+
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ CacheFileHandle* handle = array[i];
+ handle->Log();
+ }
+
+ LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
+}
+#endif
+
+// Memory reporting
+
+size_t CacheFileHandles::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ return mTable.SizeOfExcludingThis(mallocSizeOf);
+}
+
+// Events
+
+class ShutdownEvent : public Runnable, nsITimerCallback {
+ NS_DECL_ISUPPORTS_INHERITED
+ public:
+ ShutdownEvent() : Runnable("net::ShutdownEvent") {}
+
+ protected:
+ ~ShutdownEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ CacheFileIOManager::gInstance->ShutdownInternal();
+
+ mNotified = true;
+
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("CacheFileIOManager::ShutdownEvent::Run", []() {
+ // This empty runnable is dispatched just in case the MT event loop
+ // becomes empty - we need to process a task to break out of
+ // SpinEventLoopUntil.
+ }));
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Notify(nsITimer* timer) override {
+ if (mNotified) {
+ return NS_OK;
+ }
+
+ // If there is any IO blocking on the IO thread, this will
+ // try to cancel it.
+ CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();
+
+ // After this runs the first time, the browser_cache_max_shutdown_io_lag
+ // time has elapsed. The CacheIO thread may pick up more blocking IO tasks
+ // so we want to block those too if necessary.
+ mTimer->SetDelay(
+ StaticPrefs::browser_cache_shutdown_io_time_between_cancellations_ms());
+ return NS_OK;
+ }
+
+ void PostAndWait() {
+ nsresult rv = CacheFileIOManager::gInstance->mIOThread->Dispatch(
+ this,
+ CacheIOThread::WRITE); // When writes and closing of handles is done
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // If we failed to post the even there's no reason to go into the loop
+ // because mNotified will never be set to true.
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Posting ShutdownEvent task failed");
+ return;
+ }
+
+ rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this,
+ StaticPrefs::browser_cache_max_shutdown_io_lag() * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ mozilla::SpinEventLoopUntil("CacheFileIOManager::ShutdownEvent"_ns,
+ [&]() { return bool(mNotified); });
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ }
+
+ protected:
+ Atomic<bool> mNotified{false};
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(ShutdownEvent, Runnable, nsITimerCallback)
+
+// Class responsible for reporting IO performance stats
+class IOPerfReportEvent {
+ public:
+ explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType)
+ : mType(aType), mEventCounter(0) {}
+
+ void Start(CacheIOThread* aIOThread) {
+ mStartTime = TimeStamp::Now();
+ mEventCounter = aIOThread->EventCounter();
+ }
+
+ void Report(CacheIOThread* aIOThread) {
+ if (mStartTime.IsNull()) {
+ return;
+ }
+
+ // Single IO operations can take less than 1ms. So we use microseconds to
+ // keep a good resolution of data.
+ uint32_t duration = (TimeStamp::Now() - mStartTime).ToMicroseconds();
+
+ // This is a simple prefiltering of values that might differ a lot from the
+ // average value. Do not add the value to the filtered stats when the event
+ // had to wait in a long queue.
+ uint32_t eventCounter = aIOThread->EventCounter();
+ bool shortOnly = eventCounter - mEventCounter >= 5;
+
+ CacheFileUtils::CachePerfStats::AddValue(mType, duration, shortOnly);
+ }
+
+ protected:
+ CacheFileUtils::CachePerfStats::EDataType mType;
+ TimeStamp mStartTime;
+ uint32_t mEventCounter;
+};
+
+class OpenFileEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ OpenFileEvent(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::OpenFileEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN),
+ mFlags(aFlags),
+ mCallback(aCallback),
+ mKey(aKey) {
+ mIOMan = CacheFileIOManager::gInstance;
+ if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
+ Start(mIOMan->mIOThread);
+ }
+ }
+
+ protected:
+ ~OpenFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv = NS_OK;
+
+ if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
+ SHA1Sum sum;
+ sum.update(mKey.BeginReading(), mKey.Length());
+ sum.finish(mHash);
+ }
+
+ if (!mIOMan) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
+ rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
+ getter_AddRefs(mHandle));
+ } else {
+ rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
+ getter_AddRefs(mHandle));
+ if (NS_SUCCEEDED(rv)) {
+ Report(mIOMan->mIOThread);
+ }
+ }
+ mIOMan = nullptr;
+ if (mHandle) {
+ if (mHandle->Key().IsEmpty()) {
+ mHandle->Key() = mKey;
+ }
+ }
+ }
+
+ mCallback->OnFileOpened(mHandle, rv);
+ return NS_OK;
+ }
+
+ protected:
+ SHA1Sum::Hash mHash{};
+ uint32_t mFlags;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ RefPtr<CacheFileIOManager> mIOMan;
+ RefPtr<CacheFileHandle> mHandle;
+ nsCString mKey;
+};
+
+class ReadEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ ReadEvent(CacheFileHandle* aHandle, int64_t aOffset, char* aBuf,
+ int32_t aCount, CacheFileIOListener* aCallback)
+ : Runnable("net::ReadEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ),
+ mHandle(aHandle),
+ mOffset(aOffset),
+ mBuf(aBuf),
+ mCount(aCount),
+ mCallback(aCallback) {
+ if (!mHandle->IsSpecialFile()) {
+ Start(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ protected:
+ ~ReadEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->ReadInternal(mHandle, mOffset, mBuf,
+ mCount);
+ if (NS_SUCCEEDED(rv)) {
+ Report(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ mCallback->OnDataRead(mHandle, mBuf, rv);
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mOffset;
+ char* mBuf;
+ int32_t mCount;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class WriteEvent : public Runnable, public IOPerfReportEvent {
+ public:
+ WriteEvent(CacheFileHandle* aHandle, int64_t aOffset, const char* aBuf,
+ int32_t aCount, bool aValidate, bool aTruncate,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::WriteEvent"),
+ IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE),
+ mHandle(aHandle),
+ mOffset(aOffset),
+ mBuf(aBuf),
+ mCount(aCount),
+ mValidate(aValidate),
+ mTruncate(aTruncate),
+ mCallback(aCallback) {
+ if (!mHandle->IsSpecialFile()) {
+ Start(CacheFileIOManager::gInstance->mIOThread);
+ }
+ }
+
+ protected:
+ ~WriteEvent() {
+ if (!mCallback && mBuf) {
+ free(const_cast<char*>(mBuf));
+ }
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ // We usually get here only after the internal shutdown
+ // (i.e. mShuttingDown == true). Pretend write has succeeded
+ // to avoid any past-shutdown file dooming.
+ rv = (CacheObserver::IsPastShutdownIOLag() ||
+ CacheFileIOManager::gInstance->mShuttingDown)
+ ? NS_OK
+ : NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->WriteInternal(
+ mHandle, mOffset, mBuf, mCount, mValidate, mTruncate);
+ if (NS_SUCCEEDED(rv)) {
+ Report(CacheFileIOManager::gInstance->mIOThread);
+ }
+ if (NS_FAILED(rv) && !mCallback) {
+ // No listener is going to handle the error, doom the file
+ CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+ }
+ }
+ if (mCallback) {
+ mCallback->OnDataWritten(mHandle, mBuf, rv);
+ } else {
+ free(const_cast<char*>(mBuf));
+ mBuf = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mOffset;
+ const char* mBuf;
+ int32_t mCount;
+ bool mValidate : 1;
+ bool mTruncate : 1;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class DoomFileEvent : public Runnable {
+ public:
+ DoomFileEvent(CacheFileHandle* aHandle, CacheFileIOListener* aCallback)
+ : Runnable("net::DoomFileEvent"),
+ mCallback(aCallback),
+ mHandle(aHandle) {}
+
+ protected:
+ ~DoomFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed()) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+ }
+
+ if (mCallback) {
+ mCallback->OnFileDoomed(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ RefPtr<CacheFileHandle> mHandle;
+};
+
+class DoomFileByKeyEvent : public Runnable {
+ public:
+ DoomFileByKeyEvent(const nsACString& aKey, CacheFileIOListener* aCallback)
+ : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback) {
+ SHA1Sum sum;
+ sum.update(aKey.BeginReading(), aKey.Length());
+ sum.finish(mHash);
+
+ mIOMan = CacheFileIOManager::gInstance;
+ }
+
+ protected:
+ ~DoomFileByKeyEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (!mIOMan) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = mIOMan->DoomFileByKeyInternal(&mHash);
+ mIOMan = nullptr;
+ }
+
+ if (mCallback) {
+ mCallback->OnFileDoomed(nullptr, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ SHA1Sum::Hash mHash{};
+ nsCOMPtr<CacheFileIOListener> mCallback;
+ RefPtr<CacheFileIOManager> mIOMan;
+};
+
+class ReleaseNSPRHandleEvent : public Runnable {
+ public:
+ explicit ReleaseNSPRHandleEvent(CacheFileHandle* aHandle)
+ : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle) {}
+
+ protected:
+ ~ReleaseNSPRHandleEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (!mHandle->IsClosed()) {
+ CacheFileIOManager::gInstance->MaybeReleaseNSPRHandleInternal(mHandle);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+};
+
+class TruncateSeekSetEOFEvent : public Runnable {
+ public:
+ TruncateSeekSetEOFEvent(CacheFileHandle* aHandle, int64_t aTruncatePos,
+ int64_t aEOFPos, CacheFileIOListener* aCallback)
+ : Runnable("net::TruncateSeekSetEOFEvent"),
+ mHandle(aHandle),
+ mTruncatePos(aTruncatePos),
+ mEOFPos(aEOFPos),
+ mCallback(aCallback) {}
+
+ protected:
+ ~TruncateSeekSetEOFEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed() || (mCallback && mCallback->IsKilled())) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
+ mHandle, mTruncatePos, mEOFPos);
+ }
+
+ if (mCallback) {
+ mCallback->OnEOFSet(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ int64_t mTruncatePos;
+ int64_t mEOFPos;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class RenameFileEvent : public Runnable {
+ public:
+ RenameFileEvent(CacheFileHandle* aHandle, const nsACString& aNewName,
+ CacheFileIOListener* aCallback)
+ : Runnable("net::RenameFileEvent"),
+ mHandle(aHandle),
+ mNewName(aNewName),
+ mCallback(aCallback) {}
+
+ protected:
+ ~RenameFileEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ if (mHandle->IsClosed()) {
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName);
+ }
+
+ if (mCallback) {
+ mCallback->OnFileRenamed(mHandle, rv);
+ }
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ nsCString mNewName;
+ nsCOMPtr<CacheFileIOListener> mCallback;
+};
+
+class InitIndexEntryEvent : public Runnable {
+ public:
+ InitIndexEntryEvent(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash, bool aAnonymous,
+ bool aPinning)
+ : Runnable("net::InitIndexEntryEvent"),
+ mHandle(aHandle),
+ mOriginAttrsHash(aOriginAttrsHash),
+ mAnonymous(aAnonymous),
+ mPinning(aPinning) {}
+
+ protected:
+ ~InitIndexEntryEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (mHandle->IsClosed() || mHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIndex::InitEntry(mHandle->Hash(), mOriginAttrsHash, mAnonymous,
+ mPinning);
+
+ // We cannot set the filesize before we init the entry. If we're opening
+ // an existing entry file, frecency will be set after parsing the entry
+ // file, but we must set the filesize here since nobody is going to set it
+ // if there is no write to the file.
+ uint32_t sizeInK = mHandle->FileSizeInK();
+ CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
+ nullptr, &sizeInK);
+
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+ OriginAttrsHash mOriginAttrsHash;
+ bool mAnonymous;
+ bool mPinning;
+};
+
+class UpdateIndexEntryEvent : public Runnable {
+ public:
+ UpdateIndexEntryEvent(CacheFileHandle* aHandle, const uint32_t* aFrecency,
+ const bool* aHasAltData, const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType)
+ : Runnable("net::UpdateIndexEntryEvent"),
+ mHandle(aHandle),
+ mHasFrecency(false),
+ mHasHasAltData(false),
+ mHasOnStartTime(false),
+ mHasOnStopTime(false),
+ mHasContentType(false),
+ mFrecency(0),
+ mHasAltData(false),
+ mOnStartTime(0),
+ mOnStopTime(0),
+ mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) {
+ if (aFrecency) {
+ mHasFrecency = true;
+ mFrecency = *aFrecency;
+ }
+ if (aHasAltData) {
+ mHasHasAltData = true;
+ mHasAltData = *aHasAltData;
+ }
+ if (aOnStartTime) {
+ mHasOnStartTime = true;
+ mOnStartTime = *aOnStartTime;
+ }
+ if (aOnStopTime) {
+ mHasOnStopTime = true;
+ mOnStopTime = *aOnStopTime;
+ }
+ if (aContentType) {
+ mHasContentType = true;
+ mContentType = *aContentType;
+ }
+ }
+
+ protected:
+ ~UpdateIndexEntryEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ if (mHandle->IsClosed() || mHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIndex::UpdateEntry(mHandle->Hash(),
+ mHasFrecency ? &mFrecency : nullptr,
+ mHasHasAltData ? &mHasAltData : nullptr,
+ mHasOnStartTime ? &mOnStartTime : nullptr,
+ mHasOnStopTime ? &mOnStopTime : nullptr,
+ mHasContentType ? &mContentType : nullptr, nullptr);
+ return NS_OK;
+ }
+
+ protected:
+ RefPtr<CacheFileHandle> mHandle;
+
+ bool mHasFrecency;
+ bool mHasHasAltData;
+ bool mHasOnStartTime;
+ bool mHasOnStopTime;
+ bool mHasContentType;
+
+ uint32_t mFrecency;
+ bool mHasAltData;
+ uint16_t mOnStartTime;
+ uint16_t mOnStopTime;
+ uint8_t mContentType;
+};
+
+class MetadataWriteScheduleEvent : public Runnable {
+ public:
+ enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode;
+
+ RefPtr<CacheFile> mFile;
+ RefPtr<CacheFileIOManager> mIOMan;
+
+ MetadataWriteScheduleEvent(CacheFileIOManager* aManager, CacheFile* aFile,
+ EMode aMode)
+ : Runnable("net::MetadataWriteScheduleEvent"),
+ mMode(aMode),
+ mFile(aFile),
+ mIOMan(aManager) {}
+
+ virtual ~MetadataWriteScheduleEvent() = default;
+
+ NS_IMETHOD Run() override {
+ RefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
+ if (!ioMan) {
+ NS_WARNING(
+ "CacheFileIOManager already gone in "
+ "MetadataWriteScheduleEvent::Run()");
+ return NS_OK;
+ }
+
+ switch (mMode) {
+ case SCHEDULE:
+ ioMan->ScheduleMetadataWriteInternal(mFile);
+ break;
+ case UNSCHEDULE:
+ ioMan->UnscheduleMetadataWriteInternal(mFile);
+ break;
+ case SHUTDOWN:
+ ioMan->ShutdownMetadataWriteSchedulingInternal();
+ break;
+ }
+ return NS_OK;
+ }
+};
+
+StaticRefPtr<CacheFileIOManager> CacheFileIOManager::gInstance;
+
+NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback, nsINamed)
+
+CacheFileIOManager::CacheFileIOManager()
+
+{
+ LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
+ MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
+}
+
+CacheFileIOManager::~CacheFileIOManager() {
+ LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
+}
+
+// static
+nsresult CacheFileIOManager::Init() {
+ LOG(("CacheFileIOManager::Init()"));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gInstance) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
+
+ nsresult rv = ioMan->InitInternal();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gInstance = std::move(ioMan);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::InitInternal() {
+ nsresult rv;
+
+ mIOThread = new CacheIOThread();
+
+ rv = mIOThread->Init();
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStartTime = TimeStamp::NowLoRes();
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::Shutdown() {
+ LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance.get()));
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gInstance) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;
+
+ CacheIndex::PreShutdown();
+
+ ShutdownMetadataWriteScheduling();
+
+ RefPtr<ShutdownEvent> ev = new ShutdownEvent();
+ ev->PostAndWait();
+
+ MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
+ MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
+
+ if (gInstance->mIOThread) {
+ gInstance->mIOThread->Shutdown();
+ }
+
+ CacheIndex::Shutdown();
+
+ if (CacheObserver::ClearCacheOnShutdown()) {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE>
+ totalTimer;
+ gInstance->SyncRemoveAllCacheFiles();
+ }
+
+ gInstance = nullptr;
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::ShutdownInternal() {
+ LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ // No new handles can be created after this flag is set
+ mShuttingDown = true;
+
+ if (mTrashTimer) {
+ mTrashTimer->Cancel();
+ mTrashTimer = nullptr;
+ }
+
+ // close all handles and delete all associated files
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetAllHandles(&handles);
+ handles.AppendElements(mSpecialHandles);
+
+ for (uint32_t i = 0; i < handles.Length(); i++) {
+ CacheFileHandle* h = handles[i];
+ h->mClosed = true;
+
+ h->Log();
+
+ // Close completely written files.
+ MaybeReleaseNSPRHandleInternal(h);
+ // Don't bother removing invalid and/or doomed files to improve
+ // shutdown perfomrance.
+ // Doomed files are already in the doomed directory from which
+ // we never reuse files and delete the dir on next session startup.
+ // Invalid files don't have metadata and thus won't load anyway
+ // (hashes won't match).
+
+ if (!h->IsSpecialFile() && !h->mIsDoomed && !h->mFileExists) {
+ CacheIndex::RemoveEntry(h->Hash());
+ }
+
+ // Remove the handle from mHandles/mSpecialHandles
+ if (h->IsSpecialFile()) {
+ mSpecialHandles.RemoveElement(h);
+ } else {
+ mHandles.RemoveHandle(h);
+ }
+
+ // Pointer to the hash is no longer valid once the last handle with the
+ // given hash is released. Null out the pointer so that we crash if there
+ // is a bug in this code and we dereference the pointer after this point.
+ if (!h->IsSpecialFile()) {
+ h->mHash = nullptr;
+ }
+ }
+
+ // Assert the table is empty. When we are here, no new handles can be added
+ // and handles will no longer remove them self from this table and we don't
+ // want to keep invalid handles here. Also, there is no lookup after this
+ // point to happen.
+ MOZ_ASSERT(mHandles.HandleCount() == 0);
+
+ // Release trash directory enumerator
+ if (mTrashDirEnumerator) {
+ mTrashDirEnumerator->Close();
+ mTrashDirEnumerator = nullptr;
+ }
+
+ if (mContextEvictor) {
+ mContextEvictor->Shutdown();
+ mContextEvictor = nullptr;
+ }
+}
+
+// static
+nsresult CacheFileIOManager::OnProfile() {
+ LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance.get()));
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan) {
+ // CacheFileIOManager::Init() failed, probably could not create the IO
+ // thread, just go with it...
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> directory;
+
+ CacheObserver::ParentDirOverride(getter_AddRefs(directory));
+
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> profilelessDirectory;
+ char* cachePath = getenv("CACHE_DIRECTORY");
+ if (!directory && cachePath && *cachePath) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), true,
+ getter_AddRefs(directory));
+ if (NS_SUCCEEDED(rv)) {
+ // Save this directory as the profileless path.
+ rv = directory->Clone(getter_AddRefs(profilelessDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add profile leaf name to the directory name to distinguish
+ // multiple profiles Fennec supports.
+ nsCOMPtr<nsIFile> profD;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profD));
+
+ nsAutoCString leafName;
+ if (NS_SUCCEEDED(rv)) {
+ rv = profD->GetNativeLeafName(leafName);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = directory->AppendNative(leafName);
+ }
+ if (NS_FAILED(rv)) {
+ directory = nullptr;
+ }
+ }
+ }
+#endif
+
+ if (!directory) {
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ }
+
+ if (!directory) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ }
+
+ if (directory) {
+ rv = directory->Append(u"cache2"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // All functions return a clone.
+ ioMan->mCacheDirectory.swap(directory);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ if (profilelessDirectory) {
+ rv = profilelessDirectory->Append(u"cache2"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
+#endif
+
+ if (ioMan->mCacheDirectory) {
+ CacheIndex::Init(ioMan->mCacheDirectory);
+ }
+
+ return NS_OK;
+}
+
+static bool inBackgroundTask() {
+ MOZ_ASSERT(NS_IsMainThread(), "backgroundtasks are main thread only");
+#if defined(MOZ_BACKGROUNDTASKS)
+ nsCOMPtr<nsIBackgroundTasks> backgroundTaskService =
+ do_GetService("@mozilla.org/backgroundtasks;1");
+ if (!backgroundTaskService) {
+ return false;
+ }
+ bool isBackgroundTask = false;
+ backgroundTaskService->GetIsBackgroundTaskMode(&isBackgroundTask);
+ return isBackgroundTask;
+#else
+ return false;
+#endif
+}
+
+// static
+nsresult CacheFileIOManager::OnDelayedStartupFinished() {
+ // If we don't clear the cache at shutdown, or we don't use a
+ // background task then there's no need to dispatch a cleanup task
+ // at startup
+ if (!CacheObserver::ClearCacheOnShutdown()) {
+ return NS_OK;
+ }
+ if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ return NS_OK;
+ }
+
+ // If this is a background task already, there's no need to
+ // dispatch another one.
+ if (inBackgroundTask()) {
+ return NS_OK;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ nsCOMPtr<nsIEventTarget> target = IOTarget();
+ if (NS_WARN_IF(!ioMan || !target)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return target->Dispatch(
+ NS_NewRunnableFunction("CacheFileIOManager::OnDelayedStartupFinished",
+ [ioMan = std::move(ioMan)] {
+ ioMan->DispatchPurgeTask(""_ns, "0"_ns,
+ kPurgeExtension);
+ }),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+// static
+nsresult CacheFileIOManager::OnIdleDaily() {
+ // In case the background task process fails for some reason (bug 1848542)
+ // We will remove the directories in the main process on a daily idle.
+ if (!CacheObserver::ClearCacheOnShutdown()) {
+ return NS_OK;
+ }
+ if (!StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ return NS_OK;
+ }
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ nsCOMPtr<nsIFile> parentDir;
+ nsresult rv = ioMan->mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+ if (NS_FAILED(rv) || !parentDir) {
+ return NS_OK;
+ }
+
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "CacheFileIOManager::CheckLeftoverFolders",
+ [dir = std::move(parentDir)] {
+ nsCOMPtr<nsIDirectoryEnumerator> directories;
+ nsresult rv = dir->GetDirectoryEntries(getter_AddRefs(directories));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ bool hasMoreElements = false;
+ while (
+ NS_SUCCEEDED(directories->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsIFile> subdir;
+ rv = directories->GetNextFile(getter_AddRefs(subdir));
+ if (NS_FAILED(rv) || !subdir) {
+ break;
+ }
+ nsAutoCString leafName;
+ rv = subdir->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ if (leafName.Find(kPurgeExtension) != kNotFound) {
+ mozilla::glean::networking::residual_cache_folder_count.Add(1);
+ rv = subdir->Remove(true);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::glean::networking::residual_cache_folder_removal
+ .Get("success"_ns)
+ .Add(1);
+ } else {
+ mozilla::glean::networking::residual_cache_folder_removal
+ .Get("failure"_ns)
+ .Add(1);
+ }
+ }
+ }
+
+ return;
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ return NS_OK;
+}
+
+// static
+already_AddRefed<nsIEventTarget> CacheFileIOManager::IOTarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ if (gInstance && gInstance->mIOThread) {
+ target = gInstance->mIOThread->Target();
+ }
+
+ return target.forget();
+}
+
+// static
+already_AddRefed<CacheIOThread> CacheFileIOManager::IOThread() {
+ RefPtr<CacheIOThread> thread;
+ if (gInstance) {
+ thread = gInstance->mIOThread;
+ }
+
+ return thread.forget();
+}
+
+// static
+bool CacheFileIOManager::IsOnIOThread() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (ioMan && ioMan->mIOThread) {
+ return ioMan->mIOThread->IsCurrentThread();
+ }
+
+ return false;
+}
+
+// static
+bool CacheFileIOManager::IsOnIOThreadOrCeased() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (ioMan && ioMan->mIOThread) {
+ return ioMan->mIOThread->IsCurrentThread();
+ }
+
+ // Ceased...
+ return true;
+}
+
+// static
+bool CacheFileIOManager::IsShutdown() {
+ if (!gInstance) {
+ return true;
+ }
+ return gInstance->mShuttingDown;
+}
+
+// static
+nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile* aFile) {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile* aFile) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (!mMetadataWritesTimer) {
+ rv = NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer), this,
+ kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mScheduledMetadataWrites.IndexOf(aFile) !=
+ nsTArray<RefPtr<mozilla::net::CacheFile>>::NoIndex) {
+ return NS_OK;
+ }
+
+ mScheduledMetadataWrites.AppendElement(aFile);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile* aFile) {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile* aFile) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ mScheduledMetadataWrites.RemoveElement(aFile);
+
+ if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+}
+
+// static
+nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() {
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
+
+ RefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
+ ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
+ nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+
+ nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile* file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ if (mMetadataWritesTimer) {
+ mMetadataWritesTimer->Cancel();
+ mMetadataWritesTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+CacheFileIOManager::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(IsOnIOThreadOrCeased());
+ MOZ_ASSERT(mMetadataWritesTimer == aTimer);
+
+ mMetadataWritesTimer = nullptr;
+
+ nsTArray<RefPtr<CacheFile>> files = std::move(mScheduledMetadataWrites);
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ CacheFile* file = files[i];
+ file->WriteMetadataIfNeeded();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheFileIOManager::GetName(nsACString& aName) {
+ aName.AssignLiteral("CacheFileIOManager");
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::OpenFile(const nsACString& aKey, uint32_t aFlags,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
+ PromiseFlatCString(aKey).get(), aFlags, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool priority = aFlags & CacheFileIOManager::PRIORITY;
+ RefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
+ rv = ioMan->mIOThread->Dispatch(
+ ev, priority ? CacheIOThread::OPEN_PRIORITY : CacheIOThread::OPEN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash,
+ const nsACString& aKey,
+ uint32_t aFlags,
+ CacheFileHandle** _retval) {
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
+ "key=%s, flags=%d]",
+ LOGSHA1(aHash), PromiseFlatCString(aKey).get(), aFlags));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ CacheIOThread::Cancelable cancelable(
+ true /* never called for special handles */);
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ CacheFileHandle::PinningStatus pinning =
+ aFlags & PINNED ? CacheFileHandle::PinningStatus::PINNED
+ : CacheFileHandle::PinningStatus::NON_PINNED;
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aHash, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheFileHandle> handle;
+ mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+ if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
+ if (handle) {
+ rv = DoomFileInternal(handle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ handle = nullptr;
+ }
+
+ handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ CacheIndex::RemoveEntry(aHash);
+
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() - Removing old file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
+ ". [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ CacheIndex::AddEntry(aHash);
+ handle->mFile.swap(file);
+ handle->mFileSize = 0;
+ }
+
+ if (handle) {
+ handle.swap(*_retval);
+ return NS_OK;
+ }
+
+ bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists && mContextEvictor) {
+ if (mContextEvictor->ContextsCount() == 0) {
+ mContextEvictor = nullptr;
+ } else {
+ mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned,
+ &evictedAsNonPinned);
+ }
+ }
+
+ if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (exists) {
+ // For existing files we determine the pinning status later, after the
+ // metadata gets parsed.
+ pinning = CacheFileHandle::PinningStatus::UNKNOWN;
+ }
+
+ handle = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning);
+ if (exists) {
+ // If this file has been found evicted through the context file evictor
+ // above for any of pinned or non-pinned state, these calls ensure we doom
+ // the handle ASAP we know the real pinning state after metadta has been
+ // parsed. DoomFileInternal on the |handle| doesn't doom right now, since
+ // the pinning state is unknown and we pass down a pinning restriction.
+ if (evictedAsPinned) {
+ rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
+ MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+ }
+ if (evictedAsNonPinned) {
+ rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
+ MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+ }
+
+ int64_t fileSize = -1;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handle->mFileSize = fileSize;
+ handle->mFileExists = true;
+
+ CacheIndex::EnsureEntryExists(aHash);
+ } else {
+ handle->mFileSize = 0;
+
+ CacheIndex::AddEntry(aHash);
+ }
+
+ handle->mFile.swap(file);
+ handle.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenSpecialFileInternal(
+ const nsACString& aKey, uint32_t aFlags, CacheFileHandle** _retval) {
+ LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
+ PromiseFlatCString(aKey).get(), aFlags));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetSpecialFile(aKey, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CacheFileHandle> handle;
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
+ if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
+ handle = mSpecialHandles[i];
+ break;
+ }
+ }
+
+ if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
+ if (handle) {
+ rv = DoomFileInternal(handle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ handle = nullptr;
+ }
+
+ handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
+ CacheFileHandle::PinningStatus::NON_PINNED);
+ mSpecialHandles.AppendElement(handle);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ LOG(
+ ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
+ "failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ handle->mFile.swap(file);
+ handle->mFileSize = 0;
+ }
+
+ if (handle) {
+ handle.swap(*_retval);
+ return NS_OK;
+ }
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ handle = new CacheFileHandle(aKey, aFlags & PRIORITY,
+ CacheFileHandle::PinningStatus::NON_PINNED);
+ mSpecialHandles.AppendElement(handle);
+
+ if (exists) {
+ int64_t fileSize = -1;
+ rv = file->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handle->mFileSize = fileSize;
+ handle->mFileExists = true;
+ } else {
+ handle->mFileSize = 0;
+ }
+
+ handle->mFile.swap(file);
+ handle.swap(*_retval);
+ return NS_OK;
+}
+
+void CacheFileIOManager::CloseHandleInternal(CacheFileHandle* aHandle) {
+ nsresult rv;
+ LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));
+
+ MOZ_ASSERT(!aHandle->IsClosed());
+
+ aHandle->Log();
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ // Maybe close file handle (can be legally bypassed after shutdown)
+ rv = MaybeReleaseNSPRHandleInternal(aHandle);
+
+ // Delete the file if the entry was doomed or invalid and
+ // filedesc properly closed
+ if ((aHandle->mIsDoomed || aHandle->mInvalid) && aHandle->mFileExists &&
+ NS_SUCCEEDED(rv)) {
+ LOG(
+ ("CacheFileIOManager::CloseHandleInternal() - Removing file from "
+ "disk"));
+
+ rv = aHandle->mFile->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ aHandle->mFileExists = false;
+ } else {
+ LOG((" failed to remove the file [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
+ (aHandle->mInvalid || !aHandle->mFileExists)) {
+ CacheIndex::RemoveEntry(aHandle->Hash());
+ }
+
+ // Don't remove handles after shutdown
+ if (!mShuttingDown) {
+ if (aHandle->IsSpecialFile()) {
+ mSpecialHandles.RemoveElement(aHandle);
+ } else {
+ mHandles.RemoveHandle(aHandle);
+ }
+ }
+}
+
+// static
+nsresult CacheFileIOManager::Read(CacheFileHandle* aHandle, int64_t aOffset,
+ char* aBuf, int32_t aCount,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64 ", count=%d, "
+ "listener=%p]",
+ aHandle, aOffset, aCount, aCallback));
+
+ if (CacheObserver::ShuttingDown()) {
+ LOG((" no reads after shutdown"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<ReadEvent> ev =
+ new ReadEvent(aHandle, aOffset, aBuf, aCount, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+ ? CacheIOThread::READ_PRIORITY
+ : CacheIOThread::READ);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::ReadInternal(CacheFileHandle* aHandle,
+ int64_t aOffset, char* aBuf,
+ int32_t aCount) {
+ LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64
+ ", count=%d]",
+ aHandle, aOffset, aCount));
+
+ nsresult rv;
+
+ if (CacheObserver::ShuttingDown()) {
+ LOG((" no reads after shutdown"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aHandle->mFileExists) {
+ NS_WARNING("Trying to read from non-existent file");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ NS_WARNING("Trying to read from non-existent file");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
+ if (bytesRead != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::Write(CacheFileHandle* aHandle, int64_t aOffset,
+ const char* aBuf, int32_t aCount,
+ bool aValidate, bool aTruncate,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64 ", count=%d, "
+ "validate=%d, truncate=%d, listener=%p]",
+ aHandle, aOffset, aCount, aValidate, aTruncate, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
+ if (!aCallback) {
+ // When no callback is provided, CacheFileIOManager is responsible for
+ // releasing the buffer. We must release it even in case of failure.
+ free(const_cast<char*>(aBuf));
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
+ aValidate, aTruncate, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static nsresult TruncFile(PRFileDesc* aFD, int64_t aEOF) {
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ int64_t cnt = PR_Seek64(aFD, aEOF, PR_SEEK_SET);
+ if (cnt == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ MOZ_ASSERT(false, "Not implemented!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::WriteInternal(CacheFileHandle* aHandle,
+ int64_t aOffset, const char* aBuf,
+ int32_t aCount, bool aValidate,
+ bool aTruncate) {
+ LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64
+ ", count=%d, "
+ "validate=%d, truncate=%d]",
+ aHandle, aOffset, aCount, aValidate, aTruncate));
+
+ nsresult rv;
+
+ if (aHandle->mKilled) {
+ LOG((" handle already killed, nothing written"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::ShuttingDown() && (!aValidate || !aHandle->mFD)) {
+ aHandle->mKilled = true;
+ LOG((" killing the handle, nothing written"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::IsPastShutdownIOLag()) {
+ LOG((" past the shutdown I/O lag, nothing written"));
+ // Pretend the write has succeeded, otherwise upper layers will doom
+ // the file and we end up with I/O anyway.
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFileExists) {
+ rv = CreateFile(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // When this operation would increase cache size, check whether the cache size
+ // reached the hard limit and whether it would cause critical low disk space.
+ if (aHandle->mFileSize < aOffset + aCount) {
+ if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - failing because cache size "
+ "reached hard limit!"));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
+ "failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
+ LOG(
+ ("CacheFileIOManager::WriteInternal() - Low free space, refusing "
+ "to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
+ freeSpace, limit));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ }
+
+ // Write invalidates the entry by default
+ aHandle->mInvalid = true;
+
+ int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
+ if (offset == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
+
+ if (bytesWritten != -1) {
+ uint32_t oldSizeInK = aHandle->FileSizeInK();
+ int64_t writeEnd = aOffset + bytesWritten;
+
+ if (aTruncate) {
+ rv = TruncFile(aHandle->mFD, writeEnd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileSize = writeEnd;
+ } else {
+ if (aHandle->mFileSize < writeEnd) {
+ aHandle->mFileSize = writeEnd;
+ }
+ }
+
+ uint32_t newSizeInK = aHandle->FileSizeInK();
+
+ if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
+ !aHandle->IsSpecialFile()) {
+ CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr,
+ nullptr, nullptr, &newSizeInK);
+
+ if (oldSizeInK < newSizeInK) {
+ EvictIfOverLimitInternal();
+ }
+ }
+
+ CacheIndex::UpdateTotalBytesWritten(bytesWritten);
+ }
+
+ if (bytesWritten != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Write was successful and this write validates the entry (i.e. metadata)
+ if (aValidate) {
+ aHandle->mInvalid = false;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::DoomFile(CacheFileHandle* aHandle,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle,
+ aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
+ ? CacheIOThread::OPEN_PRIORITY
+ : CacheIOThread::OPEN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::DoomFileInternal(
+ CacheFileHandle* aHandle, PinningDoomRestriction aPinningDoomRestriction) {
+ LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
+ aHandle->Log();
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (aHandle->IsDoomed()) {
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (aPinningDoomRestriction > NO_RESTRICTION) {
+ switch (aHandle->mPinning) {
+ case CacheFileHandle::PinningStatus::NON_PINNED:
+ if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
+ LOG((" not dooming, it's a non-pinned handle"));
+ return NS_OK;
+ }
+ // Doom now
+ break;
+
+ case CacheFileHandle::PinningStatus::PINNED:
+ if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
+ LOG((" not dooming, it's a pinned handle"));
+ return NS_OK;
+ }
+ // Doom now
+ break;
+
+ case CacheFileHandle::PinningStatus::UNKNOWN:
+ if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
+ LOG((" doom when non-pinned set"));
+ aHandle->mDoomWhenFoundNonPinned = true;
+ } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
+ LOG((" doom when pinned set"));
+ aHandle->mDoomWhenFoundPinned = true;
+ }
+
+ LOG((" pinning status not known, deferring doom decision"));
+ return NS_OK;
+ }
+ }
+
+ if (aHandle->mFileExists) {
+ // we need to move the current file to the doomed directory
+ rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // find unused filename
+ nsCOMPtr<nsIFile> file;
+ rv = GetDoomedFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parentDir;
+ rv = file->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString leafName;
+ rv = file->GetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aHandle->mFile->MoveToNative(parentDir, leafName);
+ if (NS_ERROR_FILE_NOT_FOUND == rv) {
+ LOG((" file already removed under our hands"));
+ aHandle->mFileExists = false;
+ rv = NS_OK;
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHandle->mFile.swap(file);
+ }
+ }
+
+ if (!aHandle->IsSpecialFile()) {
+ CacheIndex::RemoveEntry(aHandle->Hash());
+ }
+
+ aHandle->mIsDoomed = true;
+
+ if (!aHandle->IsSpecialFile()) {
+ RefPtr<CacheStorageService> storageService = CacheStorageService::Self();
+ if (storageService) {
+ nsAutoCString idExtension, url;
+ nsCOMPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
+ MOZ_ASSERT(info);
+ if (info) {
+ storageService->CacheFileDoomed(info, idExtension, url);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::DoomFileByKey(const nsACString& aKey,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
+ PromiseFlatCString(aKey).get(), aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash* aHash) {
+ LOG((
+ "CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHash)));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ nsresult rv;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ // Find active handle
+ RefPtr<CacheFileHandle> handle;
+ mHandles.GetHandle(aHash, getter_AddRefs(handle));
+
+ if (handle) {
+ handle->Log();
+
+ return DoomFileInternal(handle);
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ // There is no handle for this file, delete the file if exists
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(aHash, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove old entry from the disk");
+ LOG(
+ ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+
+ CacheIndex::RemoveEntry(aHash);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle* aHandle) {
+ LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::MaybeReleaseNSPRHandleInternal(
+ CacheFileHandle* aHandle, bool aIgnoreShutdownLag) {
+ LOG(
+ ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, "
+ "ignore shutdown=%d]",
+ aHandle, aIgnoreShutdownLag));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+ if (aHandle->mFD) {
+ DebugOnly<bool> found{};
+ found = mHandlesByLastUsed.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+ }
+
+ PRFileDesc* fd = aHandle->mFD;
+ aHandle->mFD = nullptr;
+
+ // Leak invalid (w/o metadata) and doomed handles immediately after shutdown.
+ // Leak other handles when past the shutdown time maximum lag.
+ if (
+#ifndef DEBUG
+ ((aHandle->mInvalid || aHandle->mIsDoomed) &&
+ MOZ_UNLIKELY(CacheObserver::ShuttingDown())) ||
+#endif
+ MOZ_UNLIKELY(!aIgnoreShutdownLag &&
+ CacheObserver::IsPastShutdownIOLag())) {
+ // Don't bother closing this file. Return a failure code from here will
+ // cause any following IO operation on the file (mainly removal) to be
+ // bypassed, which is what we want.
+ // For mInvalid == true the entry will never be used, since it doesn't
+ // have correct metadata, thus we don't need to worry about removing it.
+ // For mIsDoomed == true the file is already in the doomed sub-dir and
+ // will be removed on next session start.
+ LOG((" past the shutdown I/O lag, leaking file handle"));
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!fd) {
+ // The filedesc has already been closed before, just let go.
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ PRStatus status = PR_Close(fd);
+ if (status != PR_SUCCESS) {
+ LOG(
+ ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() "
+ "failed to close [handle=%p, status=%u]",
+ aHandle, status));
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE"));
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::TruncateSeekSetEOF(
+ CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos,
+ CacheFileIOListener* aCallback) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, "
+ "truncatePos=%" PRId64 ", "
+ "EOFPos=%" PRId64 ", listener=%p]",
+ aHandle, aTruncatePos, aEOFPos, aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || (aCallback && aCallback->IsKilled()) || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<TruncateSeekSetEOFEvent> ev =
+ new TruncateSeekSetEOFEvent(aHandle, aTruncatePos, aEOFPos, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::GetCacheDirectory(nsIFile** result) {
+ *result = nullptr;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan || !ioMan->mCacheDirectory) {
+ return;
+ }
+
+ ioMan->mCacheDirectory->Clone(result);
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+
+// static
+void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile** result) {
+ *result = nullptr;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan || !ioMan->mCacheProfilelessDirectory) {
+ return;
+ }
+
+ ioMan->mCacheProfilelessDirectory->Clone(result);
+}
+
+#endif
+
+// static
+nsresult CacheFileIOManager::GetEntryInfo(
+ const SHA1Sum::Hash* aHash,
+ CacheStorageService::EntryInfoCallback* aCallback) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsAutoCString enhanceId;
+ nsAutoCString uriSpec;
+
+ RefPtr<CacheFileHandle> handle;
+ ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle));
+ if (handle) {
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
+
+ MOZ_ASSERT(info);
+ if (!info) {
+ return NS_OK; // ignore
+ }
+
+ RefPtr<CacheStorageService> service = CacheStorageService::Self();
+ if (!service) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Invokes OnCacheEntryInfo when an existing entry is found
+ if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
+ return NS_OK;
+ }
+
+ // When we are here, there is no existing entry and we need
+ // to synchrnously load metadata from a disk file.
+ }
+
+ // Locate the actual file
+ nsCOMPtr<nsIFile> file;
+ ioMan->GetFile(aHash, getter_AddRefs(file));
+
+ // Read metadata from the file synchronously
+ RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
+ rv = metadata->SyncReadMetadata(file);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // Now get the context + enhance id + URL from the key.
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(metadata->GetKey(), &enhanceId, &uriSpec);
+ MOZ_ASSERT(info);
+ if (!info) {
+ return NS_OK;
+ }
+
+ // Pick all data to pass to the callback.
+ int64_t dataSize = metadata->Offset();
+ int64_t altDataSize = 0;
+ uint32_t fetchCount = metadata->GetFetchCount();
+ uint32_t expirationTime = metadata->GetExpirationTime();
+ uint32_t lastModified = metadata->GetLastModified();
+
+ const char* altDataElement =
+ metadata->GetElement(CacheFileUtils::kAltDataKey);
+ if (altDataElement) {
+ int64_t altDataOffset = std::numeric_limits<int64_t>::max();
+ if (NS_SUCCEEDED(CacheFileUtils::ParseAlternativeDataInfo(
+ altDataElement, &altDataOffset, nullptr)) &&
+ altDataOffset < dataSize) {
+ dataSize = altDataOffset;
+ altDataSize = metadata->Offset() - altDataOffset;
+ } else {
+ LOG(("CacheFileIOManager::GetEntryInfo() invalid alternative data info"));
+ return NS_OK;
+ }
+ }
+
+ // Call directly on the callback.
+ aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
+ lastModified, expirationTime, metadata->Pinned(),
+ info);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::TruncateSeekSetEOFInternal(
+ CacheFileHandle* aHandle, int64_t aTruncatePos, int64_t aEOFPos) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
+ "truncatePos=%" PRId64 ", EOFPos=%" PRId64 "]",
+ aHandle, aTruncatePos, aEOFPos));
+
+ nsresult rv;
+
+ if (aHandle->mKilled) {
+ LOG((" handle already killed, file not truncated"));
+ return NS_OK;
+ }
+
+ if (CacheObserver::ShuttingDown() && !aHandle->mFD) {
+ aHandle->mKilled = true;
+ LOG((" killing the handle, file not truncated"));
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(!aHandle->IsSpecialFile());
+
+ if (!aHandle->mFileExists) {
+ rv = CreateFile(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aHandle->mFD) {
+ rv = OpenNSPRHandle(aHandle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NSPRHandleUsed(aHandle);
+ }
+
+ // Check again, OpenNSPRHandle could figure out the file was gone.
+ if (!aHandle->mFileExists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // When this operation would increase cache size, check whether the cache size
+ // reached the hard limit and whether it would cause critical low disk space.
+ if (aHandle->mFileSize < aEOFPos) {
+ if (mOverLimitEvicting && mCacheSizeOnHardLimit) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because "
+ "cache size reached hard limit!"));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
+ if (freeSpace - aEOFPos + aHandle->mFileSize < limit) {
+ LOG(
+ ("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"
+ ", refusing to write! [freeSpace=%" PRId64 "kB, limit=%ukB]",
+ freeSpace, limit));
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ }
+
+ // This operation always invalidates the entry
+ aHandle->mInvalid = true;
+
+ rv = TruncFile(aHandle->mFD, aTruncatePos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aTruncatePos != aEOFPos) {
+ rv = TruncFile(aHandle->mFD, aEOFPos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t oldSizeInK = aHandle->FileSizeInK();
+ aHandle->mFileSize = aEOFPos;
+ uint32_t newSizeInK = aHandle->FileSizeInK();
+
+ if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
+ !aHandle->IsSpecialFile()) {
+ CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
+ nullptr, &newSizeInK);
+
+ if (oldSizeInK < newSizeInK) {
+ EvictIfOverLimitInternal();
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::RenameFile(CacheFileHandle* aHandle,
+ const nsACString& aNewName,
+ CacheFileIOListener* aCallback) {
+ LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
+ aHandle, PromiseFlatCString(aNewName).get(), aCallback));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<RenameFileEvent> ev =
+ new RenameFileEvent(aHandle, aNewName, aCallback);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::RenameFileInternal(CacheFileHandle* aHandle,
+ const nsACString& aNewName) {
+ LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
+ aHandle, PromiseFlatCString(aNewName).get()));
+
+ nsresult rv;
+
+ MOZ_ASSERT(aHandle->IsSpecialFile());
+
+ if (aHandle->IsDoomed()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Doom old handle if it exists and is not doomed
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); i++) {
+ if (!mSpecialHandles[i]->IsDoomed() &&
+ mSpecialHandles[i]->Key() == aNewName) {
+ MOZ_ASSERT(aHandle != mSpecialHandles[i]);
+ rv = DoomFileInternal(mSpecialHandles[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetSpecialFile(aNewName, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = file->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ LOG(
+ ("CacheFileIOManager::RenameFileInternal() - Removing old file from "
+ "disk"));
+ rv = file->Remove(false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot remove file from the disk");
+ LOG(
+ ("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
+ ". [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ if (!aHandle->FileExists()) {
+ aHandle->mKey = aNewName;
+ return NS_OK;
+ }
+
+ rv = MaybeReleaseNSPRHandleInternal(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aHandle->mFile->MoveToNative(nullptr, aNewName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mKey = aNewName;
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictIfOverLimit() {
+ LOG(("CacheFileIOManager::EvictIfOverLimit()"));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal",
+ ioMan, &CacheFileIOManager::EvictIfOverLimitInternal);
+
+ rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::EvictIfOverLimitInternal() {
+ LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mOverLimitEvicting) {
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
+ "running."));
+ return NS_OK;
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+
+ // Do not change smart size.
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
+ uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
+
+ if (cacheUsage <= cacheLimit &&
+ (freeSpace == -1 || freeSpace >= freeSpaceLimit)) {
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free "
+ "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
+ "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
+ return NS_OK;
+ }
+
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
+ "limit. Starting overlimit eviction. [cacheSize=%ukB, limit=%ukB]",
+ cacheUsage, cacheLimit));
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal",
+ this, &CacheFileIOManager::OverLimitEvictionInternal);
+
+ rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOverLimitEvicting = true;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OverLimitEvictionInternal() {
+ LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
+ // here and set it to true again once we dispatch another event that will
+ // continue with the eviction. The reason why we do so is that we can fail
+ // early anywhere in this method and the variable will contain a correct
+ // value. Otherwise we would need to set it to false on every failing place.
+ mOverLimitEvicting = false;
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ while (true) {
+ int64_t freeSpace;
+ rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ freeSpace = -1;
+
+ // Do not change smart size.
+ LOG(
+ ("CacheFileIOManager::EvictIfOverLimitInternal() - "
+ "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ } else {
+ freeSpace >>= 10; // bytes to kilobytes
+ UpdateSmartCacheSize(freeSpace);
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t cacheLimit = CacheObserver::DiskCacheCapacity();
+ uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
+
+ if (cacheUsage > cacheLimit) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
+ "limit. [cacheSize=%ukB, limit=%ukB]",
+ cacheUsage, cacheLimit));
+
+ // We allow cache size to go over the specified limit. Eviction should
+ // keep the size within the limit in a long run, but in case the eviction
+ // is too slow, the cache could go way over the limit. To prevent this we
+ // set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit
+ // and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache
+ // additional data.
+ if ((cacheUsage - cacheLimit) > (cacheLimit / 20)) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size "
+ "reached hard limit."));
+ mCacheSizeOnHardLimit = true;
+ } else {
+ mCacheSizeOnHardLimit = false;
+ }
+ } else if (freeSpace != -1 && freeSpace < freeSpaceLimit) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
+ "limit. [freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ freeSpace, freeSpaceLimit));
+ } else {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
+ "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
+ "freeSpace=%" PRId64 "kB, freeSpaceLimit=%ukB]",
+ cacheUsage, cacheLimit, freeSpace, freeSpaceLimit));
+
+ mCacheSizeOnHardLimit = false;
+ return NS_OK;
+ }
+
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
+ "for higher level events."));
+ mOverLimitEvicting = true;
+ return NS_OK;
+ }
+
+ SHA1Sum::Hash hash;
+ uint32_t cnt;
+ static uint32_t consecutiveFailures = 0;
+ rv = CacheIndex::GetEntryForEviction(false, &hash, &cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DoomFileByKeyInternal(&hash);
+ if (NS_SUCCEEDED(rv)) {
+ consecutiveFailures = 0;
+ } else if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - "
+ "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ // TODO index is outdated, start update
+
+ // Make sure index won't return the same entry again
+ CacheIndex::RemoveEntry(&hash);
+ consecutiveFailures = 0;
+ } else {
+ // This shouldn't normally happen, but the eviction must not fail
+ // completely if we ever encounter this problem.
+ NS_WARNING(
+ "CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
+ "failure of DoomFileByKeyInternal()");
+
+ LOG(
+ ("CacheFileIOManager::OverLimitEvictionInternal() - "
+ "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+
+ // Normally, CacheIndex::UpdateEntry() is called only to update newly
+ // created/opened entries which are always fresh and UpdateEntry() expects
+ // and checks this flag. The way we use UpdateEntry() here is a kind of
+ // hack and we must make sure the flag is set by calling
+ // EnsureEntryExists().
+ rv = CacheIndex::EnsureEntryExists(&hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the entry at the end of both lists to make sure we won't end up
+ // failing on one entry forever.
+ uint32_t frecency = 0;
+ rv = CacheIndex::UpdateEntry(&hash, &frecency, nullptr, nullptr, nullptr,
+ nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ consecutiveFailures++;
+ if (consecutiveFailures >= cnt) {
+ // This doesn't necessarily mean that we've tried to doom every entry
+ // but we've reached a sane number of tries. It is likely that another
+ // eviction will start soon. And as said earlier, this normally doesn't
+ // happen at all.
+ return NS_OK;
+ }
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictAll() {
+ LOG(("CacheFileIOManager::EvictAll()"));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal", ioMan,
+ &CacheFileIOManager::EvictAllInternal);
+
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+class EvictionNotifierRunnable : public Runnable {
+ public:
+ EvictionNotifierRunnable() : Runnable("EvictionNotifierRunnable") {}
+ NS_DECL_NSIRUNNABLE
+};
+
+NS_IMETHODIMP
+EvictionNotifierRunnable::Run() {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult CacheFileIOManager::EvictAllInternal() {
+ LOG(("CacheFileIOManager::EvictAllInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+
+ if (!mCacheDirectory) {
+ // This is a kind of hack. Somebody called EvictAll() without a profile.
+ // This happens in xpcshell tests that use cache without profile. We need
+ // to notify observers in this case since the tests are waiting for it.
+ NS_DispatchToMainThread(r);
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // Doom all active handles
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetActiveHandles(&handles);
+
+ for (uint32_t i = 0; i < handles.Length(); ++i) {
+ rv = DoomFileInternal(handles[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
+ "[handle=%p]",
+ handles[i].get()));
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Trash current entries directory
+ rv = TrashDirectory(file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Files are now inaccessible in entries directory, notify observers.
+ NS_DispatchToMainThread(r);
+
+ // Create a new empty entries directory
+ rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CacheIndex::RemoveAll();
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::EvictByContext(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, const nsAString& aBaseDomain) {
+ LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
+ aLoadContextInfo));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev =
+ NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool, nsString, nsString>(
+ "net::CacheFileIOManager::EvictByContextInternal", ioMan,
+ &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo,
+ aPinned, aOrigin, aBaseDomain);
+
+ rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::EvictByContextInternal(
+ nsILoadContextInfo* aLoadContextInfo, bool aPinned,
+ const nsAString& aOrigin, const nsAString& aBaseDomain) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
+ "pinned=%d]",
+ aLoadContextInfo, aPinned));
+
+ nsresult rv;
+
+ if (aLoadContextInfo) {
+ nsAutoCString suffix;
+ aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
+ LOG((" anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(),
+ suffix.get()));
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
+ if (aLoadContextInfo->IsPrivate()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (!mCacheDirectory) {
+ // This is a kind of hack. Somebody called EvictAll() without a profile.
+ // This happens in xpcshell tests that use cache without profile. We need
+ // to notify observers in this case since the tests are waiting for it.
+ // Also notify for aPinned == true, those are interested as well.
+ if (!aLoadContextInfo) {
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+ NS_DispatchToMainThread(r);
+ }
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ NS_ConvertUTF16toUTF8 origin(aOrigin);
+ NS_ConvertUTF16toUTF8 baseDomain(aBaseDomain);
+
+ // Doom all active handles that matches the load context
+ nsTArray<RefPtr<CacheFileHandle>> handles;
+ mHandles.GetActiveHandles(&handles);
+
+ for (uint32_t i = 0; i < handles.Length(); ++i) {
+ CacheFileHandle* handle = handles[i];
+
+ const bool shouldRemove = [&] {
+ nsAutoCString uriSpec;
+ RefPtr<nsILoadContextInfo> info =
+ CacheFileUtils::ParseKey(handle->Key(), nullptr, &uriSpec);
+ if (!info) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() - Cannot parse key "
+ "in "
+ "handle! [handle=%p, key=%s]",
+ handle, handle->Key().get()));
+ MOZ_CRASH("Unexpected error!");
+ }
+
+ // Filter by base domain.
+ if (!aBaseDomain.IsEmpty()) {
+ if (StoragePrincipalHelper::PartitionKeyHasBaseDomain(
+ info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
+ return true;
+ }
+
+ // If the partitionKey does not match, check the entry URI next.
+
+ // Get host portion of uriSpec.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ // Some entries may not have valid hosts. We can skip them.
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return false;
+ }
+
+ // Clear entry if the host belongs to the given base domain.
+ bool hasRootDomain = false;
+ rv = HasRootDomain(host, baseDomain, &hasRootDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return hasRootDomain;
+ }
+
+ // Filter by LoadContextInfo.
+ if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) {
+ return false;
+ }
+
+ // Filter by origin.
+ if (!origin.IsEmpty()) {
+ RefPtr<MozURL> url;
+ rv = MozURL::Init(getter_AddRefs(url), uriSpec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString urlOrigin;
+ url->Origin(urlOrigin);
+
+ if (!urlOrigin.Equals(origin)) {
+ return false;
+ }
+ }
+
+ return true;
+ }();
+
+ if (!shouldRemove) {
+ continue;
+ }
+
+ // handle will be doomed only when pinning status is known and equal or
+ // doom decision will be deferred until pinning status is determined.
+ rv = DoomFileInternal(handle,
+ aPinned ? CacheFileIOManager::DOOM_WHEN_PINNED
+ : CacheFileIOManager::DOOM_WHEN_NON_PINNED);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
+ " [handle=%p]",
+ handle));
+ }
+ }
+
+ if (!aLoadContextInfo) {
+ RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+ NS_DispatchToMainThread(r);
+ }
+
+ if (!mContextEvictor) {
+ mContextEvictor = new CacheFileContextEvictor();
+ mContextEvictor->Init(mCacheDirectory);
+ }
+
+ mContextEvictor->AddContext(aLoadContextInfo, aPinned, aOrigin);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::CacheIndexStateChanged() {
+ LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
+
+ nsresult rv;
+
+ // CacheFileIOManager lives longer than CacheIndex so gInstance must be
+ // non-null here.
+ MOZ_ASSERT(gInstance);
+
+ // We have to re-distatch even if we are on IO thread to prevent reentering
+ // the lock in CacheIndex
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
+ "net::CacheFileIOManager::CacheIndexStateChangedInternal",
+ gInstance.get(), &CacheFileIOManager::CacheIndexStateChangedInternal);
+
+ nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::CacheIndexStateChangedInternal() {
+ if (mShuttingDown) {
+ // ignore notification during shutdown
+ return;
+ }
+
+ if (!mContextEvictor) {
+ return;
+ }
+
+ mContextEvictor->CacheIndexStateChanged();
+}
+
+nsresult CacheFileIOManager::TrashDirectory(nsIFile* aFile) {
+ LOG(("CacheFileIOManager::TrashDirectory() [file=%s]",
+ aFile->HumanReadablePath().get()));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+ MOZ_ASSERT(mCacheDirectory);
+
+ // When the directory is empty, it is cheaper to remove it directly instead of
+ // using the trash mechanism.
+ bool isEmpty;
+ rv = IsEmptyDirectory(aFile, &isEmpty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isEmpty) {
+ rv = aFile->Remove(false);
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Directory removed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<nsIFile> dirCheck;
+ rv = aFile->GetParent(getter_AddRefs(dirCheck));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool equals = false;
+ rv = dirCheck->Equals(mCacheDirectory, &equals);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(equals);
+#endif
+
+ nsCOMPtr<nsIFile> dir, trash;
+ nsAutoCString leaf;
+
+ rv = aFile->Clone(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aFile->Clone(getter_AddRefs(trash));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const int32_t kMaxTries = 16;
+ srand(static_cast<unsigned>(PR_Now()));
+ for (int32_t triesCount = 0;; ++triesCount) {
+ leaf = TRASH_DIR;
+ leaf.AppendInt(rand());
+ rv = trash->SetNativeLeafName(leaf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Trash directory already "
+ "exists [leaf=%s]",
+ leaf.get()));
+
+ if (triesCount == kMaxTries) {
+ LOG(
+ ("CacheFileIOManager::TrashDirectory() - Could not find unused trash "
+ "directory in %d tries.",
+ kMaxTries));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
+ leaf.get()));
+
+ rv = dir->MoveToNative(nullptr, leaf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartRemovingTrash();
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::OnTrashTimer(nsITimer* aTimer, void* aClosure) {
+ LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
+ aClosure));
+
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (!ioMan) {
+ return;
+ }
+
+ ioMan->mTrashTimer = nullptr;
+ ioMan->StartRemovingTrash();
+}
+
+nsresult CacheFileIOManager::StartRemovingTrash() {
+ LOG(("CacheFileIOManager::StartRemovingTrash()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (mTrashTimer) {
+ LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
+ return NS_OK;
+ }
+
+ if (mRemovingTrashDirs) {
+ LOG(
+ ("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
+ "progress."));
+ return NS_OK;
+ }
+
+ uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
+ if (elapsed < kRemoveTrashStartDelay) {
+ nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+ MOZ_ASSERT(ioTarget);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTrashTimer), CacheFileIOManager::OnTrashTimer, nullptr,
+ kRemoveTrashStartDelay - elapsed, nsITimer::TYPE_ONE_SHOT,
+ "net::CacheFileIOManager::StartRemovingTrash", ioTarget);
+ }
+
+ nsCOMPtr<nsIRunnable> ev;
+ ev = NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal", this,
+ &CacheFileIOManager::RemoveTrashInternal);
+
+ rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRemovingTrashDirs = true;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::RemoveTrashInternal() {
+ LOG(("CacheFileIOManager::RemoveTrashInternal()"));
+
+ nsresult rv;
+
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ if (mShuttingDown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ CacheIOThread::Cancelable cancelable(true);
+
+ MOZ_ASSERT(!mTrashTimer);
+ MOZ_ASSERT(mRemovingTrashDirs);
+
+ if (!mTreeCreated) {
+ rv = CreateCacheTree();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
+ // here and set it again once we dispatch a continuation event. By doing so,
+ // we don't have to drop the flag on any possible early return.
+ mRemovingTrashDirs = false;
+
+ while (true) {
+ if (CacheIOThread::YieldAndRerun()) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
+ "higher level events."));
+ mRemovingTrashDirs = true;
+ return NS_OK;
+ }
+
+ // Find some trash directory
+ if (!mTrashDir) {
+ MOZ_ASSERT(!mTrashDirEnumerator);
+
+ rv = FindTrashDirToRemove();
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
+ "found."));
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(mTrashDirEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ continue; // check elapsed time
+ }
+
+ // We null out mTrashDirEnumerator once we remove all files in the
+ // directory, so remove the trash directory if we don't have enumerator.
+ if (!mTrashDirEnumerator) {
+ rv = mTrashDir->Remove(false);
+ if (NS_FAILED(rv)) {
+ // There is no reason why removing an empty directory should fail, but
+ // if it does, we should continue and try to remove all other trash
+ // directories.
+ nsAutoCString leafName;
+ mTrashDir->GetNativeLeafName(leafName);
+ mFailedTrashDirs.AppendElement(leafName);
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
+ "trashdir. [name=%s]",
+ leafName.get()));
+ }
+
+ mTrashDir = nullptr;
+ continue; // check elapsed time
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));
+ if (!file) {
+ mTrashDirEnumerator->Close();
+ mTrashDirEnumerator = nullptr;
+ continue; // check elapsed time
+ }
+ bool isDir = false;
+ file->IsDirectory(&isDir);
+ if (isDir) {
+ NS_WARNING(
+ "Found a directory in a trash directory! It will be removed "
+ "recursively, but this can block IO thread for a while!");
+ if (LOG_ENABLED()) {
+ LOG(
+ ("CacheFileIOManager::RemoveTrashInternal() - Found a directory in "
+ "a trash "
+ "directory! It will be removed recursively, but this can block IO "
+ "thread for a while! [file=%s]",
+ file->HumanReadablePath().get()));
+ }
+ }
+ file->Remove(isDir);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::FindTrashDirToRemove() {
+ LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
+
+ nsresult rv;
+
+ if (!mCacheDirectory) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // We call this method on the main thread during shutdown when user wants to
+ // remove all cache files.
+ MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown);
+
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
+ bool isDir = false;
+ file->IsDirectory(&isDir);
+ if (!isDir) {
+ continue;
+ }
+
+ nsAutoCString leafName;
+ rv = file->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (leafName.Length() < strlen(TRASH_DIR)) {
+ continue;
+ }
+
+ if (!StringBeginsWith(leafName, nsLiteralCString(TRASH_DIR))) {
+ continue;
+ }
+
+ if (mFailedTrashDirs.Contains(leafName)) {
+ continue;
+ }
+
+ LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
+ leafName.get()));
+
+ mTrashDir = file;
+ return NS_OK;
+ }
+
+ // When we're here we've tried to delete all trash directories. Clear
+ // mFailedTrashDirs so we will try to delete them again when we start removing
+ // trash directories next time.
+ mFailedTrashDirs.Clear();
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+nsresult CacheFileIOManager::InitIndexEntry(CacheFileHandle* aHandle,
+ OriginAttrsHash aOriginAttrsHash,
+ bool aAnonymous, bool aPinning) {
+ LOG(
+ ("CacheFileIOManager::InitIndexEntry() [handle=%p, "
+ "originAttrsHash=%" PRIx64 ", "
+ "anonymous=%d, pinning=%d]",
+ aHandle, aOriginAttrsHash, aAnonymous, aPinning));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<InitIndexEntryEvent> ev =
+ new InitIndexEntryEvent(aHandle, aOriginAttrsHash, aAnonymous, aPinning);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// static
+nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle* aHandle,
+ const uint32_t* aFrecency,
+ const bool* aHasAltData,
+ const uint16_t* aOnStartTime,
+ const uint16_t* aOnStopTime,
+ const uint8_t* aContentType) {
+ LOG(
+ ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
+ "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]",
+ aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
+ aHasAltData ? (*aHasAltData ? "true" : "false") : "",
+ aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
+ aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
+ aContentType ? nsPrintfCString("%u", *aContentType).get() : ""));
+
+ nsresult rv;
+ RefPtr<CacheFileIOManager> ioMan = gInstance;
+
+ if (aHandle->IsClosed() || !ioMan) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aHandle->IsSpecialFile()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<UpdateIndexEntryEvent> ev = new UpdateIndexEntryEvent(
+ aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime, aContentType);
+ rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
+ ? CacheIOThread::WRITE_PRIORITY
+ : CacheIOThread::WRITE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CreateFile(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(!aHandle->mFD);
+ MOZ_ASSERT(aHandle->mFile);
+
+ nsresult rv;
+
+ if (aHandle->IsDoomed()) {
+ nsCOMPtr<nsIFile> file;
+
+ rv = GetDoomedFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFile.swap(file);
+ } else {
+ bool exists;
+ if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) {
+ NS_WARNING("Found a file that should not exist!");
+ }
+ }
+
+ rv = OpenNSPRHandle(aHandle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileSize = 0;
+ return NS_OK;
+}
+
+// static
+void CacheFileIOManager::HashToStr(const SHA1Sum::Hash* aHash,
+ nsACString& _retval) {
+ _retval.Truncate();
+ const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ for (uint32_t i = 0; i < sizeof(SHA1Sum::Hash); i++) {
+ _retval.Append(hexChars[(*aHash)[i] >> 4]);
+ _retval.Append(hexChars[(*aHash)[i] & 0xF]);
+ }
+}
+
+// static
+nsresult CacheFileIOManager::StrToHash(const nsACString& aHash,
+ SHA1Sum::Hash* _retval) {
+ if (aHash.Length() != 2 * sizeof(SHA1Sum::Hash)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (uint32_t i = 0; i < aHash.Length(); i++) {
+ uint8_t value;
+
+ if (aHash[i] >= '0' && aHash[i] <= '9') {
+ value = aHash[i] - '0';
+ } else if (aHash[i] >= 'A' && aHash[i] <= 'F') {
+ value = aHash[i] - 'A' + 10;
+ } else if (aHash[i] >= 'a' && aHash[i] <= 'f') {
+ value = aHash[i] - 'a' + 10;
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (i % 2 == 0) {
+ (reinterpret_cast<uint8_t*>(_retval))[i / 2] = value << 4;
+ } else {
+ (reinterpret_cast<uint8_t*>(_retval))[i / 2] += value;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetFile(const SHA1Sum::Hash* aHash,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsLiteralCString(ENTRIES_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString leafName;
+ HashToStr(aHash, leafName);
+
+ rv = file->AppendNative(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetSpecialFile(const nsACString& aKey,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(aKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::GetDoomedFile(nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative(nsLiteralCString(DOOMED_DIR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->AppendNative("dummyleaf"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const int32_t kMaxTries = 64;
+ srand(static_cast<unsigned>(PR_Now()));
+ nsAutoCString leafName;
+ for (int32_t triesCount = 0;; ++triesCount) {
+ leafName.AppendInt(rand());
+ rv = file->SetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ if (triesCount == kMaxTries) {
+ LOG(
+ ("CacheFileIOManager::GetDoomedFile() - Could not find unused file "
+ "name in %d tries.",
+ kMaxTries));
+ return NS_ERROR_FAILURE;
+ }
+
+ leafName.Truncate();
+ }
+
+ file.swap(*_retval);
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::IsEmptyDirectory(nsIFile* aFile, bool* _retval) {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDirectoryEnumerator> enumerator;
+ rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMoreElements = false;
+ rv = enumerator->HasMoreElements(&hasMoreElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = !hasMoreElements;
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CheckAndCreateDir(nsIFile* aFile, const char* aDir,
+ bool aEnsureEmptyDir) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ if (!aDir) {
+ file = aFile;
+ } else {
+ nsAutoCString dir(aDir);
+ rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = file->AppendNative(dir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool exists = false;
+ rv = file->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) {
+ // Try to remove the file
+ rv = file->Remove(false);
+ if (NS_SUCCEEDED(rv)) {
+ exists = false;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) {
+ bool isEmpty;
+ rv = IsEmptyDirectory(file, &isEmpty);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isEmpty) {
+ // Don't check the result, if this fails, it's OK. We do this
+ // only for the doomed directory that doesn't need to be deleted
+ // for the cost of completely disabling the whole browser.
+ TrashDirectory(file);
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot create directory");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::CreateCacheTree() {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+ MOZ_ASSERT(!mTreeCreated);
+
+ if (!mCacheDirectory || mTreeCreationFailed) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ nsresult rv;
+
+ // Set the flag here and clear it again below when the tree is created
+ // successfully.
+ mTreeCreationFailed = true;
+
+ // ensure parent directory exists
+ nsCOMPtr<nsIFile> parentDir;
+ rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CheckAndCreateDir(parentDir, nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure cache directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure entries directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ensure doomed directory exists
+ rv = CheckAndCreateDir(mCacheDirectory, DOOMED_DIR, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTreeCreated = true;
+ mTreeCreationFailed = false;
+
+ if (!mContextEvictor) {
+ RefPtr<CacheFileContextEvictor> contextEvictor;
+ contextEvictor = new CacheFileContextEvictor();
+
+ // Init() method will try to load unfinished contexts from the disk. Store
+ // the evictor as a member only when there is some unfinished job.
+ contextEvictor->Init(mCacheDirectory);
+ if (contextEvictor->ContextsCount()) {
+ contextEvictor.swap(mContextEvictor);
+ }
+ }
+
+ StartRemovingTrash();
+
+ return NS_OK;
+}
+
+nsresult CacheFileIOManager::OpenNSPRHandle(CacheFileHandle* aHandle,
+ bool aCreate) {
+ LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle));
+
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(!aHandle->mFD);
+ MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
+ MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
+ MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
+ (!aCreate && aHandle->mFileExists));
+
+ nsresult rv;
+
+ if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
+ // close handle that hasn't been used for the longest time
+ rv = MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed[0], true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCreate) {
+ rv = aHandle->mFile->OpenNSPRFileDesc(
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS || // error from nsLocalFileWin
+ rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { // error from nsLocalFileUnix
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we"
+ " might reached a limit on FAT32. Will evict a single entry and try "
+ "again. [hash=%08x%08x%08x%08x%08x]",
+ LOGSHA1(aHandle->Hash())));
+
+ SHA1Sum::Hash hash;
+ uint32_t cnt;
+
+ rv = CacheIndex::GetEntryForEviction(true, &hash, &cnt);
+ if (NS_SUCCEEDED(rv)) {
+ rv = DoomFileByKeyInternal(&hash);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ rv = aHandle->mFile->OpenNSPRFileDesc(
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"
+ " with hash %08x%08x%08x%08x%08x. %s to create the new file.",
+ LOGSHA1(&hash), NS_SUCCEEDED(rv) ? "Succeeded" : "Failed"));
+ } else {
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"
+ " entry."));
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFileIOManager::OpenNSPRHandle() Create failed with "
+ "0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHandle->mFileExists = true;
+ } else {
+ rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
+ if (NS_ERROR_FILE_NOT_FOUND == rv) {
+ LOG((" file doesn't exists"));
+ aHandle->mFileExists = false;
+ return DoomFileInternal(aHandle);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mHandlesByLastUsed.AppendElement(aHandle);
+
+ LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle));
+
+ return NS_OK;
+}
+
+void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle* aHandle) {
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+ MOZ_ASSERT(aHandle->mFD);
+
+ DebugOnly<bool> found{};
+ found = mHandlesByLastUsed.RemoveElement(aHandle);
+ MOZ_ASSERT(found);
+
+ mHandlesByLastUsed.AppendElement(aHandle);
+}
+
+nsresult CacheFileIOManager::SyncRemoveDir(nsIFile* aFile, const char* aDir) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+
+ if (!aFile) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aDir) {
+ file = aFile;
+ } else {
+ rv = aFile->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->AppendNative(nsDependentCString(aDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
+ file->HumanReadablePath().get()));
+ }
+
+ rv = file->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveDir() - Removing failed! "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+
+ return rv;
+}
+
+nsresult CacheFileIOManager::DispatchPurgeTask(
+ const nsCString& aCacheDirName, const nsCString& aSecondsToWait,
+ const nsCString& aPurgeExtension) {
+#if !defined(MOZ_BACKGROUNDTASKS)
+ // If background tasks are disabled, then we should just bail out early.
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsCOMPtr<nsIFile> cacheDir;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(cacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> profileDir;
+ rv = cacheDir->GetParent(getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> lf;
+ rv = XRE_GetBinaryPath(getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+# if !defined(XP_WIN)
+ rv = profileDir->GetNativePath(path);
+# else
+ rv = profileDir->GetNativeTarget(path);
+# endif
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIBackgroundTasksRunner> runner =
+ do_GetService("@mozilla.org/backgroundtasksrunner;1");
+
+ return runner->RemoveDirectoryInDetachedProcess(
+ path, aCacheDirName, aSecondsToWait, aPurgeExtension, "HttpCache"_ns);
+#endif
+}
+
+void CacheFileIOManager::SyncRemoveAllCacheFiles() {
+ LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
+ nsresult rv;
+
+ // If we are already running in a background task, we
+ // don't want to spawn yet another one at shutdown.
+ if (inBackgroundTask()) {
+ return;
+ }
+
+ if (StaticPrefs::network_cache_shutdown_purge_in_background_task()) {
+ rv = [&]() -> nsresult {
+ nsresult rv;
+
+ // If there is no cache directory, there's nothing to remove.
+ if (!mCacheDirectory) {
+ return NS_OK;
+ }
+
+ nsAutoCString leafName;
+ rv = mCacheDirectory->GetNativeLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ leafName.Append('.');
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
+ leafName.Append(nsPrintfCString(
+ "%04d-%02d-%02d-%02d-%02d-%02d", now.tm_year, now.tm_month + 1,
+ now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec));
+ leafName.Append(kPurgeExtension);
+
+ nsAutoCString secondsToWait;
+ secondsToWait.AppendInt(
+ StaticPrefs::network_cache_shutdown_purge_folder_wait_seconds());
+
+ rv = DispatchPurgeTask(leafName, secondsToWait, kPurgeExtension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mCacheDirectory->RenameToNative(nullptr, leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }();
+
+ // Dispatching to the background task has succeeded. This is finished.
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ SyncRemoveDir(mCacheDirectory, ENTRIES_DIR);
+ SyncRemoveDir(mCacheDirectory, DOOMED_DIR);
+
+ // Clear any intermediate state of trash dir enumeration.
+ mFailedTrashDirs.Clear();
+ mTrashDir = nullptr;
+
+ while (true) {
+ // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
+ rv = FindTrashDirToRemove();
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
+ "found."));
+ break;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
+ "FindTrashDirToRemove() returned an unexpected error. "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ rv = SyncRemoveDir(mTrashDir, nullptr);
+ if (NS_FAILED(rv)) {
+ nsAutoCString leafName;
+ mTrashDir->GetNativeLeafName(leafName);
+ mFailedTrashDirs.AppendElement(leafName);
+ }
+ }
+}
+
+// Returns default ("smart") size (in KB) of cache, given available disk space
+// (also in KB)
+static uint32_t SmartCacheSize(const int64_t availKB) {
+ uint32_t maxSize;
+
+ if (CacheObserver::ClearCacheOnShutdown()) {
+ maxSize = kMaxClearOnShutdownCacheSizeKB;
+ } else {
+ maxSize = kMaxCacheSizeKB;
+ }
+
+ if (availKB > 25 * 1024 * 1024) {
+ return maxSize; // skip computing if we're over 25 GB
+ }
+
+ // Grow/shrink in 10 MB units, deliberately, so that in the common case we
+ // don't shrink cache and evict items every time we startup (it's important
+ // that we don't slow down startup benchmarks).
+ uint32_t sz10MBs = 0;
+ uint32_t avail10MBs = availKB / (1024 * 10);
+
+ // 2.5% of space above 7GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700) * .025);
+ avail10MBs = 700;
+ }
+ // 7.5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50) * .075);
+ avail10MBs = 50;
+ }
+
+#ifdef ANDROID
+ // On Android, smaller/older devices may have very little storage and
+ // device owners may be sensitive to storage footprint: Use a smaller
+ // percentage of available space and a smaller minimum.
+
+ // 16% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .16));
+#else
+ // 30% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .3));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+nsresult CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace) {
+ MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+ nsresult rv;
+
+ if (!CacheObserver::SmartCacheSizeEnabled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
+ static const TimeDuration kUpdateLimit =
+ TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
+ if (!mLastSmartSizeTime.IsNull() &&
+ (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
+ return NS_OK;
+ }
+
+ // Do not compute smart size when cache size is not reliable.
+ bool isUpToDate = false;
+ CacheIndex::IsUpToDate(&isUpToDate);
+ if (!isUpToDate) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ uint32_t cacheUsage;
+ rv = CacheIndex::GetCacheSize(&cacheUsage);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ mLastSmartSizeTime = TimeStamp::NowLoRes();
+
+ uint32_t smartSize = SmartCacheSize(aFreeSpace + cacheUsage);
+
+ if (smartSize == CacheObserver::DiskCacheCapacity()) {
+ // Smart size has not changed.
+ return NS_OK;
+ }
+
+ CacheObserver::SetSmartDiskCacheCapacity(smartSize);
+
+ return NS_OK;
+}
+
+// Memory reporting
+
+namespace {
+
+// A helper class that dispatches and waits for an event that gets result of
+// CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
+// to safely get handles memory report.
+// We must do this, since the handle list is only accessed and managed w/o
+// locking on the I/O thread. That is by design.
+class SizeOfHandlesRunnable : public Runnable {
+ public:
+ SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,
+ CacheFileHandles const& handles,
+ nsTArray<CacheFileHandle*> const& specialHandles)
+ : Runnable("net::SizeOfHandlesRunnable"),
+ mMonitor("SizeOfHandlesRunnable.mMonitor"),
+ mMonitorNotified(false),
+ mMallocSizeOf(mallocSizeOf),
+ mHandles(handles),
+ mSpecialHandles(specialHandles),
+ mSize(0) {}
+
+ size_t Get(CacheIOThread* thread) {
+ nsCOMPtr<nsIEventTarget> target = thread->Target();
+ if (!target) {
+ NS_ERROR("If we have the I/O thread we also must have the I/O target");
+ return 0;
+ }
+
+ mozilla::MonitorAutoLock mon(mMonitor);
+ mMonitorNotified = false;
+ nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
+ return 0;
+ }
+
+ while (!mMonitorNotified) {
+ mon.Wait();
+ }
+ return mSize;
+ }
+
+ NS_IMETHOD Run() override {
+ mozilla::MonitorAutoLock mon(mMonitor);
+ // Excluding this since the object itself is a member of CacheFileIOManager
+ // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
+ mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf);
+ for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) {
+ mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);
+ }
+
+ mMonitorNotified = true;
+ mon.Notify();
+ return NS_OK;
+ }
+
+ private:
+ mozilla::Monitor mMonitor MOZ_UNANNOTATED;
+ bool mMonitorNotified;
+ mozilla::MallocSizeOf mMallocSizeOf;
+ CacheFileHandles const& mHandles;
+ nsTArray<CacheFileHandle*> const& mSpecialHandles;
+ size_t mSize;
+};
+
+} // namespace
+
+size_t CacheFileIOManager::SizeOfExcludingThisInternal(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ nsCOMPtr<nsISizeOf> sizeOf;
+
+ if (mIOThread) {
+ n += mIOThread->SizeOfIncludingThis(mallocSizeOf);
+
+ // mHandles and mSpecialHandles must be accessed only on the I/O thread,
+ // must sync dispatch.
+ RefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable =
+ new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles);
+ n += sizeOfHandlesRunnable->Get(mIOThread);
+ }
+
+ // mHandlesByLastUsed just refers handles reported by mHandles.
+
+ sizeOf = do_QueryInterface(mCacheDirectory);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mMetadataWritesTimer);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mTrashTimer);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ sizeOf = do_QueryInterface(mTrashDir);
+ if (sizeOf) n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
+
+ for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
+ n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ }
+
+ return n;
+}
+
+// static
+size_t CacheFileIOManager::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) {
+ if (!gInstance) return 0;
+
+ return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
+}
+
+// static
+size_t CacheFileIOManager::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net