/* 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 #include #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 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 mCallback; nsresult mRV; uint32_t mChunkIdx; RefPtr 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 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 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(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(std::move(aChunk->mFile))); DebugOnly 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 deactivateChunkAgain; CacheFileAutoLock lock(this); nsresult rv; LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08" PRIx32 ", chunk=%p, idx=%u]", this, static_cast(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(std::move(aChunk->mFile))); DebugOnly 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 mListener; bool mAlreadyDoomed; } autoDoom(aHandle); RefPtr metadata; nsCOMPtr 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(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& 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. 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 listener; bool isNew = false; { CacheFileAutoLock lock(this); MOZ_ASSERT(mListener); LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08" PRIx32 "]", this, static_cast(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(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 listener; { CacheFileAutoLock lock(this); MOZ_ASSERT(mListener); LOG(("CacheFile::OnFileDoomed() [this=%p, rv=0x%08" PRIx32 ", handle=%p]", this, static_cast(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(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(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(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(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(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(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(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 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 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 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(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(mDataSize - off), static_cast(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(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 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(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(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 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(std::move(aChunk->mFile))); DebugOnly 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 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(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(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(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& 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 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(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(aStatus))); DebugOnly found{}; found = mInputs.RemoveElement(aInput); MOZ_ASSERT(found); ReleaseOutsideLock( already_AddRefed(static_cast(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(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(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(aResult), aChunkIdx, aChunk)); RefPtr 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(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 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& 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& 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 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(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