summaryrefslogtreecommitdiffstats
path: root/netwerk/cache2/CacheFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/cache2/CacheFile.cpp2589
1 files changed, 2589 insertions, 0 deletions
diff --git a/netwerk/cache2/CacheFile.cpp b/netwerk/cache2/CacheFile.cpp
new file mode 100644
index 0000000000..02a9f3bce7
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,2589 @@
+/* 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 "CacheFile.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "CacheFileChunk.h"
+#include "CacheFileInputStream.h"
+#include "CacheFileOutputStream.h"
+#include "CacheFileUtils.h"
+#include "CacheIndex.h"
+#include "CacheLog.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICacheEntry.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+// When CACHE_CHUNKS is defined we always cache unused chunks in mCacheChunks.
+// When it is not defined, we always release the chunks ASAP, i.e. we cache
+// unused chunks only when:
+// - CacheFile is memory-only
+// - CacheFile is still waiting for the handle
+// - the chunk is preloaded
+
+// #define CACHE_CHUNKS
+
+namespace mozilla::net {
+
+using CacheFileUtils::CacheFileLock;
+
+class NotifyCacheFileListenerEvent : public Runnable {
+ public:
+ NotifyCacheFileListenerEvent(CacheFileListener* aCallback, nsresult aResult,
+ bool aIsNew)
+ : Runnable("net::NotifyCacheFileListenerEvent"),
+ mCallback(aCallback),
+ mRV(aResult),
+ mIsNew(aIsNew) {
+ LOG(
+ ("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() "
+ "[this=%p]",
+ this));
+ }
+
+ protected:
+ ~NotifyCacheFileListenerEvent() {
+ LOG(
+ ("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() "
+ "[this=%p]",
+ this));
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this));
+
+ mCallback->OnFileReady(mRV, mIsNew);
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileListener> mCallback;
+ nsresult mRV;
+ bool mIsNew;
+};
+
+class NotifyChunkListenerEvent : public Runnable {
+ public:
+ NotifyChunkListenerEvent(CacheFileChunkListener* aCallback, nsresult aResult,
+ uint32_t aChunkIdx, CacheFileChunk* aChunk)
+ : Runnable("net::NotifyChunkListenerEvent"),
+ mCallback(aCallback),
+ mRV(aResult),
+ mChunkIdx(aChunkIdx),
+ mChunk(aChunk) {
+ LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]",
+ this));
+ }
+
+ protected:
+ ~NotifyChunkListenerEvent() {
+ LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]",
+ this));
+ }
+
+ public:
+ NS_IMETHOD Run() override {
+ LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this));
+
+ mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
+ return NS_OK;
+ }
+
+ protected:
+ nsCOMPtr<CacheFileChunkListener> mCallback;
+ nsresult mRV;
+ uint32_t mChunkIdx;
+ RefPtr<CacheFileChunk> mChunk;
+};
+
+class DoomFileHelper : public CacheFileIOListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit DoomFileHelper(CacheFileListener* aListener)
+ : mListener(aListener) {}
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override {
+ if (mListener) mListener->OnFileDoomed(aResult);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
+ nsresult aResult) override {
+ MOZ_CRASH("DoomFileHelper::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ private:
+ virtual ~DoomFileHelper() = default;
+
+ nsCOMPtr<CacheFileListener> mListener;
+};
+
+NS_IMPL_ISUPPORTS(DoomFileHelper, CacheFileIOListener)
+
+NS_IMPL_ADDREF(CacheFile)
+NS_IMPL_RELEASE(CacheFile)
+NS_INTERFACE_MAP_BEGIN(CacheFile)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
+ NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,
+ mozilla::net::CacheFileChunkListener)
+NS_INTERFACE_MAP_END
+
+CacheFile::CacheFile() : mLock(new CacheFileLock()) {
+ LOG(("CacheFile::CacheFile() [this=%p]", this));
+}
+
+CacheFile::~CacheFile() {
+ LOG(("CacheFile::~CacheFile() [this=%p]", this));
+
+ MutexAutoLock lock(mLock->Lock());
+ if (!mMemoryOnly && mReady && !mKill) {
+ // mReady flag indicates we have metadata plus in a valid state.
+ WriteMetadataIfNeededLocked(true);
+ }
+}
+
+nsresult CacheFile::Init(const nsACString& aKey, bool aCreateNew,
+ bool aMemoryOnly, bool aSkipSizeCheck, bool aPriority,
+ bool aPinned, CacheFileListener* aCallback)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ MOZ_ASSERT(!mListener);
+ MOZ_ASSERT(!mHandle);
+
+ MOZ_ASSERT(!(aMemoryOnly && aPinned));
+
+ nsresult rv;
+
+ mKey = aKey;
+ mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
+ mSkipSizeCheck = aSkipSizeCheck;
+ mPriority = aPriority;
+ mPinned = aPinned;
+
+ // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
+ // such amount of data that was announced by Available().
+ // CacheFileInputStream::Available() uses also preloaded chunks to compute
+ // number of available bytes in the input stream, so we have to make sure the
+ // preloadChunkCount won't change during CacheFile's lifetime since otherwise
+ // we could potentially release some cached chunks that was used to calculate
+ // available bytes but would not be available later during call to
+ // CacheFileInputStream::Read().
+ mPreloadChunkCount = CacheObserver::PreloadChunkCount();
+
+ LOG(
+ ("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
+ "priority=%d, listener=%p]",
+ this, mKey.get(), aCreateNew, aMemoryOnly, aPriority, aCallback));
+
+ if (mMemoryOnly) {
+ MOZ_ASSERT(!aCallback);
+
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, false, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ return NS_OK;
+ }
+ uint32_t flags;
+ if (aCreateNew) {
+ MOZ_ASSERT(!aCallback);
+ flags = CacheFileIOManager::CREATE_NEW;
+
+ // make sure we can use this entry immediately
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ } else {
+ flags = CacheFileIOManager::CREATE;
+ }
+
+ if (mPriority) {
+ flags |= CacheFileIOManager::PRIORITY;
+ }
+
+ if (mPinned) {
+ flags |= CacheFileIOManager::PINNED;
+ }
+
+ mOpeningFile = true;
+ mListener = aCallback;
+ rv = CacheFileIOManager::OpenFile(mKey, flags, this);
+ if (NS_FAILED(rv)) {
+ mListener = nullptr;
+ mOpeningFile = false;
+
+ if (mPinned) {
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+ "but we want to pin, fail the file opening. [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aCreateNew) {
+ NS_WARNING("Forcing memory-only entry since OpenFile failed");
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+ "synchronously. We can continue in memory-only mode since "
+ "aCreateNew == true. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ } else if (rv == NS_ERROR_NOT_INITIALIZED) {
+ NS_WARNING(
+ "Forcing memory-only entry since CacheIOManager isn't "
+ "initialized.");
+ LOG(
+ ("CacheFile::Init() - CacheFileIOManager isn't initialized, "
+ "initializing entry as memory-only. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+
+ RefPtr<NotifyCacheFileListenerEvent> ev;
+ ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
+ rv = NS_DispatchToCurrentThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::Key(nsACString& aKey) {
+ CacheFileAutoLock lock(this);
+ aKey = mKey;
+}
+
+bool CacheFile::IsPinned() {
+ CacheFileAutoLock lock(this);
+ return mPinned;
+}
+
+nsresult CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk* aChunk) {
+ CacheFileAutoLock lock(this);
+
+ nsresult rv;
+
+ uint32_t index = aChunk->Index();
+
+ LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08" PRIx32
+ ", chunk=%p, idx=%u]",
+ this, static_cast<uint32_t>(aResult), aChunk, index));
+
+ if (aChunk->mDiscardedChunk) {
+ // We discard only unused chunks, so it must be still unused when reading
+ // data finishes.
+ MOZ_ASSERT(aChunk->mRefCnt == 2);
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ SetError(aResult);
+ }
+
+ if (HaveChunkListeners(index)) {
+ rv = NotifyChunkListeners(index, aResult, aChunk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) {
+ // In case the chunk was reused, made dirty and released between calls to
+ // CacheFileChunk::Write() and CacheFile::OnChunkWritten(), we must write
+ // the chunk to the disk again. When the chunk is unused and is dirty simply
+ // addref and release (outside the lock) the chunk which ensures that
+ // CacheFile::DeactivateChunk() will be called again.
+ RefPtr<CacheFileChunk> deactivateChunkAgain;
+
+ CacheFileAutoLock lock(this);
+
+ nsresult rv;
+
+ LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08" PRIx32
+ ", chunk=%p, idx=%u]",
+ this, static_cast<uint32_t>(aResult), aChunk, aChunk->Index()));
+
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(!mOpeningFile);
+ MOZ_ASSERT(mHandle);
+
+ if (aChunk->mDiscardedChunk) {
+ // We discard only unused chunks, so it must be still unused when writing
+ // data finishes.
+ MOZ_ASSERT(aChunk->mRefCnt == 2);
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ SetError(aResult);
+ }
+
+ if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) {
+ // update hash value in metadata
+ mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
+ }
+
+ // notify listeners if there is any
+ if (HaveChunkListeners(aChunk->Index())) {
+ // don't release the chunk since there are some listeners queued
+ rv = NotifyChunkListeners(aChunk->Index(), aResult, aChunk);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(aChunk->mRefCnt != 2);
+ return NS_OK;
+ }
+ }
+
+ if (aChunk->mRefCnt != 2) {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p,"
+ " refcnt=%" PRIuPTR "]",
+ this, aChunk, aChunk->mRefCnt.get()));
+
+ return NS_OK;
+ }
+
+ if (aChunk->IsDirty()) {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Unused chunk is dirty. We must go "
+ "through deactivation again. [this=%p, chunk=%p]",
+ this, aChunk));
+
+ deactivateChunkAgain = aChunk;
+ return NS_OK;
+ }
+
+ bool keepChunk = false;
+ if (NS_SUCCEEDED(aResult)) {
+ keepChunk = ShouldCacheChunk(aChunk->Index());
+ LOG(("CacheFile::OnChunkWritten() - %s unused chunk [this=%p, chunk=%p]",
+ keepChunk ? "Caching" : "Releasing", this, aChunk));
+ } else {
+ LOG(
+ ("CacheFile::OnChunkWritten() - Releasing failed chunk [this=%p, "
+ "chunk=%p]",
+ this, aChunk));
+ }
+
+ RemoveChunkInternal(aChunk, keepChunk);
+
+ WriteMetadataIfNeededLocked();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnChunkUpdated(CacheFileChunk* aChunk) {
+ MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) {
+ // Using an 'auto' class to perform doom or fail the listener
+ // outside the CacheFile's lock.
+ class AutoFailDoomListener {
+ public:
+ explicit AutoFailDoomListener(CacheFileHandle* aHandle)
+ : mHandle(aHandle), mAlreadyDoomed(false) {}
+ ~AutoFailDoomListener() {
+ if (!mListener) return;
+
+ if (mHandle) {
+ if (mAlreadyDoomed) {
+ mListener->OnFileDoomed(mHandle, NS_OK);
+ } else {
+ CacheFileIOManager::DoomFile(mHandle, mListener);
+ }
+ } else {
+ mListener->OnFileDoomed(nullptr, NS_ERROR_NOT_AVAILABLE);
+ }
+ }
+
+ CacheFileHandle* mHandle;
+ nsCOMPtr<CacheFileIOListener> mListener;
+ bool mAlreadyDoomed;
+ } autoDoom(aHandle);
+
+ RefPtr<CacheFileMetadata> metadata;
+ nsCOMPtr<CacheFileListener> listener;
+ bool isNew = false;
+ nsresult retval = NS_OK;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mOpeningFile);
+ MOZ_ASSERT((NS_SUCCEEDED(aResult) && aHandle) ||
+ (NS_FAILED(aResult) && !aHandle));
+ MOZ_ASSERT((mListener && !mMetadata) || // !createNew
+ (!mListener && mMetadata)); // createNew
+ MOZ_ASSERT(!mMemoryOnly || mMetadata); // memory-only was set on new entry
+
+ LOG(("CacheFile::OnFileOpened() [this=%p, rv=0x%08" PRIx32 ", handle=%p]",
+ this, static_cast<uint32_t>(aResult), aHandle));
+
+ mOpeningFile = false;
+
+ autoDoom.mListener.swap(mDoomAfterOpenListener);
+
+ if (mMemoryOnly) {
+ // We can be here only in case the entry was initilized as createNew and
+ // SetMemoryOnly() was called.
+
+ // Just don't store the handle into mHandle and exit
+ autoDoom.mAlreadyDoomed = true;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aResult)) {
+ if (mMetadata) {
+ // This entry was initialized as createNew, just switch to memory-only
+ // mode.
+ NS_WARNING("Forcing memory-only entry since OpenFile failed");
+ LOG(
+ ("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
+ "failed asynchronously. We can continue in memory-only mode since "
+ "aCreateNew == true. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ return NS_OK;
+ }
+
+ if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+ // CacheFileIOManager doesn't have mCacheDirectory, switch to
+ // memory-only mode.
+ NS_WARNING(
+ "Forcing memory-only entry since CacheFileIOManager doesn't "
+ "have mCacheDirectory.");
+ LOG(
+ ("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
+ "mCacheDirectory, initializing entry as memory-only. [this=%p]",
+ this));
+
+ mMemoryOnly = true;
+ mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
+ WrapNotNull(mLock));
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+
+ isNew = true;
+ retval = NS_OK;
+ } else {
+ // CacheFileIOManager::OpenFile() failed for another reason.
+ isNew = false;
+ retval = aResult;
+ }
+
+ mListener.swap(listener);
+ } else {
+ mHandle = aHandle;
+ if (NS_FAILED(mStatus)) {
+ CacheFileIOManager::DoomFile(mHandle, nullptr);
+ }
+
+ if (mMetadata) {
+ InitIndexEntry();
+
+ // The entry was initialized as createNew, don't try to read metadata.
+ mMetadata->SetHandle(mHandle);
+
+ // Write all cached chunks, otherwise they may stay unwritten.
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ LOG(("CacheFile::OnFileOpened() - write [this=%p, idx=%u, chunk=%p]",
+ this, idx, chunk.get()));
+
+ mChunks.InsertOrUpdate(idx, RefPtr{chunk});
+ chunk->mFile = this;
+ chunk->mActiveChunk = true;
+
+ MOZ_ASSERT(chunk->IsReady());
+
+ // This would be cleaner if we had an nsRefPtr constructor that took
+ // a RefPtr<Derived>.
+ ReleaseOutsideLock(std::move(chunk));
+
+ iter.Remove();
+ }
+
+ return NS_OK;
+ }
+ }
+ if (listener) {
+ lock.Unlock();
+ listener->OnFileReady(retval, isNew);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(aResult));
+ MOZ_ASSERT(!mMetadata);
+ MOZ_ASSERT(mListener);
+
+ // mMetaData is protected by a lock, but ReadMetaData has to be called
+ // without the lock. Alternatively we could make a
+ // "ReadMetaDataLocked", and temporarily unlock to call OnFileReady
+ metadata = mMetadata =
+ new CacheFileMetadata(mHandle, mKey, WrapNotNull(mLock));
+ }
+ metadata->ReadMetadata(this);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnDataWritten should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
+ nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnDataRead should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnMetadataRead(nsresult aResult) {
+ nsCOMPtr<CacheFileListener> listener;
+ bool isNew = false;
+ {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mListener);
+
+ LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aResult)));
+
+ if (NS_SUCCEEDED(aResult)) {
+ mPinned = mMetadata->Pinned();
+ mReady = true;
+ mDataSize = mMetadata->Offset();
+ if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
+ isNew = true;
+ mMetadata->MarkDirty();
+ } else {
+ const char* altData =
+ mMetadata->GetElement(CacheFileUtils::kAltDataKey);
+ if (altData && (NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
+ altData, &mAltDataOffset, &mAltDataType)) ||
+ (mAltDataOffset > mDataSize))) {
+ // alt-metadata cannot be parsed or alt-data offset is invalid
+ mMetadata->InitEmptyMetadata();
+ isNew = true;
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ mDataSize = 0;
+ } else {
+ PreloadChunks(0);
+ }
+ }
+
+ InitIndexEntry();
+ }
+
+ mListener.swap(listener);
+ }
+ listener->OnFileReady(aResult, isNew);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnMetadataWritten(nsresult aResult) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::OnMetadataWritten() [this=%p, rv=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aResult)));
+
+ MOZ_ASSERT(mWritingMetadata);
+ mWritingMetadata = false;
+
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(!mOpeningFile);
+
+ if (NS_WARN_IF(NS_FAILED(aResult))) {
+ // TODO close streams with an error ???
+ SetError(aResult);
+ }
+
+ if (mOutput || mInputs.Length() || mChunks.Count()) return NS_OK;
+
+ if (IsDirty()) WriteMetadataIfNeededLocked();
+
+ if (!mWritingMetadata) {
+ LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]",
+ this));
+ CacheFileIOManager::ReleaseNSPRHandle(mHandle);
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) {
+ nsCOMPtr<CacheFileListener> listener;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mListener);
+
+ LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08" PRIx32 ", handle=%p]",
+ this, static_cast<uint32_t>(aResult), aHandle));
+
+ mListener.swap(listener);
+ }
+
+ listener->OnFileDoomed(aResult);
+ return NS_OK;
+}
+
+nsresult CacheFile::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnEOFSet should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult CacheFile::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
+ MOZ_CRASH("CacheFile::OnFileRenamed should not be called!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+bool CacheFile::IsKilled() {
+ bool killed = mKill;
+ if (killed) {
+ LOG(("CacheFile is killed, this=%p", this));
+ }
+
+ return killed;
+}
+
+nsresult CacheFile::OpenInputStream(nsICacheEntry* aEntryHandle,
+ nsIInputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (!mReady) {
+ LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenInputStream() - CacheFile is in a failure state "
+ "[this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // Don't allow opening the input stream when this CacheFile is in
+ // a failed state. This is the only way to protect consumers correctly
+ // from reading a broken entry. When the file is in the failed state,
+ // it's also doomed, so reopening the entry won't make any difference -
+ // data will still be inaccessible anymore. Note that for just doomed
+ // files, we must allow reading the data.
+ return mStatus;
+ }
+
+ // Once we open input stream we no longer allow preloading of chunks without
+ // input stream, i.e. we will no longer keep first few chunks preloaded when
+ // the last input stream is closed.
+ mPreloadWithoutInputStreams = false;
+
+ CacheFileInputStream* input =
+ new CacheFileInputStream(this, aEntryHandle, false);
+ LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
+ input, this));
+
+ mInputs.AppendElement(input);
+ NS_ADDREF(input);
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(input).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenAlternativeInputStream(nsICacheEntry* aEntryHandle,
+ const char* aAltDataType,
+ nsIInputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (NS_WARN_IF(!mReady)) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - CacheFile is not ready "
+ "[this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mAltDataOffset == -1) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Alternative data is not "
+ "available [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - CacheFile is in a failure "
+ "state [this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // Don't allow opening the input stream when this CacheFile is in
+ // a failed state. This is the only way to protect consumers correctly
+ // from reading a broken entry. When the file is in the failed state,
+ // it's also doomed, so reopening the entry won't make any difference -
+ // data will still be inaccessible anymore. Note that for just doomed
+ // files, we must allow reading the data.
+ return mStatus;
+ }
+
+ if (mAltDataType != aAltDataType) {
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Alternative data is of a "
+ "different type than requested [this=%p, availableType=%s, "
+ "requestedType=%s]",
+ this, mAltDataType.get(), aAltDataType));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Once we open input stream we no longer allow preloading of chunks without
+ // input stream, i.e. we will no longer keep first few chunks preloaded when
+ // the last input stream is closed.
+ mPreloadWithoutInputStreams = false;
+
+ CacheFileInputStream* input =
+ new CacheFileInputStream(this, aEntryHandle, true);
+
+ LOG(
+ ("CacheFile::OpenAlternativeInputStream() - Creating new input stream %p "
+ "[this=%p]",
+ input, this));
+
+ mInputs.AppendElement(input);
+ NS_ADDREF(input);
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(input).take();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenOutputStream(CacheOutputCloseListener* aCloseListener,
+ nsIOutputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ nsresult rv;
+
+ if (!mReady) {
+ LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOutput) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - We already have output stream %p "
+ "[this=%p]",
+ mOutput, this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - CacheFile is in a failure state "
+ "[this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // The CacheFile is already doomed. It make no sense to allow to write any
+ // data to such entry.
+ return mStatus;
+ }
+
+ // Fail if there is any input stream opened for alternative data
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (mAltDataOffset != -1) {
+ // Remove alt-data
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenOutputStream() - Truncating alt-data failed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ SetAltMetadata(nullptr);
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ }
+
+ // Once we open output stream we no longer allow preloading of chunks without
+ // input stream. There is no reason to believe that some input stream will be
+ // opened soon. Otherwise we would cache unused chunks of all newly created
+ // entries until the CacheFile is destroyed.
+ mPreloadWithoutInputStreams = false;
+
+ mOutput = new CacheFileOutputStream(this, aCloseListener, false);
+
+ LOG(
+ ("CacheFile::OpenOutputStream() - Creating new output stream %p "
+ "[this=%p]",
+ mOutput, this));
+
+ mDataAccessed = true;
+ *_retval = do_AddRef(mOutput).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::OpenAlternativeOutputStream(
+ CacheOutputCloseListener* aCloseListener, const char* aAltDataType,
+ nsIAsyncOutputStream** _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ if (!mReady) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - CacheFile is not ready "
+ "[this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOutput) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - We already have output "
+ "stream %p [this=%p]",
+ mOutput, this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - CacheFile is in a failure "
+ "state [this=%p, status=0x%08" PRIx32 "]",
+ this, static_cast<uint32_t>(mStatus)));
+
+ // The CacheFile is already doomed. It make no sense to allow to write any
+ // data to such entry.
+ return mStatus;
+ }
+
+ // Fail if there is any input stream opened for alternative data
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ nsresult rv;
+
+ if (mAltDataOffset != -1) {
+ // Truncate old alt-data
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Truncating old alt-data "
+ "failed [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ } else {
+ mAltDataOffset = mDataSize;
+ }
+
+ nsAutoCString altMetadata;
+ CacheFileUtils::BuildAlternativeDataInfo(aAltDataType, mAltDataOffset,
+ altMetadata);
+ rv = SetAltMetadata(altMetadata.get());
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Set Metadata for alt-data"
+ "failed [rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // Once we open output stream we no longer allow preloading of chunks without
+ // input stream. There is no reason to believe that some input stream will be
+ // opened soon. Otherwise we would cache unused chunks of all newly created
+ // entries until the CacheFile is destroyed.
+ mPreloadWithoutInputStreams = false;
+
+ mOutput = new CacheFileOutputStream(this, aCloseListener, true);
+
+ LOG(
+ ("CacheFile::OpenAlternativeOutputStream() - Creating new output stream "
+ "%p [this=%p]",
+ mOutput, this));
+
+ mDataAccessed = true;
+ mAltDataType = aAltDataType;
+ *_retval = do_AddRef(mOutput).take();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetMemoryOnly() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]", mMemoryOnly,
+ this));
+
+ if (mMemoryOnly) return NS_OK;
+
+ MOZ_ASSERT(mReady);
+
+ if (!mReady) {
+ LOG(("CacheFile::SetMemoryOnly() - CacheFile is not ready [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mDataAccessed) {
+ LOG(("CacheFile::SetMemoryOnly() - Data was already accessed [this=%p]",
+ this));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // TODO what to do when this isn't a new entry and has an existing metadata???
+ mMemoryOnly = true;
+ return NS_OK;
+}
+
+nsresult CacheFile::Doom(CacheFileListener* aCallback) {
+ LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback));
+
+ CacheFileAutoLock lock(this);
+
+ return DoomLocked(aCallback);
+}
+
+nsresult CacheFile::DoomLocked(CacheFileListener* aCallback) {
+ AssertOwnsLock();
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+
+ LOG(("CacheFile::DoomLocked() [this=%p, listener=%p]", this, aCallback));
+
+ nsresult rv = NS_OK;
+
+ if (mMemoryOnly) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ if (mHandle && mHandle->IsDoomed()) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ nsCOMPtr<CacheFileIOListener> listener;
+ if (aCallback || !mHandle) {
+ listener = new DoomFileHelper(aCallback);
+ }
+ if (mHandle) {
+ rv = CacheFileIOManager::DoomFile(mHandle, listener);
+ } else if (mOpeningFile) {
+ mDoomAfterOpenListener = listener;
+ }
+
+ return rv;
+}
+
+nsresult CacheFile::ThrowMemoryCachedData() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this));
+
+ if (mMemoryOnly) {
+ // This method should not be called when the CacheFile was initialized as
+ // memory-only, but it can be called when CacheFile end up as memory-only
+ // due to e.g. IO failure since CacheEntry doesn't know it.
+ LOG(
+ ("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+ "entry is memory-only. [this=%p]",
+ this));
+
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mOpeningFile) {
+ // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading
+ // entries from being purged.
+
+ LOG(
+ ("CacheFile::ThrowMemoryCachedData() - Ignoring request because the "
+ "entry is still opening the file [this=%p]",
+ this));
+
+ return NS_ERROR_ABORT;
+ }
+
+ // We cannot release all cached chunks since we need to keep preloaded chunks
+ // in memory. See initialization of mPreloadChunkCount for explanation.
+ CleanUpCachedChunks();
+
+ return NS_OK;
+}
+
+nsresult CacheFile::GetElement(const char* aKey, char** _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ const char* value;
+ value = mMetadata->GetElement(aKey);
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = NS_xstrdup(value);
+ return NS_OK;
+}
+
+nsresult CacheFile::SetElement(const char* aKey, const char* aValue) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetElement() this=%p", this));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ if (!strcmp(aKey, CacheFileUtils::kAltDataKey)) {
+ NS_ERROR(
+ "alt-data element is reserved for internal use and must not be "
+ "changed via CacheFile::SetElement()");
+ return NS_ERROR_FAILURE;
+ }
+
+ PostWriteTimer();
+ return mMetadata->SetElement(aKey, aValue);
+}
+
+nsresult CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor* aVisitor) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ MOZ_ASSERT(mReady);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ mMetadata->Visit(aVisitor);
+ return NS_OK;
+}
+
+nsresult CacheFile::ElementsSize(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ if (!mMetadata) return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = mMetadata->ElementsSize();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetExpirationTime(uint32_t aExpirationTime) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetExpirationTime() this=%p, expiration=%u", this,
+ aExpirationTime));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+ mMetadata->SetExpirationTime(aExpirationTime);
+ return NS_OK;
+}
+
+nsresult CacheFile::GetExpirationTime(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetExpirationTime();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetFrecency(uint32_t aFrecency) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetFrecency() this=%p, frecency=%u", this, aFrecency));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr, nullptr,
+ nullptr, nullptr);
+ }
+
+ mMetadata->SetFrecency(aFrecency);
+ return NS_OK;
+}
+
+nsresult CacheFile::GetFrecency(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+ *_retval = mMetadata->GetFrecency();
+ return NS_OK;
+}
+
+nsresult CacheFile::SetNetworkTimes(uint64_t aOnStartTime,
+ uint64_t aOnStopTime) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetNetworkTimes() this=%p, aOnStartTime=%" PRIu64
+ ", aOnStopTime=%" PRIu64 "",
+ this, aOnStartTime, aOnStopTime));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ nsAutoCString onStartTime;
+ onStartTime.AppendInt(aOnStartTime);
+ nsresult rv =
+ mMetadata->SetElement("net-response-time-onstart", onStartTime.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString onStopTime;
+ onStopTime.AppendInt(aOnStopTime);
+ rv = mMetadata->SetElement("net-response-time-onstop", onStopTime.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint16_t onStartTime16 = aOnStartTime <= kIndexTimeOutOfBound
+ ? aOnStartTime
+ : kIndexTimeOutOfBound;
+ uint16_t onStopTime16 =
+ aOnStopTime <= kIndexTimeOutOfBound ? aOnStopTime : kIndexTimeOutOfBound;
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(
+ mHandle, nullptr, nullptr, &onStartTime16, &onStopTime16, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult CacheFile::GetOnStartTime(uint64_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mMetadata);
+ const char* onStartTimeStr =
+ mMetadata->GetElement("net-response-time-onstart");
+ if (!onStartTimeStr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv;
+ *_retval = nsDependentCString(onStartTimeStr).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult CacheFile::GetOnStopTime(uint64_t* _retval) {
+ CacheFileAutoLock lock(this);
+
+ MOZ_ASSERT(mMetadata);
+ const char* onStopTimeStr = mMetadata->GetElement("net-response-time-onstop");
+ if (!onStopTimeStr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv;
+ *_retval = nsDependentCString(onStopTimeStr).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult CacheFile::SetContentType(uint8_t aContentType) {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::SetContentType() this=%p, contentType=%u", this,
+ aContentType));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ // Save the content type to metadata for case we need to rebuild the index.
+ nsAutoCString contentType;
+ contentType.AppendInt(aContentType);
+ nsresult rv = mMetadata->SetElement("ctid", contentType.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, nullptr, nullptr,
+ nullptr, &aContentType);
+ }
+ return NS_OK;
+}
+
+nsresult CacheFile::SetAltMetadata(const char* aAltMetadata) {
+ AssertOwnsLock();
+ LOG(("CacheFile::SetAltMetadata() this=%p, aAltMetadata=%s", this,
+ aAltMetadata ? aAltMetadata : ""));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ nsresult rv =
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, aAltMetadata);
+
+ bool hasAltData = !!aAltMetadata;
+
+ if (NS_FAILED(rv)) {
+ // Removing element shouldn't fail because it doesn't allocate memory.
+ mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
+
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ hasAltData = false;
+ }
+
+ if (mHandle && !mHandle->IsDoomed()) {
+ CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &hasAltData, nullptr,
+ nullptr, nullptr);
+ }
+ return rv;
+}
+
+nsresult CacheFile::GetLastModified(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetLastModified();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetLastFetched(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ *_retval = mMetadata->GetLastFetched();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetFetchCount(uint32_t* _retval) {
+ CacheFileAutoLock lock(this);
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+ *_retval = mMetadata->GetFetchCount();
+ return NS_OK;
+}
+
+nsresult CacheFile::GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) {
+ CacheFileAutoLock lock(this);
+ if (!mHandle) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aDiskStorageSize = mHandle->FileSizeInK();
+ return NS_OK;
+}
+
+nsresult CacheFile::OnFetched() {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::OnFetched() this=%p", this));
+
+ MOZ_ASSERT(mMetadata);
+ NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+ PostWriteTimer();
+
+ mMetadata->OnFetched();
+ return NS_OK;
+}
+
+void CacheFile::ReleaseOutsideLock(RefPtr<nsISupports> aObject) {
+ AssertOwnsLock();
+
+ mObjsToRelease.AppendElement(std::move(aObject));
+}
+
+nsresult CacheFile::GetChunkLocked(uint32_t aIndex, ECallerType aCaller,
+ CacheFileChunkListener* aCallback,
+ CacheFileChunk** _retval) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%u, caller=%d, listener=%p]",
+ this, aIndex, aCaller, aCallback));
+
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
+ MOZ_ASSERT((aCaller == READER && aCallback) ||
+ (aCaller == WRITER && !aCallback) ||
+ (aCaller == PRELOADER && !aCallback));
+
+ // Preload chunks from disk when this is disk backed entry and the listener
+ // is reader.
+ bool preload = !mMemoryOnly && (aCaller == READER);
+
+ nsresult rv;
+
+ RefPtr<CacheFileChunk> chunk;
+ if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
+ chunk.get(), this));
+
+ // Preloader calls this method to preload only non-loaded chunks.
+ MOZ_ASSERT(aCaller != PRELOADER, "Unexpected!");
+
+ // We might get failed chunk between releasing the lock in
+ // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read
+ rv = chunk->GetStatus();
+ if (NS_FAILED(rv)) {
+ SetError(rv);
+ LOG(
+ ("CacheFile::GetChunkLocked() - Found failed chunk in mChunks "
+ "[this=%p]",
+ this));
+ return rv;
+ }
+
+ if (chunk->IsReady() || aCaller == WRITER) {
+ chunk.swap(*_retval);
+ } else {
+ QueueChunkListener(aIndex, aCallback);
+ }
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+
+ if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]",
+ chunk.get(), this));
+
+ // Preloader calls this method to preload only non-loaded chunks.
+ MOZ_ASSERT(aCaller != PRELOADER, "Unexpected!");
+
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ mCachedChunks.Remove(aIndex);
+ chunk->mFile = this;
+ chunk->mActiveChunk = true;
+
+ MOZ_ASSERT(chunk->IsReady());
+
+ chunk.swap(*_retval);
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+
+ int64_t off = aIndex * static_cast<int64_t>(kChunkSize);
+
+ if (off < mDataSize) {
+ // We cannot be here if this is memory only entry since the chunk must exist
+ MOZ_ASSERT(!mMemoryOnly);
+ if (mMemoryOnly) {
+ // If this ever really happen it is better to fail rather than crashing on
+ // a null handle.
+ LOG(
+ ("CacheFile::GetChunkLocked() - Unexpected state! Offset < mDataSize "
+ "for memory-only entry. [this=%p, off=%" PRId64
+ ", mDataSize=%" PRId64 "]",
+ this, off, mDataSize));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ chunk = new CacheFileChunk(this, aIndex, aCaller == WRITER);
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ chunk->mActiveChunk = true;
+
+ LOG(
+ ("CacheFile::GetChunkLocked() - Reading newly created chunk %p from "
+ "the disk [this=%p]",
+ chunk.get(), this));
+
+ // Read the chunk from the disk
+ rv = chunk->Read(mHandle,
+ std::min(static_cast<uint32_t>(mDataSize - off),
+ static_cast<uint32_t>(kChunkSize)),
+ mMetadata->GetHash(aIndex), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RemoveChunkInternal(chunk, false);
+ return rv;
+ }
+
+ if (aCaller == WRITER) {
+ chunk.swap(*_retval);
+ } else if (aCaller != PRELOADER) {
+ QueueChunkListener(aIndex, aCallback);
+ }
+
+ if (preload) {
+ PreloadChunks(aIndex + 1);
+ }
+
+ return NS_OK;
+ }
+ if (off == mDataSize) {
+ if (aCaller == WRITER) {
+ // this listener is going to write to the chunk
+ chunk = new CacheFileChunk(this, aIndex, true);
+ mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
+ chunk->mActiveChunk = true;
+
+ LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
+ chunk.get(), this));
+
+ chunk->InitNew();
+ mMetadata->SetHash(aIndex, chunk->Hash());
+
+ if (HaveChunkListeners(aIndex)) {
+ rv = NotifyChunkListeners(aIndex, NS_OK, chunk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ chunk.swap(*_retval);
+ return NS_OK;
+ }
+ } else {
+ if (aCaller == WRITER) {
+ // this chunk was requested by writer, but we need to fill the gap first
+
+ // Fill with zero the last chunk if it is incomplete
+ if (mDataSize % kChunkSize) {
+ rv = PadChunkWithZeroes(mDataSize / kChunkSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(!(mDataSize % kChunkSize));
+ }
+
+ uint32_t startChunk = mDataSize / kChunkSize;
+
+ if (mMemoryOnly) {
+ // We need to create all missing CacheFileChunks if this is memory-only
+ // entry
+ for (uint32_t i = startChunk; i < aIndex; i++) {
+ rv = PadChunkWithZeroes(i);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // We don't need to create CacheFileChunk for other empty chunks unless
+ // there is some input stream waiting for this chunk.
+
+ if (startChunk != aIndex) {
+ // Make sure the file contains zeroes at the end of the file
+ rv = CacheFileIOManager::TruncateSeekSetEOF(
+ mHandle, startChunk * kChunkSize, aIndex * kChunkSize, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (uint32_t i = startChunk; i < aIndex; i++) {
+ if (HaveChunkListeners(i)) {
+ rv = PadChunkWithZeroes(i);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mMetadata->SetHash(i, kEmptyChunkHash);
+ mDataSize = (i + 1) * kChunkSize;
+ }
+ }
+ }
+
+ MOZ_ASSERT(mDataSize == off);
+ rv = GetChunkLocked(aIndex, WRITER, nullptr, getter_AddRefs(chunk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ chunk.swap(*_retval);
+ return NS_OK;
+ }
+ }
+
+ // We can be here only if the caller is reader since writer always create a
+ // new chunk above and preloader calls this method to preload only chunks that
+ // are not loaded but that do exist.
+ MOZ_ASSERT(aCaller == READER, "Unexpected!");
+
+ if (mOutput) {
+ // the chunk doesn't exist but mOutput may create it
+ QueueChunkListener(aIndex, aCallback);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::PreloadChunks(uint32_t aIndex) {
+ AssertOwnsLock();
+
+ uint32_t limit = aIndex + mPreloadChunkCount;
+
+ for (uint32_t i = aIndex; i < limit; ++i) {
+ int64_t off = i * static_cast<int64_t>(kChunkSize);
+
+ if (off >= mDataSize) {
+ // This chunk is beyond EOF.
+ return;
+ }
+
+ if (mChunks.GetWeak(i) || mCachedChunks.GetWeak(i)) {
+ // This chunk is already in memory or is being read right now.
+ continue;
+ }
+
+ LOG(("CacheFile::PreloadChunks() - Preloading chunk [this=%p, idx=%u]",
+ this, i));
+
+ RefPtr<CacheFileChunk> chunk;
+ GetChunkLocked(i, PRELOADER, nullptr, getter_AddRefs(chunk));
+ // We've checked that we don't have this chunk, so no chunk must be
+ // returned.
+ MOZ_ASSERT(!chunk);
+ }
+}
+
+bool CacheFile::ShouldCacheChunk(uint32_t aIndex) {
+ AssertOwnsLock();
+
+#ifdef CACHE_CHUNKS
+ // We cache all chunks.
+ return true;
+#else
+
+ if (mPreloadChunkCount != 0 && mInputs.Length() == 0 &&
+ mPreloadWithoutInputStreams && aIndex < mPreloadChunkCount) {
+ // We don't have any input stream yet, but it is likely that some will be
+ // opened soon. Keep first mPreloadChunkCount chunks in memory. The
+ // condition is here instead of in MustKeepCachedChunk() since these
+ // chunks should be preloaded and can be kept in memory as an optimization,
+ // but they can be released at any time until they are considered as
+ // preloaded chunks for any input stream.
+ return true;
+ }
+
+ // Cache only chunks that we really need to keep.
+ return MustKeepCachedChunk(aIndex);
+#endif
+}
+
+bool CacheFile::MustKeepCachedChunk(uint32_t aIndex) {
+ AssertOwnsLock();
+
+ // We must keep the chunk when this is memory only entry or we don't have
+ // a handle yet.
+ if (mMemoryOnly || mOpeningFile) {
+ return true;
+ }
+
+ if (mPreloadChunkCount == 0) {
+ // Preloading of chunks is disabled
+ return false;
+ }
+
+ // Check whether this chunk should be considered as preloaded chunk for any
+ // existing input stream.
+
+ // maxPos is the position of the last byte in the given chunk
+ int64_t maxPos = static_cast<int64_t>(aIndex + 1) * kChunkSize - 1;
+
+ // minPos is the position of the first byte in a chunk that precedes the given
+ // chunk by mPreloadChunkCount chunks
+ int64_t minPos;
+ if (mPreloadChunkCount >= aIndex) {
+ minPos = 0;
+ } else {
+ minPos = static_cast<int64_t>(aIndex - mPreloadChunkCount) * kChunkSize;
+ }
+
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ int64_t inputPos = mInputs[i]->GetPosition();
+ if (inputPos >= minPos && inputPos <= maxPos) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult CacheFile::DeactivateChunk(CacheFileChunk* aChunk) {
+ nsresult rv;
+
+ // Avoid lock reentrancy by increasing the RefCnt
+ RefPtr<CacheFileChunk> chunk = aChunk;
+
+ {
+ CacheFileAutoLock lock(this);
+
+ LOG(("CacheFile::DeactivateChunk() [this=%p, chunk=%p, idx=%u]", this,
+ aChunk, aChunk->Index()));
+
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT((mHandle && !mMemoryOnly && !mOpeningFile) ||
+ (!mHandle && mMemoryOnly && !mOpeningFile) ||
+ (!mHandle && !mMemoryOnly && mOpeningFile));
+
+ if (aChunk->mRefCnt != 2) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - Chunk is still used [this=%p, "
+ "chunk=%p, refcnt=%" PRIuPTR "]",
+ this, aChunk, aChunk->mRefCnt.get()));
+
+ // somebody got the reference before the lock was acquired
+ return NS_OK;
+ }
+
+ if (aChunk->mDiscardedChunk) {
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(
+ RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ DebugOnly<bool> removed = mDiscardedChunks.RemoveElement(aChunk);
+ MOZ_ASSERT(removed);
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ {
+ // We can be here iff the chunk is in the hash table
+ RefPtr<CacheFileChunk> chunkCheck;
+ mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
+ MOZ_ASSERT(chunkCheck == chunk);
+
+ // We also shouldn't have any queued listener for this chunk
+ ChunkListeners* listeners;
+ mChunkListeners.Get(chunk->Index(), &listeners);
+ MOZ_ASSERT(!listeners);
+ }
+#endif
+
+ if (NS_FAILED(chunk->GetStatus())) {
+ SetError(chunk->GetStatus());
+ }
+
+ if (NS_FAILED(mStatus)) {
+ // Don't write any chunk to disk since this entry will be doomed
+ LOG(
+ ("CacheFile::DeactivateChunk() - Releasing chunk because of status "
+ "[this=%p, chunk=%p, mStatus=0x%08" PRIx32 "]",
+ this, chunk.get(), static_cast<uint32_t>(mStatus)));
+
+ RemoveChunkInternal(chunk, false);
+ return mStatus;
+ }
+
+ if (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - Writing dirty chunk to the disk "
+ "[this=%p]",
+ this));
+
+ mDataIsDirty = true;
+
+ rv = chunk->Write(mHandle, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::DeactivateChunk() - CacheFileChunk::Write() failed "
+ "synchronously. Removing it. [this=%p, chunk=%p, rv=0x%08" PRIx32
+ "]",
+ this, chunk.get(), static_cast<uint32_t>(rv)));
+
+ RemoveChunkInternal(chunk, false);
+
+ SetError(rv);
+ return rv;
+ }
+
+ // Chunk will be removed in OnChunkWritten if it is still unused
+
+ // chunk needs to be released under the lock to be able to rely on
+ // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
+ chunk = nullptr;
+ return NS_OK;
+ }
+
+ bool keepChunk = ShouldCacheChunk(aChunk->Index());
+ LOG(("CacheFile::DeactivateChunk() - %s unused chunk [this=%p, chunk=%p]",
+ keepChunk ? "Caching" : "Releasing", this, chunk.get()));
+
+ RemoveChunkInternal(chunk, keepChunk);
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+ }
+
+ return NS_OK;
+}
+
+void CacheFile::RemoveChunkInternal(CacheFileChunk* aChunk, bool aCacheChunk) {
+ AssertOwnsLock();
+
+ aChunk->mActiveChunk = false;
+ ReleaseOutsideLock(RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
+
+ if (aCacheChunk) {
+ mCachedChunks.InsertOrUpdate(aChunk->Index(), RefPtr{aChunk});
+ }
+
+ mChunks.Remove(aChunk->Index());
+}
+
+bool CacheFile::OutputStreamExists(bool aAlternativeData) {
+ AssertOwnsLock();
+
+ if (!mOutput) {
+ return false;
+ }
+
+ return mOutput->IsAlternativeData() == aAlternativeData;
+}
+
+int64_t CacheFile::BytesFromChunk(uint32_t aIndex, bool aAlternativeData) {
+ AssertOwnsLock();
+
+ int64_t dataSize;
+
+ if (mAltDataOffset != -1) {
+ if (aAlternativeData) {
+ dataSize = mDataSize;
+ } else {
+ dataSize = mAltDataOffset;
+ }
+ } else {
+ MOZ_ASSERT(!aAlternativeData);
+ dataSize = mDataSize;
+ }
+
+ if (!dataSize) {
+ return 0;
+ }
+
+ // Index of the last existing chunk.
+ uint32_t lastChunk = (dataSize - 1) / kChunkSize;
+ if (aIndex > lastChunk) {
+ return 0;
+ }
+
+ // We can use only preloaded chunks for the given stream to calculate
+ // available bytes if this is an entry stored on disk, since only those
+ // chunks are guaranteed not to be released.
+ uint32_t maxPreloadedChunk;
+ if (mMemoryOnly) {
+ maxPreloadedChunk = lastChunk;
+ } else {
+ maxPreloadedChunk = std::min(aIndex + mPreloadChunkCount, lastChunk);
+ }
+
+ uint32_t i;
+ for (i = aIndex; i <= maxPreloadedChunk; ++i) {
+ CacheFileChunk* chunk;
+
+ chunk = mChunks.GetWeak(i);
+ if (chunk) {
+ MOZ_ASSERT(i == lastChunk || chunk->DataSize() == kChunkSize);
+ if (chunk->IsReady()) {
+ continue;
+ }
+
+ // don't search this chunk in cached
+ break;
+ }
+
+ chunk = mCachedChunks.GetWeak(i);
+ if (chunk) {
+ MOZ_ASSERT(i == lastChunk || chunk->DataSize() == kChunkSize);
+ continue;
+ }
+
+ break;
+ }
+
+ // theoretic bytes in advance
+ int64_t advance = int64_t(i - aIndex) * kChunkSize;
+ // real bytes till the end of the file
+ int64_t tail = dataSize - (aIndex * kChunkSize);
+
+ return std::min(advance, tail);
+}
+
+nsresult CacheFile::Truncate(int64_t aOffset) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::Truncate() [this=%p, offset=%" PRId64 "]", this, aOffset));
+
+ nsresult rv;
+
+ // If we ever need to truncate on non alt-data boundary, we need to handle
+ // existing input streams.
+ MOZ_ASSERT(aOffset == mAltDataOffset,
+ "Truncating normal data not implemented");
+ MOZ_ASSERT(mReady);
+ MOZ_ASSERT(!mOutput);
+
+ uint32_t lastChunk = 0;
+ if (mDataSize > 0) {
+ lastChunk = (mDataSize - 1) / kChunkSize;
+ }
+
+ uint32_t newLastChunk = 0;
+ if (aOffset > 0) {
+ newLastChunk = (aOffset - 1) / kChunkSize;
+ }
+
+ uint32_t bytesInNewLastChunk = aOffset - newLastChunk * kChunkSize;
+
+ LOG(
+ ("CacheFileTruncate() - lastChunk=%u, newLastChunk=%u, "
+ "bytesInNewLastChunk=%u",
+ lastChunk, newLastChunk, bytesInNewLastChunk));
+
+ // Remove all truncated chunks from mCachedChunks
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+
+ if (idx > newLastChunk) {
+ // This is unused chunk, simply remove it.
+ LOG(("CacheFile::Truncate() - removing cached chunk [idx=%u]", idx));
+ iter.Remove();
+ }
+ }
+
+ // We need to make sure no input stream holds a reference to a chunk we're
+ // going to discard. In theory, if alt-data begins at chunk boundary, input
+ // stream for normal data can get the chunk containing only alt-data via
+ // EnsureCorrectChunk() call. The input stream won't read the data from such
+ // chunk, but it will keep the reference until the stream is closed and we
+ // cannot simply discard this chunk.
+ int64_t maxInputChunk = -1;
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ int64_t inputChunk = mInputs[i]->GetChunkIdx();
+
+ if (maxInputChunk < inputChunk) {
+ maxInputChunk = inputChunk;
+ }
+
+ MOZ_RELEASE_ASSERT(mInputs[i]->GetPosition() <= aOffset);
+ }
+
+ MOZ_RELEASE_ASSERT(maxInputChunk <= newLastChunk + 1);
+ if (maxInputChunk == newLastChunk + 1) {
+ // Truncating must be done at chunk boundary
+ MOZ_RELEASE_ASSERT(bytesInNewLastChunk == kChunkSize);
+ newLastChunk++;
+ bytesInNewLastChunk = 0;
+ LOG(
+ ("CacheFile::Truncate() - chunk %p is still in use, using "
+ "newLastChunk=%u and bytesInNewLastChunk=%u",
+ mChunks.GetWeak(newLastChunk), newLastChunk, bytesInNewLastChunk));
+ }
+
+ // Discard all truncated chunks in mChunks
+ for (auto iter = mChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+
+ if (idx > newLastChunk) {
+ RefPtr<CacheFileChunk>& chunk = iter.Data();
+ LOG(("CacheFile::Truncate() - discarding chunk [idx=%u, chunk=%p]", idx,
+ chunk.get()));
+
+ if (HaveChunkListeners(idx)) {
+ NotifyChunkListeners(idx, NS_ERROR_NOT_AVAILABLE, chunk);
+ }
+
+ chunk->mDiscardedChunk = true;
+ mDiscardedChunks.AppendElement(chunk);
+ iter.Remove();
+ }
+ }
+
+ // Remove hashes of all removed chunks from the metadata
+ for (uint32_t i = lastChunk; i > newLastChunk; --i) {
+ mMetadata->RemoveHash(i);
+ }
+
+ // Truncate new last chunk
+ if (bytesInNewLastChunk == kChunkSize) {
+ LOG(("CacheFile::Truncate() - not truncating last chunk."));
+ } else {
+ RefPtr<CacheFileChunk> chunk;
+ if (mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::Truncate() - New last chunk %p got from mChunks.",
+ chunk.get()));
+ } else if (mCachedChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ LOG(("CacheFile::Truncate() - New last chunk %p got from mCachedChunks.",
+ chunk.get()));
+ } else {
+ // New last chunk isn't loaded but we need to update the hash.
+ MOZ_ASSERT(!mMemoryOnly);
+ MOZ_ASSERT(mHandle);
+
+ rv = GetChunkLocked(newLastChunk, PRELOADER, nullptr,
+ getter_AddRefs(chunk));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // We've checked that we don't have this chunk, so no chunk must be
+ // returned.
+ MOZ_ASSERT(!chunk);
+
+ if (!mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ LOG(("CacheFile::Truncate() - New last chunk %p got from preloader.",
+ chunk.get()));
+ }
+
+ rv = chunk->GetStatus();
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::Truncate() - New last chunk is failed "
+ "[status=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ chunk->Truncate(bytesInNewLastChunk);
+
+ // If the chunk is ready set the new hash now. If it's still being loaded
+ // CacheChunk::Truncate() made the chunk dirty and the hash will be updated
+ // in OnChunkWritten().
+ if (chunk->IsReady()) {
+ mMetadata->SetHash(newLastChunk, chunk->Hash());
+ }
+ }
+
+ if (mHandle) {
+ rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle, aOffset, aOffset,
+ nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mDataSize = aOffset;
+
+ return NS_OK;
+}
+
+static uint32_t StatusToTelemetryEnum(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ return 0;
+ }
+
+ switch (aStatus) {
+ case NS_BASE_STREAM_CLOSED:
+ return 0; // Log this as a success
+ case NS_ERROR_OUT_OF_MEMORY:
+ return 2;
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ return 3;
+ case NS_ERROR_FILE_CORRUPTED:
+ return 4;
+ case NS_ERROR_FILE_NOT_FOUND:
+ return 5;
+ case NS_BINDING_ABORTED:
+ return 6;
+ default:
+ return 1; // other error
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should never get here");
+}
+
+void CacheFile::RemoveInput(CacheFileInputStream* aInput, nsresult aStatus) {
+ AssertOwnsLock();
+
+ LOG(("CacheFile::RemoveInput() [this=%p, input=%p, status=0x%08" PRIx32 "]",
+ this, aInput, static_cast<uint32_t>(aStatus)));
+
+ DebugOnly<bool> found{};
+ found = mInputs.RemoveElement(aInput);
+ MOZ_ASSERT(found);
+
+ ReleaseOutsideLock(
+ already_AddRefed<nsIInputStream>(static_cast<nsIInputStream*>(aInput)));
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+
+ // If the input didn't read all data, there might be left some preloaded
+ // chunks that won't be used anymore.
+ CleanUpCachedChunks();
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_INPUT_STREAM_STATUS,
+ StatusToTelemetryEnum(aStatus));
+}
+
+void CacheFile::RemoveOutput(CacheFileOutputStream* aOutput, nsresult aStatus) {
+ AssertOwnsLock();
+
+ nsresult rv;
+
+ LOG(("CacheFile::RemoveOutput() [this=%p, output=%p, status=0x%08" PRIx32 "]",
+ this, aOutput, static_cast<uint32_t>(aStatus)));
+
+ if (mOutput != aOutput) {
+ LOG(
+ ("CacheFile::RemoveOutput() - This output was already removed, ignoring"
+ " call [this=%p]",
+ this));
+ return;
+ }
+
+ mOutput = nullptr;
+
+ // Cancel all queued chunk and update listeners that cannot be satisfied
+ NotifyListenersAboutOutputRemoval();
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+
+ // Make sure the CacheFile status is set to a failure when the output stream
+ // is closed with a fatal error. This way we propagate correctly and w/o any
+ // windows the failure state of this entry to end consumers.
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(aStatus) &&
+ aStatus != NS_BASE_STREAM_CLOSED) {
+ if (aOutput->IsAlternativeData()) {
+ MOZ_ASSERT(mAltDataOffset != -1);
+ // If there is no alt-data input stream truncate only alt-data, otherwise
+ // doom the entry.
+ bool altDataInputExists = false;
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ if (mInputs[i]->IsAlternativeData()) {
+ altDataInputExists = true;
+ break;
+ }
+ }
+ if (altDataInputExists) {
+ SetError(aStatus);
+ } else {
+ rv = Truncate(mAltDataOffset);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("CacheFile::RemoveOutput() - Truncating alt-data failed "
+ "[rv=0x%08" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ SetError(aStatus);
+ } else {
+ SetAltMetadata(nullptr);
+ mAltDataOffset = -1;
+ mAltDataType.Truncate();
+ }
+ }
+ } else {
+ SetError(aStatus);
+ }
+ }
+
+ // Notify close listener as the last action
+ aOutput->NotifyCloseListener();
+
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_V2_OUTPUT_STREAM_STATUS,
+ StatusToTelemetryEnum(aStatus));
+}
+
+nsresult CacheFile::NotifyChunkListener(CacheFileChunkListener* aCallback,
+ nsIEventTarget* aTarget,
+ nsresult aResult, uint32_t aChunkIdx,
+ CacheFileChunk* aChunk) {
+ LOG(
+ ("CacheFile::NotifyChunkListener() [this=%p, listener=%p, target=%p, "
+ "rv=0x%08" PRIx32 ", idx=%u, chunk=%p]",
+ this, aCallback, aTarget, static_cast<uint32_t>(aResult), aChunkIdx,
+ aChunk));
+
+ RefPtr<NotifyChunkListenerEvent> ev;
+ ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk);
+ if (aTarget) {
+ return aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ return NS_DispatchToCurrentThread(ev);
+}
+
+void CacheFile::QueueChunkListener(uint32_t aIndex,
+ CacheFileChunkListener* aCallback) {
+ LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%u, listener=%p]", this,
+ aIndex, aCallback));
+
+ AssertOwnsLock();
+
+ MOZ_ASSERT(aCallback);
+
+ ChunkListenerItem* item = new ChunkListenerItem();
+ item->mTarget = CacheFileIOManager::IOTarget();
+ if (!item->mTarget) {
+ LOG(
+ ("CacheFile::QueueChunkListener() - Cannot get Cache I/O thread! Using "
+ "main thread for callback."));
+ item->mTarget = GetMainThreadSerialEventTarget();
+ }
+ item->mCallback = aCallback;
+
+ mChunkListeners.GetOrInsertNew(aIndex)->mItems.AppendElement(item);
+}
+
+nsresult CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult,
+ CacheFileChunk* aChunk) {
+ LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%u, rv=0x%08" PRIx32
+ ", "
+ "chunk=%p]",
+ this, aIndex, static_cast<uint32_t>(aResult), aChunk));
+
+ AssertOwnsLock();
+
+ nsresult rv, rv2;
+
+ ChunkListeners* listeners;
+ mChunkListeners.Get(aIndex, &listeners);
+ MOZ_ASSERT(listeners);
+
+ rv = NS_OK;
+ for (uint32_t i = 0; i < listeners->mItems.Length(); i++) {
+ ChunkListenerItem* item = listeners->mItems[i];
+ rv2 = NotifyChunkListener(item->mCallback, item->mTarget, aResult, aIndex,
+ aChunk);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) rv = rv2;
+ delete item;
+ }
+
+ mChunkListeners.Remove(aIndex);
+
+ return rv;
+}
+
+bool CacheFile::HaveChunkListeners(uint32_t aIndex) {
+ AssertOwnsLock();
+ ChunkListeners* listeners;
+ mChunkListeners.Get(aIndex, &listeners);
+ return !!listeners;
+}
+
+void CacheFile::NotifyListenersAboutOutputRemoval() {
+ LOG(("CacheFile::NotifyListenersAboutOutputRemoval() [this=%p]", this));
+
+ AssertOwnsLock();
+
+ // First fail all chunk listeners that wait for non-existent chunk
+ for (auto iter = mChunkListeners.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ auto* listeners = iter.UserData();
+
+ LOG(
+ ("CacheFile::NotifyListenersAboutOutputRemoval() - fail "
+ "[this=%p, idx=%u]",
+ this, idx));
+
+ RefPtr<CacheFileChunk> chunk;
+ mChunks.Get(idx, getter_AddRefs(chunk));
+ if (chunk) {
+ // Skip these listeners because the chunk is being read. We don't have
+ // assertion here to check its state because it might be already in READY
+ // state while CacheFile::OnChunkRead() is waiting on Cache I/O thread for
+ // a lock so the listeners hasn't been notified yet. In any case, the
+ // listeners will be notified from CacheFile::OnChunkRead().
+ continue;
+ }
+
+ for (uint32_t i = 0; i < listeners->mItems.Length(); i++) {
+ ChunkListenerItem* item = listeners->mItems[i];
+ NotifyChunkListener(item->mCallback, item->mTarget,
+ NS_ERROR_NOT_AVAILABLE, idx, nullptr);
+ delete item;
+ }
+
+ iter.Remove();
+ }
+
+ // Fail all update listeners
+ for (const auto& entry : mChunks) {
+ const RefPtr<CacheFileChunk>& chunk = entry.GetData();
+ LOG(
+ ("CacheFile::NotifyListenersAboutOutputRemoval() - fail2 "
+ "[this=%p, idx=%u]",
+ this, entry.GetKey()));
+
+ if (chunk->IsReady()) {
+ chunk->NotifyUpdateListeners();
+ }
+ }
+}
+
+bool CacheFile::DataSize(int64_t* aSize) {
+ CacheFileAutoLock lock(this);
+
+ if (OutputStreamExists(false)) {
+ return false;
+ }
+
+ if (mAltDataOffset == -1) {
+ *aSize = mDataSize;
+ } else {
+ *aSize = mAltDataOffset;
+ }
+
+ return true;
+}
+
+nsresult CacheFile::GetAltDataSize(int64_t* aSize) {
+ CacheFileAutoLock lock(this);
+ if (mOutput) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ if (mAltDataOffset == -1) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aSize = mDataSize - mAltDataOffset;
+ return NS_OK;
+}
+
+nsresult CacheFile::GetAltDataType(nsACString& aType) {
+ CacheFileAutoLock lock(this);
+
+ if (mAltDataOffset == -1) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aType = mAltDataType;
+ return NS_OK;
+}
+
+bool CacheFile::IsDoomed() {
+ CacheFileAutoLock lock(this);
+
+ if (!mHandle) return false;
+
+ return mHandle->IsDoomed();
+}
+
+bool CacheFile::IsWriteInProgress() {
+ CacheFileAutoLock lock(this);
+
+ bool result = false;
+
+ if (!mMemoryOnly) {
+ result =
+ mDataIsDirty || (mMetadata && mMetadata->IsDirty()) || mWritingMetadata;
+ }
+
+ result = result || mOpeningFile || mOutput || mChunks.Count();
+
+ return result;
+}
+
+bool CacheFile::EntryWouldExceedLimit(int64_t aOffset, int64_t aSize,
+ bool aIsAltData) {
+ CacheFileAutoLock lock(this);
+
+ if (mSkipSizeCheck || aSize < 0) {
+ return false;
+ }
+
+ int64_t totalSize = aOffset + aSize;
+ if (aIsAltData) {
+ totalSize += (mAltDataOffset == -1) ? mDataSize : mAltDataOffset;
+ }
+
+ return CacheObserver::EntryIsTooBig(totalSize, !mMemoryOnly);
+}
+
+bool CacheFile::IsDirty() { return mDataIsDirty || mMetadata->IsDirty(); }
+
+void CacheFile::WriteMetadataIfNeeded() {
+ LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this));
+
+ CacheFileAutoLock lock(this);
+
+ if (!mMemoryOnly) WriteMetadataIfNeededLocked();
+}
+
+void CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget) {
+ // When aFireAndForget is set to true, we are called from dtor.
+ // |this| must not be referenced after this method returns!
+
+ LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this));
+
+ nsresult rv;
+
+ AssertOwnsLock();
+ MOZ_ASSERT(!mMemoryOnly);
+
+ if (!mMetadata) {
+ MOZ_CRASH("Must have metadata here");
+ return;
+ }
+
+ if (NS_FAILED(mStatus)) return;
+
+ if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() ||
+ mWritingMetadata || mOpeningFile || mKill) {
+ return;
+ }
+
+ if (!aFireAndForget) {
+ // if aFireAndForget is set, we are called from dtor. Write
+ // scheduler hard-refers CacheFile otherwise, so we cannot be here.
+ CacheFileIOManager::UnscheduleMetadataWrite(this);
+ }
+
+ LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]",
+ this));
+
+ rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this);
+ if (NS_SUCCEEDED(rv)) {
+ mWritingMetadata = true;
+ mDataIsDirty = false;
+ } else {
+ LOG(
+ ("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously "
+ "failed [this=%p]",
+ this));
+ // TODO: close streams with error
+ SetError(rv);
+ }
+}
+
+void CacheFile::PostWriteTimer() {
+ if (mMemoryOnly) return;
+ LOG(("CacheFile::PostWriteTimer() [this=%p]", this));
+
+ CacheFileIOManager::ScheduleMetadataWrite(this);
+}
+
+void CacheFile::CleanUpCachedChunks() {
+ for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
+ uint32_t idx = iter.Key();
+ const RefPtr<CacheFileChunk>& chunk = iter.Data();
+
+ LOG(("CacheFile::CleanUpCachedChunks() [this=%p, idx=%u, chunk=%p]", this,
+ idx, chunk.get()));
+
+ if (MustKeepCachedChunk(idx)) {
+ LOG(("CacheFile::CleanUpCachedChunks() - Keeping chunk"));
+ continue;
+ }
+
+ LOG(("CacheFile::CleanUpCachedChunks() - Removing chunk"));
+ iter.Remove();
+ }
+}
+
+nsresult CacheFile::PadChunkWithZeroes(uint32_t aChunkIdx) {
+ AssertOwnsLock();
+
+ // This method is used to pad last incomplete chunk with zeroes or create
+ // a new chunk full of zeroes
+ MOZ_ASSERT(mDataSize / kChunkSize == aChunkIdx);
+
+ nsresult rv;
+ RefPtr<CacheFileChunk> chunk;
+ rv = GetChunkLocked(aChunkIdx, WRITER, nullptr, getter_AddRefs(chunk));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(
+ ("CacheFile::PadChunkWithZeroes() - Zeroing hole in chunk %d, range %d-%d"
+ " [this=%p]",
+ aChunkIdx, chunk->DataSize(), kChunkSize - 1, this));
+
+ CacheFileChunkWriteHandle hnd = chunk->GetWriteHandle(kChunkSize);
+ if (!hnd.Buf()) {
+ ReleaseOutsideLock(std::move(chunk));
+ SetError(NS_ERROR_OUT_OF_MEMORY);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t offset = hnd.DataSize();
+ memset(hnd.Buf() + offset, 0, kChunkSize - offset);
+ hnd.UpdateDataSize(offset, kChunkSize - offset);
+
+ ReleaseOutsideLock(std::move(chunk));
+
+ return NS_OK;
+}
+
+void CacheFile::SetError(nsresult aStatus) {
+ AssertOwnsLock();
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ if (mHandle) {
+ CacheFileIOManager::DoomFile(mHandle, nullptr);
+ }
+ }
+}
+
+nsresult CacheFile::InitIndexEntry() {
+ AssertOwnsLock();
+ MOZ_ASSERT(mHandle);
+
+ if (mHandle->IsDoomed()) return NS_OK;
+
+ nsresult rv;
+
+ rv = CacheFileIOManager::InitIndexEntry(
+ mHandle, GetOriginAttrsHash(mMetadata->OriginAttributes()),
+ mMetadata->IsAnonymous(), mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t frecency = mMetadata->GetFrecency();
+
+ bool hasAltData =
+ mMetadata->GetElement(CacheFileUtils::kAltDataKey) != nullptr;
+
+ static auto toUint16 = [](const char* s) -> uint16_t {
+ if (s) {
+ nsresult rv;
+ uint64_t n64 = nsDependentCString(s).ToInteger64(&rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
+ }
+ return kIndexTimeNotAvailable;
+ };
+
+ const char* onStartTimeStr =
+ mMetadata->GetElement("net-response-time-onstart");
+ uint16_t onStartTime = toUint16(onStartTimeStr);
+
+ const char* onStopTimeStr = mMetadata->GetElement("net-response-time-onstop");
+ uint16_t onStopTime = toUint16(onStopTimeStr);
+
+ const char* contentTypeStr = mMetadata->GetElement("ctid");
+ uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ if (contentTypeStr) {
+ int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
+ if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
+ n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
+ n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ }
+ contentType = n64;
+ }
+
+ rv = CacheFileIOManager::UpdateIndexEntry(
+ mHandle, &frecency, &hasAltData, &onStartTime, &onStopTime, &contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+size_t CacheFile::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ CacheFileAutoLock lock(const_cast<CacheFile*>(this));
+
+ size_t n = 0;
+ n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mChunks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& chunk : mChunks.Values()) {
+ n += chunk->SizeOfIncludingThis(mallocSizeOf);
+ }
+ n += mCachedChunks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& chunk : mCachedChunks.Values()) {
+ n += chunk->SizeOfIncludingThis(mallocSizeOf);
+ }
+ // Ignore metadata if it's still being read. It's not safe to access buffers
+ // in CacheFileMetadata because they might be reallocated on another thread
+ // outside CacheFile's lock.
+ if (mMetadata && mReady) {
+ n += mMetadata->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // Input streams are not elsewhere reported.
+ n += mInputs.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (uint32_t i = 0; i < mInputs.Length(); ++i) {
+ n += mInputs[i]->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // Output streams are not elsewhere reported.
+ if (mOutput) {
+ n += mOutput->SizeOfIncludingThis(mallocSizeOf);
+ }
+
+ // The listeners are usually classes reported just above.
+ n += mChunkListeners.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mObjsToRelease.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ // mHandle reported directly from CacheFileIOManager.
+
+ return n;
+}
+
+size_t CacheFile::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace mozilla::net