diff --git a/netwerk/cache2/CacheFile.cpp b/netwerk/cache2/CacheFile.cpp new file mode 100644 index 000000000000..e474a9e86f19 --- /dev/null +++ b/netwerk/cache2/CacheFile.cpp @@ -0,0 +1,1634 @@ +/* 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 "CacheLog.h" +#include "CacheFile.h" + +#include "CacheFileChunk.h" +#include "CacheFileInputStream.h" +#include "CacheFileOutputStream.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include +#include "nsComponentManagerUtils.h" +#include "nsProxyRelease.h" + +namespace mozilla { +namespace net { + +#define kMetadataWriteDelay 5000 + +class NotifyCacheFileListenerEvent : public nsRunnable { +public: + NotifyCacheFileListenerEvent(CacheFileListener *aCallback, + nsresult aResult, + bool aIsNew) + : mCallback(aCallback) + , mRV(aResult) + , mIsNew(aIsNew) + { + LOG(("NotifyCacheFileListenerEvent::NotifyCacheFileListenerEvent() " + "[this=%p]", this)); + MOZ_COUNT_CTOR(NotifyCacheFileListenerEvent); + } + + ~NotifyCacheFileListenerEvent() + { + LOG(("NotifyCacheFileListenerEvent::~NotifyCacheFileListenerEvent() " + "[this=%p]", this)); + MOZ_COUNT_DTOR(NotifyCacheFileListenerEvent); + } + + NS_IMETHOD Run() + { + LOG(("NotifyCacheFileListenerEvent::Run() [this=%p]", this)); + + mCallback->OnFileReady(mRV, mIsNew); + return NS_OK; + } + +protected: + nsCOMPtr mCallback; + nsresult mRV; + bool mIsNew; +}; + +class NotifyChunkListenerEvent : public nsRunnable { +public: + NotifyChunkListenerEvent(CacheFileChunkListener *aCallback, + nsresult aResult, + uint32_t aChunkIdx, + CacheFileChunk *aChunk) + : mCallback(aCallback) + , mRV(aResult) + , mChunkIdx(aChunkIdx) + , mChunk(aChunk) + { + LOG(("NotifyChunkListenerEvent::NotifyChunkListenerEvent() [this=%p]", + this)); + MOZ_COUNT_CTOR(NotifyChunkListenerEvent); + } + + ~NotifyChunkListenerEvent() + { + LOG(("NotifyChunkListenerEvent::~NotifyChunkListenerEvent() [this=%p]", + this)); + MOZ_COUNT_DTOR(NotifyChunkListenerEvent); + } + + NS_IMETHOD Run() + { + LOG(("NotifyChunkListenerEvent::Run() [this=%p]", this)); + + mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk); + return NS_OK; + } + +protected: + nsCOMPtr mCallback; + nsresult mRV; + uint32_t mChunkIdx; + nsRefPtr mChunk; +}; + +class MetadataWriteTimer : public nsITimerCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + MetadataWriteTimer(CacheFile *aFile); + nsresult Fire(); + nsresult Cancel(); + bool ShouldFireNew(); + +protected: + virtual ~MetadataWriteTimer(); + + nsCOMPtr mFile; + nsCOMPtr mTimer; + nsCOMPtr mTarget; + PRIntervalTime mFireTime; +}; + +NS_IMPL_ADDREF(MetadataWriteTimer) +NS_IMPL_RELEASE(MetadataWriteTimer) + +NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) +NS_INTERFACE_MAP_END_THREADSAFE + +MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile) + : mFireTime(0) +{ + LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]", + this, aFile)); + MOZ_COUNT_CTOR(MetadataWriteTimer); + + mFile = do_GetWeakReference(static_cast(aFile)); + mTarget = NS_GetCurrentThread(); +} + +MetadataWriteTimer::~MetadataWriteTimer() +{ + LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this)); + MOZ_COUNT_DTOR(MetadataWriteTimer); + + NS_ProxyRelease(mTarget, mTimer.forget().get()); + NS_ProxyRelease(mTarget, mFile.forget().get()); +} + +NS_IMETHODIMP +MetadataWriteTimer::Notify(nsITimer *aTimer) +{ + LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer)); + + MOZ_ASSERT(aTimer == mTimer); + + nsCOMPtr supp = do_QueryReferent(mFile); + if (!supp) + return NS_OK; + + CacheFile *file = static_cast( + static_cast(supp.get())); + + CacheFileAutoLock lock(file); + + if (file->mTimer != this) + return NS_OK; + + if (file->mMemoryOnly) + return NS_OK; + + file->WriteMetadataIfNeeded(); + file->mTimer = nullptr; + + return NS_OK; +} + +nsresult +MetadataWriteTimer::Fire() +{ + LOG(("MetadataWriteTimer::Fire() [this=%p]", this)); + + nsresult rv; + +#ifdef DEBUG + bool onCurrentThread = false; + rv = mTarget->IsOnCurrentThread(&onCurrentThread); + MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread); +#endif + + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mTimer->InitWithCallback(this, kMetadataWriteDelay, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + + mFireTime = PR_IntervalNow(); + + return NS_OK; +} + +nsresult +MetadataWriteTimer::Cancel() +{ + LOG(("MetadataWriteTimer::Cancel() [this=%p]", this)); + return mTimer->Cancel(); +} + +bool +MetadataWriteTimer::ShouldFireNew() +{ + uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime); + + if (delta > kMetadataWriteDelay / 2) { + LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]", + this)); + return true; + } + + LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]", + this)); + return false; +} + +class DoomFileHelper : public CacheFileIOListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + DoomFileHelper(CacheFileListener *aListener) + : mListener(aListener) + { + MOZ_COUNT_CTOR(DoomFileHelper); + } + + + NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) + { + MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!"); + return NS_ERROR_UNEXPECTED; + } + + NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult) + { + MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!"); + return NS_ERROR_UNEXPECTED; + } + + NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) + { + MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!"); + return NS_ERROR_UNEXPECTED; + } + + NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) + { + mListener->OnFileDoomed(aResult); + return NS_OK; + } + + NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) + { + MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!"); + return NS_ERROR_UNEXPECTED; + } + +private: + virtual ~DoomFileHelper() + { + MOZ_COUNT_DTOR(DoomFileHelper); + } + + nsCOMPtr mListener; +}; + +NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener) + + +// We try to write metadata when the last reference to CacheFile is released. +// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and +// if writing fails synchronously the listener is released and lock in +// CacheFile::Release() is re-entered. This helper class ensures that the +// listener is released outside the lock in case of synchronous failure. +class MetadataListenerHelper : public CacheFileMetadataListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + MetadataListenerHelper(CacheFile *aFile) + : mFile(aFile) + { + MOZ_COUNT_CTOR(MetadataListenerHelper); + mListener = static_cast(aFile); + } + + NS_IMETHOD OnMetadataRead(nsresult aResult) + { + MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!"); + return NS_ERROR_UNEXPECTED; + } + + NS_IMETHOD OnMetadataWritten(nsresult aResult) + { + mFile = nullptr; + return mListener->OnMetadataWritten(aResult); + } + +private: + virtual ~MetadataListenerHelper() + { + MOZ_COUNT_DTOR(MetadataListenerHelper); + if (mFile) { + mFile->ReleaseOutsideLock(mListener.forget().get()); + } + } + + CacheFile* mFile; + nsCOMPtr mListener; +}; + +NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener) + + +NS_IMPL_ADDREF(CacheFile) +NS_IMETHODIMP_(nsrefcnt) +CacheFile::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "CacheFile"); + + MOZ_ASSERT(count != 0, "Unexpected"); + + if (count == 1) { + bool deleteFile = false; + + // Not using CacheFileAutoLock since it hard-refers the file + // and in it's destructor reenters this method. + Lock(); + + if (mMemoryOnly) { + deleteFile = true; + } + else if (mMetadata) { + WriteMetadataIfNeeded(); + if (mWritingMetadata) { + MOZ_ASSERT(mRefCnt > 1); + } else { + if (mRefCnt == 1) + deleteFile = true; + } + } + + Unlock(); + + if (!deleteFile) { + return count; + } + + NS_LOG_RELEASE(this, 0, "CacheFile"); + delete (this); + return 0; + } + + return count; +} + +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(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, + mozilla::net::CacheFileChunkListener) +NS_INTERFACE_MAP_END_THREADSAFE + +CacheFile::CacheFile() + : mLock("CacheFile.mLock") + , mOpeningFile(false) + , mReady(false) + , mMemoryOnly(false) + , mDataAccessed(false) + , mDataIsDirty(false) + , mWritingMetadata(false) + , mStatus(NS_OK) + , mDataSize(-1) + , mOutput(nullptr) +{ + LOG(("CacheFile::CacheFile() [this=%p]", this)); + + NS_ADDREF(this); + MOZ_COUNT_CTOR(CacheFile); +} + +CacheFile::~CacheFile() +{ + LOG(("CacheFile::~CacheFile() [this=%p]", this)); + + MOZ_COUNT_DTOR(CacheFile); +} + +nsresult +CacheFile::Init(const nsACString &aKey, + bool aCreateNew, + bool aMemoryOnly, + bool aPriority, + bool aKeyIsHash, + CacheFileListener *aCallback) +{ + MOZ_ASSERT(!mListener); + MOZ_ASSERT(!mHandle); + + nsresult rv; + + mKey = aKey; + mMemoryOnly = aMemoryOnly; + mKeyIsHash = aKeyIsHash; + + LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, " + "listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly, aCallback)); + + if (mMemoryOnly) { + MOZ_ASSERT(!aCallback); + + MOZ_ASSERT(!mKeyIsHash); + mMetadata = new CacheFileMetadata(mKey); + mReady = true; + mDataSize = mMetadata->Offset(); + return NS_OK; + } + else { + uint32_t flags; + if (aCreateNew) { + MOZ_ASSERT(!aCallback); + flags = CacheFileIOManager::CREATE_NEW; + + // make sure we can use this entry immediately + MOZ_ASSERT(!mKeyIsHash); + mMetadata = new CacheFileMetadata(mKey); + mReady = true; + mDataSize = mMetadata->Offset(); + } + else + flags = CacheFileIOManager::CREATE; + + if (aPriority) + flags |= CacheFileIOManager::PRIORITY; + if (aKeyIsHash) + flags |= CacheFileIOManager::NOHASH; + + mOpeningFile = true; + mListener = aCallback; + rv = CacheFileIOManager::OpenFile(mKey, flags, this); + if (NS_FAILED(rv)) { + mListener = nullptr; + mOpeningFile = false; + + 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; + MOZ_ASSERT(!mKeyIsHash); + mMetadata = new CacheFileMetadata(mKey); + mReady = true; + mDataSize = mMetadata->Offset(); + + nsRefPtr 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; +} + +nsresult +CacheFile::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) +{ + CacheFileAutoLock lock(this); + + nsresult rv; + + uint32_t index = aChunk->Index(); + + LOG(("CacheFile::OnChunkRead() [this=%p, rv=0x%08x, chunk=%p, idx=%d]", + this, aResult, aChunk, index)); + + // TODO handle ERROR state + + if (HaveChunkListeners(index)) { + rv = NotifyChunkListeners(index, aResult, aChunk); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) +{ + CacheFileAutoLock lock(this); + + nsresult rv; + + LOG(("CacheFile::OnChunkWritten() [this=%p, rv=0x%08x, chunk=%p, idx=%d]", + this, aResult, aChunk, aChunk->Index())); + + MOZ_ASSERT(!mMemoryOnly); + + // TODO handle ERROR state + + if (NS_FAILED(aResult)) { + // TODO ??? doom entry + // TODO mark this chunk as memory only, since it wasn't written to disk and + // therefore cannot be released from memory + // LOG + } + + 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(), NS_OK, 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=%d]", this, aChunk, aChunk->mRefCnt.get())); + + return NS_OK; + } + + LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]", + this, aChunk)); + + aChunk->mRemovingChunk = true; + ReleaseOutsideLock(static_cast( + aChunk->mFile.forget().get())); + mCachedChunks.Put(aChunk->Index(), aChunk); + mChunks.Remove(aChunk->Index()); + WriteMetadataIfNeeded(); + + 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) +{ + nsresult rv; + + 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%08x, handle=%p]", + this, aResult, aHandle)); + + mOpeningFile = false; + + 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 + return NS_OK; + } + else 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; + } + else 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; + MOZ_ASSERT(!mKeyIsHash); + mMetadata = new CacheFileMetadata(mKey); + 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 (mMetadata) { + // The entry was initialized as createNew, don't try to read metadata. + mMetadata->SetHandle(mHandle); + + // Write all cached chunks, otherwise thay may stay unwritten. + mCachedChunks.Enumerate(&CacheFile::WriteAllCachedChunks, this); + + return NS_OK; + } + } + } + + if (listener) { + listener->OnFileReady(retval, isNew); + return NS_OK; + } + + MOZ_ASSERT(NS_SUCCEEDED(aResult)); + MOZ_ASSERT(!mMetadata); + MOZ_ASSERT(mListener); + + mMetadata = new CacheFileMetadata(mHandle, mKey, mKeyIsHash); + + rv = mMetadata->ReadMetadata(this); + if (NS_FAILED(rv)) { + mListener.swap(listener); + listener->OnFileReady(rv, false); + } + + 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) +{ + MOZ_ASSERT(mListener); + + LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult)); + + bool isNew = false; + if (NS_SUCCEEDED(aResult)) { + mReady = true; + mDataSize = mMetadata->Offset(); + if (mDataSize == 0 && mMetadata->ElementsSize() == 0) { + isNew = true; + mMetadata->MarkDirty(); + } + } + + nsCOMPtr listener; + 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%08x]", this, aResult)); + + MOZ_ASSERT(mWritingMetadata); + mWritingMetadata = false; + + MOZ_ASSERT(!mMemoryOnly); + MOZ_ASSERT(!mOpeningFile); + + if (NS_FAILED(aResult)) { + // TODO close streams with an error ??? + } + + if (mOutput || mInputs.Length() || mChunks.Count()) + return NS_OK; + + if (IsDirty()) + WriteMetadataIfNeeded(); + + 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%08x, handle=%p]", + this, 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::OpenInputStream(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; + } + + CacheFileInputStream *input = new CacheFileInputStream(this); + + LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]", + input, this)); + + mInputs.AppendElement(input); + NS_ADDREF(input); + + mDataAccessed = true; + NS_ADDREF(*_retval = input); + return NS_OK; +} + +nsresult +CacheFile::OpenOutputStream(nsIOutputStream **_retval) +{ + CacheFileAutoLock lock(this); + + MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); + + 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; + } + + mOutput = new CacheFileOutputStream(this); + + LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p " + "[this=%p]", mOutput, this)); + + mDataAccessed = true; + NS_ADDREF(*_retval = mOutput); + return NS_OK; +} + +nsresult +CacheFile::SetMemoryOnly() +{ + LOG(("CacheFile::SetMemoryOnly() aMemoryOnly=%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) +{ + CacheFileAutoLock lock(this); + + MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); + + LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback)); + + nsresult rv; + + if (mMemoryOnly) { + // TODO what exactly to do here? + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr listener; + if (aCallback) + listener = new DoomFileHelper(aCallback); + + if (mHandle) { + rv = CacheFileIOManager::DoomFile(mHandle, listener); + } else { + rv = CacheFileIOManager::DoomFileByKey(mKey, listener); + } + + return rv; +} + +nsresult +CacheFile::ThrowMemoryCachedData() +{ + CacheFileAutoLock lock(this); + + LOG(("CacheFile::ThrowMemoryCachedData() [this=%p]", this)); + + 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; + } + + mCachedChunks.Clear(); + return NS_OK; +} + +nsresult +CacheFile::GetElement(const char *aKey, const char **_retval) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + *_retval = mMetadata->GetElement(aKey); + return NS_OK; +} + +nsresult +CacheFile::SetElement(const char *aKey, const char *aValue) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + PostWriteTimer(); + return mMetadata->SetElement(aKey, aValue); +} + +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); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + PostWriteTimer(); + return mMetadata->SetExpirationTime(aExpirationTime); +} + +nsresult +CacheFile::GetExpirationTime(uint32_t *_retval) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + return mMetadata->GetExpirationTime(_retval); +} + +nsresult +CacheFile::SetLastModified(uint32_t aLastModified) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + PostWriteTimer(); + return mMetadata->SetLastModified(aLastModified); +} + +nsresult +CacheFile::GetLastModified(uint32_t *_retval) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + return mMetadata->GetLastModified(_retval); +} + +nsresult +CacheFile::GetLastFetched(uint32_t *_retval) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + return mMetadata->GetLastFetched(_retval); +} + +nsresult +CacheFile::GetFetchCount(uint32_t *_retval) +{ + CacheFileAutoLock lock(this); + MOZ_ASSERT(mMetadata); + NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED); + + return mMetadata->GetFetchCount(_retval); +} + +void +CacheFile::Lock() +{ + mLock.Lock(); +} + +void +CacheFile::Unlock() +{ + nsTArray objs; + objs.SwapElements(mObjsToRelease); + + mLock.Unlock(); + + for (uint32_t i = 0; i < objs.Length(); i++) + objs[i]->Release(); +} + +void +CacheFile::AssertOwnsLock() +{ + mLock.AssertCurrentThreadOwns(); +} + +void +CacheFile::ReleaseOutsideLock(nsISupports *aObject) +{ + AssertOwnsLock(); + + mObjsToRelease.AppendElement(aObject); +} + +nsresult +CacheFile::GetChunk(uint32_t aIndex, bool aWriter, + CacheFileChunkListener *aCallback, CacheFileChunk **_retval) +{ + CacheFileAutoLock lock(this); + return GetChunkLocked(aIndex, aWriter, aCallback, _retval); +} + +nsresult +CacheFile::GetChunkLocked(uint32_t aIndex, bool aWriter, + CacheFileChunkListener *aCallback, + CacheFileChunk **_retval) +{ + AssertOwnsLock(); + + LOG(("CacheFile::GetChunkLocked() [this=%p, idx=%d, writer=%d, listener=%p]", + this, aIndex, aWriter, aCallback)); + + MOZ_ASSERT(mReady); + MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); + MOZ_ASSERT((aWriter && !aCallback) || (!aWriter && aCallback)); + + nsresult rv; + + nsRefPtr chunk; + if (mChunks.Get(aIndex, getter_AddRefs(chunk))) { + LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]", + chunk.get(), this)); + + if (chunk->IsReady() || aWriter) { + chunk.swap(*_retval); + } + else { + rv = QueueChunkListener(aIndex, aCallback); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; + } + + if (mCachedChunks.Get(aIndex, getter_AddRefs(chunk))) { + LOG(("CacheFile::GetChunkLocked() - Reusing cached chunk %p [this=%p]", + chunk.get(), this)); + + mChunks.Put(aIndex, chunk); + mCachedChunks.Remove(aIndex); + chunk->mFile = this; + chunk->mRemovingChunk = false; + + MOZ_ASSERT(chunk->IsReady()); + + chunk.swap(*_retval); + return NS_OK; + } + + int64_t off = aIndex * kChunkSize; + + if (off < mDataSize) { + // We cannot be here if this is memory only entry since the chunk must exist + MOZ_ASSERT(!mMemoryOnly); + + chunk = new CacheFileChunk(this, aIndex); + mChunks.Put(aIndex, chunk); + + 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_FAILED(rv)) { + chunk->mRemovingChunk = true; + ReleaseOutsideLock(static_cast( + chunk->mFile.forget().get())); + mChunks.Remove(aIndex); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aWriter) { + chunk.swap(*_retval); + } + else { + rv = QueueChunkListener(aIndex, aCallback); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; + } + else if (off == mDataSize) { + if (aWriter) { + // this listener is going to write to the chunk + chunk = new CacheFileChunk(this, aIndex); + mChunks.Put(aIndex, chunk); + + LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]", + chunk.get(), this)); + + chunk->InitNew(this); + 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 (aWriter) { + // 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, true, nullptr, getter_AddRefs(chunk)); + NS_ENSURE_SUCCESS(rv, rv); + + chunk.swap(*_retval); + return NS_OK; + } + } + + if (mOutput) { + // the chunk doesn't exist but mOutput may create it + rv = QueueChunkListener(aIndex, aCallback); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +nsresult +CacheFile::RemoveChunk(CacheFileChunk *aChunk) +{ + nsresult rv; + + // Avoid lock reentrancy by increasing the RefCnt + nsRefPtr chunk = aChunk; + + { + CacheFileAutoLock lock(this); + + LOG(("CacheFile::RemoveChunk() [this=%p, chunk=%p, idx=%d]", + this, aChunk, aChunk->Index())); + + MOZ_ASSERT(mReady); + MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); + + if (aChunk->mRefCnt != 2) { + LOG(("CacheFile::RemoveChunk() - Chunk is still used [this=%p, chunk=%p, " + "refcnt=%d]", this, aChunk, aChunk->mRefCnt.get())); + + // somebody got the reference before the lock was acquired + return NS_OK; + } + +#ifdef DEBUG + { + // We can be here iff the chunk is in the hash table + nsRefPtr 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 (chunk->IsDirty() && !mMemoryOnly && !mOpeningFile) { + LOG(("CacheFile::RemoveChunk() - Writing dirty chunk to the disk " + "[this=%p]", this)); + + mDataIsDirty = true; + + rv = chunk->Write(mHandle, this); + if (NS_FAILED(rv)) { + // TODO ??? doom entry + // TODO mark this chunk as memory only, since it wasn't written to disk + // and therefore cannot be released from memory + // LOG + } + else { + // 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; + } + } + + LOG(("CacheFile::RemoveChunk() - Caching unused chunk [this=%p, chunk=%p]", + this, chunk.get())); + + chunk->mRemovingChunk = true; + ReleaseOutsideLock(static_cast( + chunk->mFile.forget().get())); + mCachedChunks.Put(chunk->Index(), chunk); + mChunks.Remove(chunk->Index()); + if (!mMemoryOnly) + WriteMetadataIfNeeded(); + } + + return NS_OK; +} + +nsresult +CacheFile::RemoveInput(CacheFileInputStream *aInput) +{ + CacheFileAutoLock lock(this); + + LOG(("CacheFile::RemoveInput() [this=%p, input=%p]", this, aInput)); + + DebugOnly found; + found = mInputs.RemoveElement(aInput); + MOZ_ASSERT(found); + + ReleaseOutsideLock(static_cast(aInput)); + + if (!mMemoryOnly) + WriteMetadataIfNeeded(); + + return NS_OK; +} + +nsresult +CacheFile::RemoveOutput(CacheFileOutputStream *aOutput) +{ + AssertOwnsLock(); + + LOG(("CacheFile::RemoveOutput() [this=%p, output=%p]", this, aOutput)); + + if (mOutput != aOutput) { + LOG(("CacheFile::RemoveOutput() - This output was already removed, ignoring" + " call [this=%p]", this)); + return NS_OK; + } + + mOutput = nullptr; + + // Cancel all queued chunk and update listeners that cannot be satisfied + NotifyListenersAboutOutputRemoval(); + + if (!mMemoryOnly) + WriteMetadataIfNeeded(); + + return NS_OK; +} + +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%08x, idx=%d, chunk=%p]", this, aCallback, aTarget, aResult, + aChunkIdx, aChunk)); + + nsresult rv; + nsRefPtr ev; + ev = new NotifyChunkListenerEvent(aCallback, aResult, aChunkIdx, aChunk); + if (aTarget) + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + else + rv = NS_DispatchToCurrentThread(ev); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFile::QueueChunkListener(uint32_t aIndex, + CacheFileChunkListener *aCallback) +{ + LOG(("CacheFile::QueueChunkListener() [this=%p, idx=%d, listener=%p]", + this, aIndex, aCallback)); + + AssertOwnsLock(); + + MOZ_ASSERT(aCallback); + + ChunkListenerItem *item = new ChunkListenerItem(); + item->mTarget = NS_GetCurrentThread(); + item->mCallback = aCallback; + + ChunkListeners *listeners; + if (!mChunkListeners.Get(aIndex, &listeners)) { + listeners = new ChunkListeners(); + mChunkListeners.Put(aIndex, listeners); + } + + listeners->mItems.AppendElement(item); + return NS_OK; +} + +nsresult +CacheFile::NotifyChunkListeners(uint32_t aIndex, nsresult aResult, + CacheFileChunk *aChunk) +{ + LOG(("CacheFile::NotifyChunkListeners() [this=%p, idx=%d, rv=0x%08x, " + "chunk=%p]", this, aIndex, 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) +{ + 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 + mChunkListeners.Enumerate(&CacheFile::FailListenersIfNonExistentChunk, + this); + + // Fail all update listeners + mChunks.Enumerate(&CacheFile::FailUpdateListeners, this); +} + +bool +CacheFile::DataSize(int64_t* aSize) +{ + CacheFileAutoLock lock(this); + + if (mOutput) + return false; + + *aSize = mDataSize; + return true; +} + +bool +CacheFile::IsDirty() +{ + return mDataIsDirty || mMetadata->IsDirty(); +} + +void +CacheFile::WriteMetadataIfNeeded() +{ + LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this)); + + nsresult rv; + + AssertOwnsLock(); + MOZ_ASSERT(!mMemoryOnly); + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + if (NS_FAILED(mStatus)) + return; + + if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() || + mWritingMetadata || mOpeningFile) + return; + + LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]", + this)); + + nsRefPtr mlh = new MetadataListenerHelper(this); + + rv = mMetadata->WriteMetadata(mDataSize, mlh); + if (NS_SUCCEEDED(rv)) { + mWritingMetadata = true; + mDataIsDirty = false; + } + else { + LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed " + "[this=%p]", this)); + // TODO: close streams with error + if (NS_SUCCEEDED(mStatus)) + mStatus = rv; + } +} + +void +CacheFile::PostWriteTimer() +{ + LOG(("CacheFile::PostWriteTimer() [this=%p]", this)); + + nsresult rv; + + AssertOwnsLock(); + + if (mTimer) { + if (mTimer->ShouldFireNew()) { + LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]", + this)); + mTimer->Cancel(); + mTimer = nullptr; + } + else { + LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this)); + return; + } + } + + mTimer = new MetadataWriteTimer(this); + + rv = mTimer->Fire(); + if (NS_FAILED(rv)) { + LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x " + "[this=%p]", rv, this)); + } +} + +PLDHashOperator +CacheFile::WriteAllCachedChunks(const uint32_t& aIdx, + nsRefPtr& aChunk, + void* aClosure) +{ + CacheFile *file = static_cast(aClosure); + + LOG(("CacheFile::WriteAllCachedChunks() [this=%p, idx=%d, chunk=%p]", + file, aIdx, aChunk.get())); + + file->mChunks.Put(aIdx, aChunk); + aChunk->mFile = file; + aChunk->mRemovingChunk = false; + + MOZ_ASSERT(aChunk->IsReady()); + + NS_ADDREF(aChunk); + file->ReleaseOutsideLock(aChunk); + + return PL_DHASH_REMOVE; +} + +PLDHashOperator +CacheFile::FailListenersIfNonExistentChunk( + const uint32_t& aIdx, + nsAutoPtr& aListeners, + void* aClosure) +{ + CacheFile *file = static_cast(aClosure); + + LOG(("CacheFile::FailListenersIfNonExistentChunk() [this=%p, idx=%d]", + file, aIdx)); + + nsRefPtr chunk; + file->mChunks.Get(aIdx, getter_AddRefs(chunk)); + if (chunk) { + MOZ_ASSERT(!chunk->IsReady()); + return PL_DHASH_NEXT; + } + + for (uint32_t i = 0 ; i < aListeners->mItems.Length() ; i++) { + ChunkListenerItem *item = aListeners->mItems[i]; + file->NotifyChunkListener(item->mCallback, item->mTarget, + NS_ERROR_NOT_AVAILABLE, aIdx, nullptr); + delete item; + } + + return PL_DHASH_REMOVE; +} + +PLDHashOperator +CacheFile::FailUpdateListeners( + const uint32_t& aIdx, + nsRefPtr& aChunk, + void* aClosure) +{ +#ifdef PR_LOGGING + CacheFile *file = static_cast(aClosure); +#endif + + LOG(("CacheFile::FailUpdateListeners() [this=%p, idx=%d]", + file, aIdx)); + + if (aChunk->IsReady()) { + aChunk->NotifyUpdateListeners(); + } + + return PL_DHASH_NEXT; +} + +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; + nsRefPtr chunk; + rv = GetChunkLocked(aChunkIdx, true, 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)); + + chunk->EnsureBufSize(kChunkSize); + memset(chunk->BufForWriting() + chunk->DataSize(), 0, kChunkSize - chunk->DataSize()); + + chunk->UpdateDataSize(chunk->DataSize(), kChunkSize - chunk->DataSize(), + false); + + ReleaseOutsideLock(chunk.forget().get()); + + return NS_OK; +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFile.h b/netwerk/cache2/CacheFile.h new file mode 100644 index 000000000000..cbc1c57f11e6 --- /dev/null +++ b/netwerk/cache2/CacheFile.h @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFile__h__ +#define CacheFile__h__ + +#include "CacheFileChunk.h" +#include "nsWeakReference.h" +#include "CacheFileIOManager.h" +#include "CacheFileMetadata.h" +#include "nsRefPtrHashtable.h" +#include "nsClassHashtable.h" +#include "mozilla/Mutex.h" + +class nsIInputStream; +class nsIOutputStream; + +namespace mozilla { +namespace net { + +class CacheFileInputStream; +class CacheFileOutputStream; +class MetadataWriteTimer; + +#define CACHEFILELISTENER_IID \ +{ /* 95e7f284-84ba-48f9-b1fc-3a7336b4c33c */ \ + 0x95e7f284, \ + 0x84ba, \ + 0x48f9, \ + {0xb1, 0xfc, 0x3a, 0x73, 0x36, 0xb4, 0xc3, 0x3c} \ +} + +class CacheFileListener : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILELISTENER_IID) + + NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew) = 0; + NS_IMETHOD OnFileDoomed(nsresult aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID) + + +class CacheFile : public CacheFileChunkListener + , public CacheFileIOListener + , public CacheFileMetadataListener + , public nsSupportsWeakReference +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + CacheFile(); + + nsresult Init(const nsACString &aKey, + bool aCreateNew, + bool aMemoryOnly, + bool aPriority, + bool aKeyIsHash, + CacheFileListener *aCallback); + + NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, + CacheFileChunk *aChunk); + NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk); + + NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult); + NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult); + NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult); + + NS_IMETHOD OnMetadataRead(nsresult aResult); + NS_IMETHOD OnMetadataWritten(nsresult aResult); + + NS_IMETHOD OpenInputStream(nsIInputStream **_retval); + NS_IMETHOD OpenOutputStream(nsIOutputStream **_retval); + NS_IMETHOD SetMemoryOnly(); + NS_IMETHOD Doom(CacheFileListener *aCallback); + + nsresult ThrowMemoryCachedData(); + + // metadata forwarders + nsresult GetElement(const char *aKey, const char **_retval); + nsresult SetElement(const char *aKey, const char *aValue); + nsresult ElementsSize(uint32_t *_retval); + nsresult SetExpirationTime(uint32_t aExpirationTime); + nsresult GetExpirationTime(uint32_t *_retval); + nsresult SetLastModified(uint32_t aLastModified); + nsresult GetLastModified(uint32_t *_retval); + nsresult GetLastFetched(uint32_t *_retval); + nsresult GetFetchCount(uint32_t *_retval); + + bool DataSize(int64_t* aSize); + void Key(nsACString& aKey) { aKey = mKey; } + +private: + friend class CacheFileChunk; + friend class CacheFileInputStream; + friend class CacheFileOutputStream; + friend class CacheFileAutoLock; + friend class MetadataWriteTimer; + friend class MetadataListenerHelper; + + virtual ~CacheFile(); + + void Lock(); + void Unlock(); + void AssertOwnsLock(); + void ReleaseOutsideLock(nsISupports *aObject); + + nsresult GetChunk(uint32_t aIndex, bool aWriter, + CacheFileChunkListener *aCallback, + CacheFileChunk **_retval); + nsresult GetChunkLocked(uint32_t aIndex, bool aWriter, + CacheFileChunkListener *aCallback, + CacheFileChunk **_retval); + nsresult RemoveChunk(CacheFileChunk *aChunk); + + nsresult RemoveInput(CacheFileInputStream *aInput); + nsresult RemoveOutput(CacheFileOutputStream *aOutput); + nsresult NotifyChunkListener(CacheFileChunkListener *aCallback, + nsIEventTarget *aTarget, + nsresult aResult, + uint32_t aChunkIdx, + CacheFileChunk *aChunk); + nsresult QueueChunkListener(uint32_t aIndex, + CacheFileChunkListener *aCallback); + nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult, + CacheFileChunk *aChunk); + bool HaveChunkListeners(uint32_t aIndex); + void NotifyListenersAboutOutputRemoval(); + + bool IsDirty(); + void WriteMetadataIfNeeded(); + void PostWriteTimer(); + + static PLDHashOperator WriteAllCachedChunks(const uint32_t& aIdx, + nsRefPtr& aChunk, + void* aClosure); + + static PLDHashOperator FailListenersIfNonExistentChunk( + const uint32_t& aIdx, + nsAutoPtr& aListeners, + void* aClosure); + + static PLDHashOperator FailUpdateListeners(const uint32_t& aIdx, + nsRefPtr& aChunk, + void* aClosure); + + nsresult PadChunkWithZeroes(uint32_t aChunkIdx); + + mozilla::Mutex mLock; + bool mOpeningFile; + bool mReady; + bool mMemoryOnly; + bool mDataAccessed; + bool mDataIsDirty; + bool mWritingMetadata; + bool mKeyIsHash; + nsresult mStatus; + int64_t mDataSize; + nsCString mKey; + + nsRefPtr mHandle; + nsRefPtr mMetadata; + nsCOMPtr mListener; + nsRefPtr mTimer; + + nsRefPtrHashtable mChunks; + nsClassHashtable mChunkListeners; + nsRefPtrHashtable mCachedChunks; + + nsTArray mInputs; + CacheFileOutputStream *mOutput; + + nsTArray mObjsToRelease; +}; + +class CacheFileAutoLock { +public: + CacheFileAutoLock(CacheFile *aFile) + : mFile(aFile) + , mLocked(true) + { + mFile->Lock(); + } + ~CacheFileAutoLock() + { + if (mLocked) + mFile->Unlock(); + } + void Lock() + { + MOZ_ASSERT(!mLocked); + mFile->Lock(); + mLocked = true; + } + void Unlock() + { + MOZ_ASSERT(mLocked); + mFile->Unlock(); + mLocked = false; + } + +private: + nsRefPtr mFile; + bool mLocked; +}; + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheFileChunk.cpp b/netwerk/cache2/CacheFileChunk.cpp new file mode 100644 index 000000000000..abc2a417cc1e --- /dev/null +++ b/netwerk/cache2/CacheFileChunk.cpp @@ -0,0 +1,675 @@ +/* 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 "CacheFileChunk.h" + +#include "CacheLog.h" +#include "CacheFile.h" +#include "nsThreadUtils.h" +#include "nsAlgorithm.h" +#include + +namespace mozilla { +namespace net { + +#define kMinBufSize 512 + +class NotifyUpdateListenerEvent : public nsRunnable { +public: + NotifyUpdateListenerEvent(CacheFileChunkListener *aCallback, + CacheFileChunk *aChunk) + : mCallback(aCallback) + , mChunk(aChunk) + { + LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]", + this)); + MOZ_COUNT_CTOR(NotifyUpdateListenerEvent); + } + + ~NotifyUpdateListenerEvent() + { + LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]", + this)); + MOZ_COUNT_DTOR(NotifyUpdateListenerEvent); + } + + NS_IMETHOD Run() + { + LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this)); + + mCallback->OnChunkUpdated(mChunk); + return NS_OK; + } + +protected: + nsCOMPtr mCallback; + nsRefPtr mChunk; +}; + + +class ValidityPair { +public: + ValidityPair(uint32_t aOffset, uint32_t aLen) + : mOffset(aOffset), mLen(aLen) + {} + + ValidityPair& operator=(const ValidityPair& aOther) { + mOffset = aOther.mOffset; + mLen = aOther.mLen; + return *this; + } + + bool Overlaps(const ValidityPair& aOther) const { + if ((mOffset <= aOther.mOffset && mOffset + mLen >= aOther.mOffset) || + (aOther.mOffset <= mOffset && aOther.mOffset + mLen >= mOffset)) + return true; + + return false; + } + + bool LessThan(const ValidityPair& aOther) const { + if (mOffset < aOther.mOffset) + return true; + + if (mOffset == aOther.mOffset && mLen < aOther.mLen) + return true; + + return false; + } + + void Merge(const ValidityPair& aOther) { + MOZ_ASSERT(Overlaps(aOther)); + + uint32_t offset = std::min(mOffset, aOther.mOffset); + uint32_t end = std::max(mOffset + mLen, aOther.mOffset + aOther.mLen); + + mOffset = offset; + mLen = end - offset; + } + + uint32_t Offset() { return mOffset; } + uint32_t Len() { return mLen; } + +private: + uint32_t mOffset; + uint32_t mLen; +}; + + +NS_IMPL_ADDREF(CacheFileChunk) +NS_IMETHODIMP_(nsrefcnt) +CacheFileChunk::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "CacheFileChunk"); + + if (0 == count) { + mRefCnt = 1; + delete (this); + return 0; + } + + if (!mRemovingChunk && count == 1) { + mFile->RemoveChunk(this); + } + + return count; +} + +NS_INTERFACE_MAP_BEGIN(CacheFileChunk) + NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END_THREADSAFE + +CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex) + : mIndex(aIndex) + , mState(INITIAL) + , mIsDirty(false) + , mRemovingChunk(false) + , mDataSize(0) + , mBuf(nullptr) + , mBufSize(0) + , mRWBuf(nullptr) + , mRWBufSize(0) + , mReadHash(0) + , mFile(aFile) +{ + LOG(("CacheFileChunk::CacheFileChunk() [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileChunk); +} + +CacheFileChunk::~CacheFileChunk() +{ + LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileChunk); + + if (mBuf) { + free(mBuf); + mBuf = nullptr; + mBufSize = 0; + } + + if (mRWBuf) { + free(mRWBuf); + mRWBuf = nullptr; + mRWBufSize = 0; + } + + DoMemoryReport(MemorySize()); +} + +void +CacheFileChunk::InitNew(CacheFileChunkListener *aCallback) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::InitNew() [this=%p, listener=%p]", this, aCallback)); + + MOZ_ASSERT(mState == INITIAL); + MOZ_ASSERT(!mBuf); + MOZ_ASSERT(!mRWBuf); + + mBuf = static_cast(moz_xmalloc(kMinBufSize)); + mBufSize = kMinBufSize; + mDataSize = 0; + mState = READY; + mIsDirty = true; + + DoMemoryReport(MemorySize()); +} + +nsresult +CacheFileChunk::Read(CacheFileHandle *aHandle, uint32_t aLen, + CacheHashUtils::Hash16_t aHash, + CacheFileChunkListener *aCallback) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]", + this, aHandle, aLen, aCallback)); + + MOZ_ASSERT(mState == INITIAL); + MOZ_ASSERT(!mBuf); + MOZ_ASSERT(!mRWBuf); + MOZ_ASSERT(aLen); + + nsresult rv; + + mRWBuf = static_cast(moz_xmalloc(aLen)); + mRWBufSize = aLen; + + DoMemoryReport(MemorySize()); + + rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize, mRWBuf, aLen, + this); + if (NS_FAILED(rv)) { + mState = READING; // TODO: properly handle error states +// mState = ERROR; + NS_ENSURE_SUCCESS(rv, rv); + } + + mState = READING; + mListener = aCallback; + mDataSize = aLen; + mReadHash = aHash; + return NS_OK; +} + +nsresult +CacheFileChunk::Write(CacheFileHandle *aHandle, + CacheFileChunkListener *aCallback) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]", + this, aHandle, aCallback)); + + MOZ_ASSERT(mState == READY); + MOZ_ASSERT(!mRWBuf); + MOZ_ASSERT(mBuf); + MOZ_ASSERT(mDataSize); // Don't write chunk when it is empty + + nsresult rv; + + mRWBuf = mBuf; + mRWBufSize = mBufSize; + mBuf = nullptr; + mBufSize = 0; + + rv = CacheFileIOManager::Write(aHandle, mIndex * kChunkSize, mRWBuf, + mDataSize, false, this); + if (NS_FAILED(rv)) { + mState = WRITING; // TODO: properly handle error states +// mState = ERROR; + NS_ENSURE_SUCCESS(rv, rv); + } + + mState = WRITING; + mListener = aCallback; + mIsDirty = false; + return NS_OK; +} + +void +CacheFileChunk::WaitForUpdate(CacheFileChunkListener *aCallback) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]", + this, aCallback)); + + MOZ_ASSERT(mFile->mOutput); + MOZ_ASSERT(IsReady()); + +#ifdef DEBUG + for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) { + MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback); + } +#endif + + ChunkListenerItem *item = new ChunkListenerItem(); + item->mTarget = NS_GetCurrentThread(); + item->mCallback = aCallback; + + mUpdateListeners.AppendElement(item); +} + +nsresult +CacheFileChunk::CancelWait(CacheFileChunkListener *aCallback) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback)); + + MOZ_ASSERT(IsReady()); + + uint32_t i; + for (i = 0 ; i < mUpdateListeners.Length() ; i++) { + ChunkListenerItem *item = mUpdateListeners[i]; + + if (item->mCallback == aCallback) { + mUpdateListeners.RemoveElementAt(i); + delete item; + break; + } + } + +#ifdef DEBUG + for ( ; i < mUpdateListeners.Length() ; i++) { + MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback); + } +#endif + + return NS_OK; +} + +nsresult +CacheFileChunk::NotifyUpdateListeners() +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this)); + + MOZ_ASSERT(IsReady()); + + nsresult rv, rv2; + + rv = NS_OK; + for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) { + ChunkListenerItem *item = mUpdateListeners[i]; + + LOG(("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p " + "[this=%p]", item->mCallback.get(), this)); + + nsRefPtr ev; + ev = new NotifyUpdateListenerEvent(item->mCallback, this); + rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) + rv = rv2; + delete item; + } + + mUpdateListeners.Clear(); + + return rv; +} + +uint32_t +CacheFileChunk::Index() +{ + return mIndex; +} + +CacheHashUtils::Hash16_t +CacheFileChunk::Hash() +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT(mBuf); + MOZ_ASSERT(!mListener); + MOZ_ASSERT(IsReady()); + + return CacheHashUtils::Hash16(BufForReading(), mDataSize); +} + +uint32_t +CacheFileChunk::DataSize() +{ + mFile->AssertOwnsLock(); + return mDataSize; +} + +void +CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen, bool aEOF) +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT(!aEOF, "Implement me! What to do with opened streams?"); + MOZ_ASSERT(aOffset <= mDataSize); + + LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d, EOF=%d]", + this, aOffset, aLen, aEOF)); + + mIsDirty = true; + + int64_t fileSize = kChunkSize * mIndex + aOffset + aLen; + bool notify = false; + + if (fileSize > mFile->mDataSize) + mFile->mDataSize = fileSize; + + if (aOffset + aLen > mDataSize) { + mDataSize = aOffset + aLen; + notify = true; + } + + if (mState == READY || mState == WRITING) { + MOZ_ASSERT(mValidityMap.Length() == 0); + + if (notify) + NotifyUpdateListeners(); + + return; + } + + // We're still waiting for data from the disk. This chunk cannot be used by + // input stream, so there must be no update listener. We also need to keep + // track of where the data is written so that we can correctly merge the new + // data with the old one. + + MOZ_ASSERT(mUpdateListeners.Length() == 0); + MOZ_ASSERT(mState == READING); + + ValidityPair pair(aOffset, aLen); + + if (mValidityMap.Length() == 0) { + mValidityMap.AppendElement(pair); + return; + } + + + // Find out where to place this pair into the map, it can overlap with + // one preceding pair and all subsequent pairs. + uint32_t pos = 0; + for (pos = mValidityMap.Length() ; pos > 0 ; pos--) { + if (mValidityMap[pos-1].LessThan(pair)) { + if (mValidityMap[pos-1].Overlaps(pair)) { + // Merge with the preceding pair + mValidityMap[pos-1].Merge(pair); + pos--; // Point to the updated pair + } + else { + if (pos == mValidityMap.Length()) + mValidityMap.AppendElement(pair); + else + mValidityMap.InsertElementAt(pos, pair); + } + + break; + } + } + + if (!pos) + mValidityMap.InsertElementAt(0, pair); + + // Now pos points to merged or inserted pair, check whether it overlaps with + // subsequent pairs. + while (pos + 1 < mValidityMap.Length()) { + if (mValidityMap[pos].Overlaps(mValidityMap[pos + 1])) { + mValidityMap[pos].Merge(mValidityMap[pos + 1]); + mValidityMap.RemoveElementAt(pos + 1); + } + else { + break; + } + } +} + +nsresult +CacheFileChunk::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileChunk::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult) +{ + LOG(("CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08x]", + this, aHandle, aResult)); + + nsCOMPtr listener; + + { + CacheFileAutoLock lock(mFile); + + MOZ_ASSERT(mState == WRITING); + MOZ_ASSERT(mListener); + +#if 0 + // TODO: properly handle error states + if (NS_FAILED(aResult)) { + mState = ERROR; + } + else { +#endif + mState = READY; + if (!mBuf) { + mBuf = mRWBuf; + mBufSize = mRWBufSize; + } + else { + free(mRWBuf); + } + + mRWBuf = nullptr; + mRWBufSize = 0; + + DoMemoryReport(MemorySize()); +#if 0 + } +#endif + + mListener.swap(listener); + } + + listener->OnChunkWritten(aResult, this); + + return NS_OK; +} + +nsresult +CacheFileChunk::OnDataRead(CacheFileHandle *aHandle, char *aBuf, + nsresult aResult) +{ + LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08x]", + this, aHandle, aResult)); + + nsCOMPtr listener; + + { + CacheFileAutoLock lock(mFile); + + MOZ_ASSERT(mState == READING); + MOZ_ASSERT(mListener); + + if (NS_SUCCEEDED(aResult)) { + CacheHashUtils::Hash16_t hash = CacheHashUtils::Hash16(mRWBuf, + mRWBufSize); + if (hash != mReadHash) { + LOG(("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is" + " %hx, hash in metadata is %hx. [this=%p, idx=%d]", + hash, mReadHash, this, mIndex)); + aResult = NS_ERROR_FILE_CORRUPTED; + } + else { + if (!mBuf) { + // Just swap the buffers if we don't have mBuf yet + MOZ_ASSERT(mDataSize == mRWBufSize); + mBuf = mRWBuf; + mBufSize = mRWBufSize; + mRWBuf = nullptr; + mRWBufSize = 0; + } else { + // Merge data with write buffer + if (mRWBufSize < mBufSize) { + mRWBuf = static_cast(moz_xrealloc(mRWBuf, mBufSize)); + mRWBufSize = mBufSize; + } + + for (uint32_t i = 0 ; i < mValidityMap.Length() ; i++) { + memcpy(mRWBuf + mValidityMap[i].Offset(), + mBuf + mValidityMap[i].Offset(), mValidityMap[i].Len()); + } + + free(mBuf); + mBuf = mRWBuf; + mBufSize = mRWBufSize; + mRWBuf = nullptr; + mRWBufSize = 0; + + DoMemoryReport(MemorySize()); + } + } + } + + if (NS_FAILED(aResult)) { +#if 0 + // TODO: properly handle error states + mState = ERROR; +#endif + mState = READY; + mDataSize = 0; + } + else { + mState = READY; + } + + mListener.swap(listener); + } + + listener->OnChunkRead(aResult, this); + + return NS_OK; +} + +nsresult +CacheFileChunk::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileChunk::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +bool +CacheFileChunk::IsReady() +{ + mFile->AssertOwnsLock(); + + return (mState == READY || mState == WRITING); +} + +bool +CacheFileChunk::IsDirty() +{ + mFile->AssertOwnsLock(); + + return mIsDirty; +} + +char * +CacheFileChunk::BufForWriting() +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT(mBuf); // Writer should always first call EnsureBufSize() + + MOZ_ASSERT((mState == READY && !mRWBuf) || + (mState == WRITING && mRWBuf) || + (mState == READING && mRWBuf)); + + return mBuf; +} + +const char * +CacheFileChunk::BufForReading() +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT((mState == READY && mBuf && !mRWBuf) || + (mState == WRITING && mRWBuf)); + + return mBuf ? mBuf : mRWBuf; +} + +void +CacheFileChunk::EnsureBufSize(uint32_t aBufSize) +{ + mFile->AssertOwnsLock(); + + if (mBufSize >= aBufSize) + return; + + bool copy = false; + if (!mBuf && mState == WRITING) { + // We need to duplicate the data that is being written on the background + // thread, so make sure that all the data fits into the new buffer. + copy = true; + + if (mRWBufSize > aBufSize) + aBufSize = mRWBufSize; + } + + // find smallest power of 2 greater than or equal to aBufSize + aBufSize--; + aBufSize |= aBufSize >> 1; + aBufSize |= aBufSize >> 2; + aBufSize |= aBufSize >> 4; + aBufSize |= aBufSize >> 8; + aBufSize |= aBufSize >> 16; + aBufSize++; + + const uint32_t minBufSize = kMinBufSize; + const uint32_t maxBufSize = kChunkSize; + aBufSize = clamped(aBufSize, minBufSize, maxBufSize); + + mBuf = static_cast(moz_xrealloc(mBuf, aBufSize)); + mBufSize = aBufSize; + + if (copy) + memcpy(mBuf, mRWBuf, mRWBufSize); + + DoMemoryReport(MemorySize()); +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFileChunk.h b/netwerk/cache2/CacheFileChunk.h new file mode 100644 index 000000000000..5368f237a0b1 --- /dev/null +++ b/netwerk/cache2/CacheFileChunk.h @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFileChunk__h__ +#define CacheFileChunk__h__ + +#include "CacheFileIOManager.h" +#include "CacheStorageService.h" +#include "CacheHashUtils.h" +#include "nsAutoPtr.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +#define kChunkSize 16384 +#define kEmptyChunkHash 0xA8CA + +class CacheFileChunk; +class CacheFile; +class ValidityPair; + + +#define CACHEFILECHUNKLISTENER_IID \ +{ /* baf16149-2ab5-499c-a9c2-5904eb95c288 */ \ + 0xbaf16149, \ + 0x2ab5, \ + 0x499c, \ + {0xa9, 0xc2, 0x59, 0x04, 0xeb, 0x95, 0xc2, 0x88} \ +} + +class CacheFileChunkListener : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILECHUNKLISTENER_IID) + + NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) = 0; + NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) = 0; + NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, + CacheFileChunk *aChunk) = 0; + NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileChunkListener, + CACHEFILECHUNKLISTENER_IID) + + +class ChunkListenerItem { +public: + ChunkListenerItem() { MOZ_COUNT_CTOR(ChunkListenerItem); } + ~ChunkListenerItem() { MOZ_COUNT_DTOR(ChunkListenerItem); } + + nsCOMPtr mTarget; + nsCOMPtr mCallback; +}; + +class ChunkListeners { +public: + ChunkListeners() { MOZ_COUNT_CTOR(ChunkListeners); } + ~ChunkListeners() { MOZ_COUNT_DTOR(ChunkListeners); } + + nsTArray mItems; +}; + +class CacheFileChunk : public CacheFileIOListener + , public CacheMemoryConsumer +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + CacheFileChunk(CacheFile *aFile, uint32_t aIndex); + + void InitNew(CacheFileChunkListener *aCallback); + nsresult Read(CacheFileHandle *aHandle, uint32_t aLen, + CacheHashUtils::Hash16_t aHash, + CacheFileChunkListener *aCallback); + nsresult Write(CacheFileHandle *aHandle, CacheFileChunkListener *aCallback); + void WaitForUpdate(CacheFileChunkListener *aCallback); + nsresult CancelWait(CacheFileChunkListener *aCallback); + nsresult NotifyUpdateListeners(); + + uint32_t Index(); + CacheHashUtils::Hash16_t Hash(); + uint32_t DataSize(); + void UpdateDataSize(uint32_t aOffset, uint32_t aLen, + bool aEOF); + + NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult); + NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult); + NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult); + + bool IsReady(); + bool IsDirty(); + + char * BufForWriting(); + const char * BufForReading(); + void EnsureBufSize(uint32_t aBufSize); + uint32_t MemorySize() { return mRWBufSize + mBufSize; } + +private: + friend class CacheFileInputStream; + friend class CacheFileOutputStream; + friend class CacheFile; + + virtual ~CacheFileChunk(); + + enum EState { + INITIAL = 0, + READING = 1, + WRITING = 2, + READY = 3, + ERROR = 4 + }; + + uint32_t mIndex; + EState mState; + bool mIsDirty; + bool mRemovingChunk; + uint32_t mDataSize; + + char *mBuf; + uint32_t mBufSize; + + char *mRWBuf; + uint32_t mRWBufSize; + CacheHashUtils::Hash16_t mReadHash; + + nsRefPtr mFile; // is null if chunk is cached to + // prevent reference cycles + nsCOMPtr mListener; + nsTArray mUpdateListeners; + nsTArray mValidityMap; +}; + + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp new file mode 100644 index 000000000000..4a5a6a29c065 --- /dev/null +++ b/netwerk/cache2/CacheFileIOManager.cpp @@ -0,0 +1,1780 @@ +/* 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 "CacheFileIOManager.h" + +#include "CacheLog.h" +#include "../cache/nsCacheUtils.h" +#include "CacheHashUtils.h" +#include "CacheStorageService.h" +#include "nsThreadUtils.h" +#include "nsIFile.h" +#include "mozilla/Telemetry.h" +#include "mozilla/DebugOnly.h" +#include "nsDirectoryServiceUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "private/pprio.h" +#include "mozilla/VisualEventTracer.h" + +// include files for ftruncate (or equivalent) +#if defined(XP_UNIX) +#include +#elif defined(XP_WIN) +#include +#undef CreateFile +#undef CREATE_NEW +#elif defined(XP_OS2) +#define INCL_DOSERRORS +#include +#else +// XXX add necessary include file for ftruncate (or equivalent) +#endif + + +namespace mozilla { +namespace net { + +#define kOpenHandlesLimit 64 + + +NS_IMPL_ADDREF(CacheFileHandle) +NS_IMETHODIMP_(nsrefcnt) +CacheFileHandle::Release() +{ + LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get())); + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "CacheFileHandle"); + + if (0 == count) { + mRefCnt = 1; + delete (this); + return 0; + } + + if (!mRemovingHandle && count == 1 && !mClosed) { + CacheFileIOManager::gInstance->CloseHandle(this); + } + + return count; +} + +NS_INTERFACE_MAP_BEGIN(CacheFileHandle) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END_THREADSAFE + +CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, + bool aPriority) + : mHash(aHash) + , mIsDoomed(false) + , mRemovingHandle(false) + , mPriority(aPriority) + , mClosed(false) + , mInvalid(false) + , mFileExists(false) + , mFileSize(-1) + , mFD(nullptr) +{ + LOG(("CacheFileHandle::CacheFileHandle() [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileHandle); + PR_INIT_CLIST(this); +} + +CacheFileHandle::~CacheFileHandle() +{ + LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileHandle); +} + + +/****************************************************************************** + * CacheFileHandles + *****************************************************************************/ + +class CacheFileHandlesEntry : public PLDHashEntryHdr +{ +public: + PRCList *mHandles; + SHA1Sum::Hash mHash; +}; + +PLDHashTableOps CacheFileHandles::mOps = +{ + PL_DHashAllocTable, + PL_DHashFreeTable, + HashKey, + MatchEntry, + MoveEntry, + ClearEntry, + PL_DHashFinalizeStub +}; + +CacheFileHandles::CacheFileHandles() + : mInitialized(false) +{ + LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileHandles); +} + +CacheFileHandles::~CacheFileHandles() +{ + LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileHandles); + + if (mInitialized) + Shutdown(); +} + +nsresult +CacheFileHandles::Init() +{ + LOG(("CacheFileHandles::Init() %p", this)); + + MOZ_ASSERT(!mInitialized); + mInitialized = PL_DHashTableInit(&mTable, &mOps, nullptr, + sizeof(CacheFileHandlesEntry), 512); + + return mInitialized ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +void +CacheFileHandles::Shutdown() +{ + LOG(("CacheFileHandles::Shutdown() %p", this)); + + if (mInitialized) { + PL_DHashTableFinish(&mTable); + mInitialized = false; + } +} + +PLDHashNumber +CacheFileHandles::HashKey(PLDHashTable *table, const void *key) +{ + const SHA1Sum::Hash *hash = static_cast(key); + return static_cast(((*hash)[0] << 24) | ((*hash)[1] << 16) | + ((*hash)[2] << 8) | (*hash)[3]); +} + +bool +CacheFileHandles::MatchEntry(PLDHashTable *table, + const PLDHashEntryHdr *header, + const void *key) +{ + const CacheFileHandlesEntry *entry; + + entry = static_cast(header); + + return (memcmp(&entry->mHash, key, sizeof(SHA1Sum::Hash)) == 0); +} + +void +CacheFileHandles::MoveEntry(PLDHashTable *table, + const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + const CacheFileHandlesEntry *src; + CacheFileHandlesEntry *dst; + + src = static_cast(from); + dst = static_cast(to); + + dst->mHandles = src->mHandles; + memcpy(&dst->mHash, &src->mHash, sizeof(SHA1Sum::Hash)); + + LOG(("CacheFileHandles::MoveEntry() hash=%08x%08x%08x%08x%08x " + "moving from %p to %p", LOGSHA1(src->mHash), from, to)); + + // update pointer to mHash in all handles + CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(dst->mHandles); + while (handle != dst->mHandles) { + handle->mHash = &dst->mHash; + handle = (CacheFileHandle *)PR_NEXT_LINK(handle); + } +} + +void +CacheFileHandles::ClearEntry(PLDHashTable *table, + PLDHashEntryHdr *header) +{ + CacheFileHandlesEntry *entry = static_cast(header); + delete entry->mHandles; + entry->mHandles = nullptr; +} + +nsresult +CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, + CacheFileHandle **_retval) +{ + MOZ_ASSERT(mInitialized); + + // find hash entry for key + CacheFileHandlesEntry *entry; + entry = static_cast( + PL_DHashTableOperate(&mTable, + (void *)aHash, + PL_DHASH_LOOKUP)); + if (PL_DHASH_ENTRY_IS_FREE(entry)) { + LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " + "no handle found", LOGSHA1(aHash))); + return NS_ERROR_NOT_AVAILABLE; + } + + // Check if the entry is doomed + CacheFileHandle *handle = static_cast( + PR_LIST_HEAD(entry->mHandles)); + if (handle->IsDoomed()) { + LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " + "found doomed handle %p, entry %p", LOGSHA1(aHash), handle, entry)); + return NS_ERROR_NOT_AVAILABLE; + } + + LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " + "found handle %p, entry %p", LOGSHA1(aHash), handle, entry)); + NS_ADDREF(*_retval = handle); + return NS_OK; +} + + +nsresult +CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash, + bool aPriority, + CacheFileHandle **_retval) +{ + MOZ_ASSERT(mInitialized); + + // find hash entry for key + CacheFileHandlesEntry *entry; + entry = static_cast( + PL_DHashTableOperate(&mTable, + (void *)aHash, + PL_DHASH_ADD)); + if (!entry) return NS_ERROR_OUT_OF_MEMORY; + + if (!entry->mHandles) { + // new entry + entry->mHandles = new PRCList; + memcpy(&entry->mHash, aHash, sizeof(SHA1Sum::Hash)); + PR_INIT_CLIST(entry->mHandles); + + LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x " + "created new entry %p, list %p", LOGSHA1(aHash), entry, + entry->mHandles)); + } +#ifdef DEBUG + else { + MOZ_ASSERT(!PR_CLIST_IS_EMPTY(entry->mHandles)); + CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles); + MOZ_ASSERT(handle->IsDoomed()); + } +#endif + + nsRefPtr handle = new CacheFileHandle(&entry->mHash, aPriority); + PR_APPEND_LINK(handle, entry->mHandles); + + LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x " + "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry)); + + NS_ADDREF(*_retval = handle); + handle.forget(); + return NS_OK; +} + +void +CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle) +{ + MOZ_ASSERT(mInitialized); + + void *key = (void *)aHandle->Hash(); + + CacheFileHandlesEntry *entry; + entry = static_cast( + PL_DHashTableOperate(&mTable, key, PL_DHASH_LOOKUP)); + + MOZ_ASSERT(PL_DHASH_ENTRY_IS_BUSY(entry)); + +#ifdef DEBUG + { + CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles); + bool handleFound = false; + while (handle != entry->mHandles) { + if (handle == aHandle) { + handleFound = true; + break; + } + handle = (CacheFileHandle *)PR_NEXT_LINK(handle); + } + MOZ_ASSERT(handleFound); + } +#endif + + LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " + "removing handle %p", LOGSHA1(&entry->mHash), aHandle)); + + PR_REMOVE_AND_INIT_LINK(aHandle); + NS_RELEASE(aHandle); + + if (PR_CLIST_IS_EMPTY(entry->mHandles)) { + LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " + "list %p is empty, removing entry %p", LOGSHA1(&entry->mHash), + entry->mHandles, entry)); + PL_DHashTableOperate(&mTable, key, PL_DHASH_REMOVE); + } +} + +PLDHashOperator +GetHandles(PLDHashTable *table, + PLDHashEntryHdr *hdr, + uint32_t number, + void * arg) +{ + const CacheFileHandlesEntry *entry; + nsTArray > *handles; + + entry = static_cast(hdr); + handles = reinterpret_cast > *>(arg); + + CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(entry->mHandles); + while (handle != entry->mHandles) { + handles->AppendElement(handle); + handle = (CacheFileHandle *)PR_NEXT_LINK(handle); + } + + return PL_DHASH_NEXT; +} + +void +CacheFileHandles::GetAllHandles(nsTArray > *_retval) +{ + MOZ_ASSERT(mInitialized); + + PL_DHashTableEnumerate(&mTable, GetHandles, _retval); +} + +uint32_t +CacheFileHandles::HandleCount() +{ + MOZ_ASSERT(mInitialized); + + return mTable.entryCount; +} + +// Events + +class ShutdownEvent : public nsRunnable { +public: + ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar) + : mLock(aLock) + , mCondVar(aCondVar) + { + MOZ_COUNT_CTOR(ShutdownEvent); + } + + ~ShutdownEvent() + { + MOZ_COUNT_DTOR(ShutdownEvent); + } + + NS_IMETHOD Run() + { + MutexAutoLock lock(*mLock); + + CacheFileIOManager::gInstance->ShutdownInternal(); + + mCondVar->Notify(); + return NS_OK; + } + +protected: + mozilla::Mutex *mLock; + mozilla::CondVar *mCondVar; +}; + +class OpenFileEvent : public nsRunnable { +public: + OpenFileEvent(const nsACString &aKey, + uint32_t aFlags, + CacheFileIOListener *aCallback) + : mFlags(aFlags) + , mCallback(aCallback) + , mRV(NS_ERROR_FAILURE) + , mKey(aKey) + { + MOZ_COUNT_CTOR(OpenFileEvent); + + mTarget = static_cast(NS_GetCurrentThread()); + mIOMan = CacheFileIOManager::gInstance; + MOZ_ASSERT(mTarget); + + MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aKey.BeginReading()); + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::open-background"); + } + + ~OpenFileEvent() + { + MOZ_COUNT_DTOR(OpenFileEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + mRV = NS_OK; + + if (mFlags & CacheFileIOManager::NOHASH) { + nsACString::const_char_iterator begin, end; + begin = mKey.BeginReading(); + end = mKey.EndReading(); + uint32_t i = 0; + while (begin != end && i < (SHA1Sum::HashSize << 1)) { + if (!(i & 1)) + mHash[i >> 1] = 0; + uint8_t shift = (i & 1) ? 0 : 4; + if (*begin >= '0' && *begin <= '9') + mHash[i >> 1] |= (*begin - '0') << shift; + else if (*begin >= 'A' && *begin <= 'F') + mHash[i >> 1] |= (*begin - 'A' + 10) << shift; + else + break; + + ++i; + ++begin; + } + + if (i != (SHA1Sum::HashSize << 1) || begin != end) + mRV = NS_ERROR_INVALID_ARG; + } + else { + SHA1Sum sum; + sum.update(mKey.BeginReading(), mKey.Length()); + sum.finish(mHash); + } + + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::open-background"); + if (NS_SUCCEEDED(mRV)) { + if (!mIOMan) + mRV = NS_ERROR_NOT_INITIALIZED; + else { + mRV = mIOMan->OpenFileInternal(&mHash, mFlags, getter_AddRefs(mHandle)); + mIOMan = nullptr; + if (mHandle) { + MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get()); + mHandle->Key() = mKey; + } + } + } + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::open-background"); + + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::open-result"); + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::open-result"); + mCallback->OnFileOpened(mHandle, mRV); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::open-result"); + } + return NS_OK; + } + +protected: + SHA1Sum::Hash mHash; + uint32_t mFlags; + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsRefPtr mIOMan; + nsRefPtr mHandle; + nsresult mRV; + nsCString mKey; +}; + +class CloseHandleEvent : public nsRunnable { +public: + CloseHandleEvent(CacheFileHandle *aHandle) + : mHandle(aHandle) + { + MOZ_COUNT_CTOR(CloseHandleEvent); + } + + ~CloseHandleEvent() + { + MOZ_COUNT_DTOR(CloseHandleEvent); + } + + NS_IMETHOD Run() + { + if (!mHandle->IsClosed()) + CacheFileIOManager::gInstance->CloseHandleInternal(mHandle); + return NS_OK; + } + +protected: + nsRefPtr mHandle; +}; + +class ReadEvent : public nsRunnable { +public: + ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf, + int32_t aCount, CacheFileIOListener *aCallback) + : mHandle(aHandle) + , mOffset(aOffset) + , mBuf(aBuf) + , mCount(aCount) + , mCallback(aCallback) + , mRV(NS_ERROR_FAILURE) + { + MOZ_COUNT_CTOR(ReadEvent); + mTarget = static_cast(NS_GetCurrentThread()); + + MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::read-background"); + } + + ~ReadEvent() + { + MOZ_COUNT_DTOR(ReadEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::read-background"); + if (mHandle->IsClosed()) + mRV = NS_ERROR_NOT_INITIALIZED; + else + mRV = CacheFileIOManager::gInstance->ReadInternal( + mHandle, mOffset, mBuf, mCount); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::read-background"); + + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::read-result"); + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::read-result"); + if (mCallback) + mCallback->OnDataRead(mHandle, mBuf, mRV); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::read-result"); + } + return NS_OK; + } + +protected: + nsRefPtr mHandle; + int64_t mOffset; + char *mBuf; + int32_t mCount; + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsresult mRV; +}; + +class WriteEvent : public nsRunnable { +public: + WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf, + int32_t aCount, bool aValidate, CacheFileIOListener *aCallback) + : mHandle(aHandle) + , mOffset(aOffset) + , mBuf(aBuf) + , mCount(aCount) + , mValidate(aValidate) + , mCallback(aCallback) + , mRV(NS_ERROR_FAILURE) + { + MOZ_COUNT_CTOR(WriteEvent); + mTarget = static_cast(NS_GetCurrentThread()); + + MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::write-background"); + } + + ~WriteEvent() + { + MOZ_COUNT_DTOR(WriteEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::write-background"); + if (mHandle->IsClosed()) + mRV = NS_ERROR_NOT_INITIALIZED; + else + mRV = CacheFileIOManager::gInstance->WriteInternal( + mHandle, mOffset, mBuf, mCount, mValidate); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::write-background"); + + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::write-result"); + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::write-result"); + if (mCallback) + mCallback->OnDataWritten(mHandle, mBuf, mRV); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::write-result"); + } + return NS_OK; + } + +protected: + nsRefPtr mHandle; + int64_t mOffset; + const char *mBuf; + int32_t mCount; + bool mValidate; + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsresult mRV; +}; + +class DoomFileEvent : public nsRunnable { +public: + DoomFileEvent(CacheFileHandle *aHandle, + CacheFileIOListener *aCallback) + : mCallback(aCallback) + , mHandle(aHandle) + , mRV(NS_ERROR_FAILURE) + { + MOZ_COUNT_CTOR(DoomFileEvent); + mTarget = static_cast(NS_GetCurrentThread()); + + MOZ_EVENT_TRACER_NAME_OBJECT(static_cast(this), aHandle->Key().get()); + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::doom-background"); + } + + ~DoomFileEvent() + { + MOZ_COUNT_DTOR(DoomFileEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::doom-background"); + if (mHandle->IsClosed()) + mRV = NS_ERROR_NOT_INITIALIZED; + else + mRV = CacheFileIOManager::gInstance->DoomFileInternal(mHandle); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::doom-background"); + + MOZ_EVENT_TRACER_WAIT(static_cast(this), "net::cache::doom-result"); + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + MOZ_EVENT_TRACER_EXEC(static_cast(this), "net::cache::doom-result"); + if (mCallback) + mCallback->OnFileDoomed(mHandle, mRV); + MOZ_EVENT_TRACER_DONE(static_cast(this), "net::cache::doom-result"); + } + return NS_OK; + } + +protected: + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsRefPtr mHandle; + nsresult mRV; +}; + +class DoomFileByKeyEvent : public nsRunnable { +public: + DoomFileByKeyEvent(const nsACString &aKey, + CacheFileIOListener *aCallback) + : mCallback(aCallback) + , mRV(NS_ERROR_FAILURE) + { + MOZ_COUNT_CTOR(DoomFileByKeyEvent); + + SHA1Sum sum; + sum.update(aKey.BeginReading(), aKey.Length()); + sum.finish(mHash); + + mTarget = static_cast(NS_GetCurrentThread()); + mIOMan = CacheFileIOManager::gInstance; + MOZ_ASSERT(mTarget); + } + + ~DoomFileByKeyEvent() + { + MOZ_COUNT_DTOR(DoomFileByKeyEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + if (!mIOMan) + mRV = NS_ERROR_NOT_INITIALIZED; + else { + mRV = mIOMan->DoomFileByKeyInternal(&mHash); + mIOMan = nullptr; + } + + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + if (mCallback) + mCallback->OnFileDoomed(nullptr, mRV); + } + return NS_OK; + } + +protected: + SHA1Sum::Hash mHash; + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsRefPtr mIOMan; + nsresult mRV; +}; + +class ReleaseNSPRHandleEvent : public nsRunnable { +public: + ReleaseNSPRHandleEvent(CacheFileHandle *aHandle) + : mHandle(aHandle) + { + MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent); + } + + ~ReleaseNSPRHandleEvent() + { + MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent); + } + + NS_IMETHOD Run() + { + if (mHandle->mFD && !mHandle->IsClosed()) + CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle); + + return NS_OK; + } + +protected: + nsRefPtr mHandle; +}; + +class TruncateSeekSetEOFEvent : public nsRunnable { +public: + TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos, + int64_t aEOFPos, CacheFileIOListener *aCallback) + : mHandle(aHandle) + , mTruncatePos(aTruncatePos) + , mEOFPos(aEOFPos) + , mCallback(aCallback) + , mRV(NS_ERROR_FAILURE) + { + MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent); + mTarget = static_cast(NS_GetCurrentThread()); + } + + ~TruncateSeekSetEOFEvent() + { + MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent); + } + + NS_IMETHOD Run() + { + if (mTarget) { + if (mHandle->IsClosed()) + mRV = NS_ERROR_NOT_INITIALIZED; + else + mRV = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal( + mHandle, mTruncatePos, mEOFPos); + + nsCOMPtr target; + mTarget.swap(target); + target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); + } + else { + if (mCallback) + mCallback->OnEOFSet(mHandle, mRV); + } + return NS_OK; + } + +protected: + nsRefPtr mHandle; + int64_t mTruncatePos; + int64_t mEOFPos; + nsCOMPtr mCallback; + nsCOMPtr mTarget; + nsresult mRV; +}; + + +CacheFileIOManager * CacheFileIOManager::gInstance = nullptr; + +NS_IMPL_ISUPPORTS0(CacheFileIOManager) + +CacheFileIOManager::CacheFileIOManager() + : mShuttingDown(false) + , mTreeCreated(false) +{ + LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileIOManager); + MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!"); +} + +CacheFileIOManager::~CacheFileIOManager() +{ + LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileIOManager); +} + +nsresult +CacheFileIOManager::Init() +{ + LOG(("CacheFileIOManager::Init()")); + + MOZ_ASSERT(NS_IsMainThread()); + + if (gInstance) + return NS_ERROR_ALREADY_INITIALIZED; + + nsRefPtr ioMan = new CacheFileIOManager(); + + nsresult rv = ioMan->InitInternal(); + NS_ENSURE_SUCCESS(rv, rv); + + ioMan.swap(gInstance); + return NS_OK; +} + +nsresult +CacheFileIOManager::InitInternal() +{ + nsresult rv; + + mIOThread = new CacheIOThread(); + + rv = mIOThread->Init(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mHandles.Init(); + if (NS_FAILED(rv)) { + mIOThread->Shutdown(); + + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +CacheFileIOManager::Shutdown() +{ + LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance)); + + MOZ_ASSERT(NS_IsMainThread()); + + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + { + mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock"); + mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar"); + + MutexAutoLock autoLock(lock); + nsRefPtr ev = new ShutdownEvent(&lock, &condVar); + DebugOnly rv; + rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + condVar.Wait(); + } + + MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0); + MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0); + + nsRefPtr ioMan; + ioMan.swap(gInstance); + + if (ioMan->mIOThread) + ioMan->mIOThread->Shutdown(); + + return NS_OK; +} + +nsresult +CacheFileIOManager::ShutdownInternal() +{ + mShuttingDown = true; + + // close all handles and delete all associated files + nsTArray > handles; + mHandles.GetAllHandles(&handles); + + for (uint32_t i=0 ; imRemovingHandle = true; + h->mClosed = true; + + // Close file handle + if (h->mFD) { + ReleaseNSPRHandleInternal(h); + } + + // Remove file if entry is doomed or invalid + if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) { + h->mFile->Remove(false); + } + + // Remove the handle from hashtable + mHandles.RemoveHandle(h); + } + + return NS_OK; +} + +nsresult +CacheFileIOManager::OnProfile() +{ + LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance)); + + MOZ_ASSERT(gInstance); + + nsresult rv; + + nsCOMPtr directory; + +#if defined(MOZ_WIDGET_ANDROID) + char* cachePath = getenv("CACHE_DIRECTORY"); + if (cachePath && *cachePath) { + rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), + true, getter_AddRefs(directory)); + } +#endif + + if (!directory) { + rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, + getter_AddRefs(directory)); + } + + if (!directory) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(directory)); + } + + if (directory) { + rv = directory->Clone(getter_AddRefs(gInstance->mCacheDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = gInstance->mCacheDirectory->Append(NS_LITERAL_STRING("cache2")); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// static +already_AddRefed +CacheFileIOManager::IOTarget() +{ + nsCOMPtr target; + if (gInstance && gInstance->mIOThread) + target = gInstance->mIOThread->Target(); + + return target.forget(); +} + +already_AddRefed +CacheFileIOManager::IOThread() +{ + nsRefPtr thread; + if (gInstance) + thread = gInstance->mIOThread; + + return thread.forget(); +} + +nsresult +CacheFileIOManager::OpenFile(const nsACString &aKey, + uint32_t aFlags, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]", + PromiseFlatCString(aKey).get(), aFlags, aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (!ioMan) + return NS_ERROR_NOT_INITIALIZED; + + bool priority = aFlags & CacheFileIOManager::PRIORITY; + nsRefPtr ev = new OpenFileEvent(aKey, aFlags, aCallback); + rv = ioMan->mIOThread->Dispatch(ev, priority + ? CacheIOThread::OPEN_PRIORITY + : CacheIOThread::OPEN); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash, + uint32_t aFlags, + CacheFileHandle **_retval) +{ + nsresult rv; + + if (mShuttingDown) + return NS_ERROR_NOT_INITIALIZED; + + if (!mTreeCreated) { + rv = CreateCacheTree(); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr file; + rv = GetFile(aHash, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr handle; + mHandles.GetHandle(aHash, getter_AddRefs(handle)); + + if (aFlags == CREATE_NEW) { + if (handle) { + rv = DoomFileInternal(handle); + NS_ENSURE_SUCCESS(rv, rv); + handle = nullptr; + } + + rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + rv = file->Remove(false); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot remove old entry from the disk"); + // TODO log + } + } + + handle->mFile.swap(file); + handle->mFileSize = 0; + } + + if (handle) { + handle.swap(*_retval); + return NS_OK; + } + + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists && aFlags == OPEN) + return NS_ERROR_NOT_AVAILABLE; + + rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + rv = file->GetFileSize(&handle->mFileSize); + NS_ENSURE_SUCCESS(rv, rv); + + handle->mFileExists = true; + } + else { + handle->mFileSize = 0; + } + + handle->mFile.swap(file); + handle.swap(*_retval); + return NS_OK; +} + +nsresult +CacheFileIOManager::CloseHandle(CacheFileHandle *aHandle) +{ + LOG(("CacheFileIOManager::CloseHandle() [handle=%p]", aHandle)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new CloseHandleEvent(aHandle); + rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle) +{ + // This method should be called only from CloseHandleEvent. If this handle is + // still unused then mRefCnt should be 2 (reference in hashtable and in + // CacheHandleEvent) + + if (aHandle->mRefCnt != 2) { + // someone got this handle between calls to CloseHandle() and + // CloseHandleInternal() + return NS_OK; + } + + // We're going to remove this handle, don't call CloseHandle() again + aHandle->mRemovingHandle = true; + + // Close file handle + if (aHandle->mFD) { + ReleaseNSPRHandleInternal(aHandle); + } + + // If the entry was doomed delete the file + if (aHandle->IsDoomed()) { + aHandle->mFile->Remove(false); + } + + // Remove the handle from hashtable + mHandles.RemoveHandle(aHandle); + + return NS_OK; +} + +nsresult +CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset, + char *aBuf, int32_t aCount, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, " + "listener=%p]", aHandle, aOffset, aCount, aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new ReadEvent(aHandle, aOffset, aBuf, aCount, + aCallback); + rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() + ? CacheIOThread::READ_PRIORITY + : CacheIOThread::READ); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset, + char *aBuf, int32_t aCount) +{ + nsresult rv; + + if (!aHandle->mFileExists) { + NS_WARNING("Trying to read from non-existent file"); + return NS_ERROR_NOT_AVAILABLE; + } + + if (!aHandle->mFD) { + rv = OpenNSPRHandle(aHandle); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + NSPRHandleUsed(aHandle); + } + + // Check again, OpenNSPRHandle could figure out the file was gone. + if (!aHandle->mFileExists) { + NS_WARNING("Trying to read from non-existent file"); + return NS_ERROR_NOT_AVAILABLE; + } + + int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); + if (offset == -1) + return NS_ERROR_FAILURE; + + int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount); + if (bytesRead != aCount) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset, + const char *aBuf, int32_t aCount, bool aValidate, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, " + "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate, + aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new WriteEvent(aHandle, aOffset, aBuf, aCount, + aValidate, aCallback); + rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset, + const char *aBuf, int32_t aCount, + bool aValidate) +{ + nsresult rv; + + if (!aHandle->mFileExists) { + rv = CreateFile(aHandle); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!aHandle->mFD) { + rv = OpenNSPRHandle(aHandle); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + NSPRHandleUsed(aHandle); + } + + // Check again, OpenNSPRHandle could figure out the file was gone. + if (!aHandle->mFileExists) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Write invalidates the entry by default + aHandle->mInvalid = true; + + int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET); + if (offset == -1) + return NS_ERROR_FAILURE; + + int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount); + + if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten) + aHandle->mFileSize = aOffset+bytesWritten; + + if (bytesWritten != aCount) + return NS_ERROR_FAILURE; + + // Write was successful and this write validates the entry (i.e. metadata) + if (aValidate) + aHandle->mInvalid = false; + + return NS_OK; +} + +nsresult +CacheFileIOManager::DoomFile(CacheFileHandle *aHandle, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", + aHandle, aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new DoomFileEvent(aHandle, aCallback); + rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority() + ? CacheIOThread::DOOM_PRIORITY + : CacheIOThread::DOOM); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle) +{ + nsresult rv; + + if (aHandle->IsDoomed()) + return NS_OK; + + if (aHandle->mFileExists) { + // we need to move the current file to the doomed directory + if (aHandle->mFD) + ReleaseNSPRHandleInternal(aHandle); + + // find unused filename + nsCOMPtr file; + rv = GetDoomedFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr parentDir; + rv = file->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString leafName; + rv = file->GetNativeLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aHandle->mFile->MoveToNative(parentDir, leafName); + if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) { + LOG((" file already removed under our hands")); + aHandle->mFileExists = false; + rv = NS_OK; + } + else { + NS_ENSURE_SUCCESS(rv, rv); + aHandle->mFile.swap(file); + } + } + + aHandle->mIsDoomed = true; + return NS_OK; +} + +nsresult +CacheFileIOManager::DoomFileByKey(const nsACString &aKey, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]", + PromiseFlatCString(aKey).get(), aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (!ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new DoomFileByKeyEvent(aKey, aCallback); + rv = ioMan->mIOThread->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash) +{ + nsresult rv; + + if (mShuttingDown) + return NS_ERROR_NOT_INITIALIZED; + + if (!mCacheDirectory) + return NS_ERROR_FILE_INVALID_PATH; + + // Find active handle + nsRefPtr handle; + mHandles.GetHandle(aHash, getter_AddRefs(handle)); + + if (handle) { + if (handle->IsDoomed()) + return NS_OK; + + return DoomFileInternal(handle); + } + + // There is no handle for this file, delete the file if exists + nsCOMPtr file; + rv = GetFile(aHash, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) + return NS_ERROR_NOT_AVAILABLE; + + rv = file->Remove(false); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot remove old entry from the disk"); + // TODO log + } + + return NS_OK; +} + +nsresult +CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle) +{ + LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new ReleaseNSPRHandleEvent(aHandle); + rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle) +{ + MOZ_ASSERT(aHandle->mFD); + + DebugOnly found; + found = mHandlesByLastUsed.RemoveElement(aHandle); + MOZ_ASSERT(found); + + PR_Close(aHandle->mFD); + aHandle->mFD = nullptr; + + return NS_OK; +} + +nsresult +CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle, + int64_t aTruncatePos, int64_t aEOFPos, + CacheFileIOListener *aCallback) +{ + LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, " + "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (aHandle->IsClosed() || !ioMan) + return NS_ERROR_NOT_INITIALIZED; + + nsRefPtr ev = new TruncateSeekSetEOFEvent( + aHandle, aTruncatePos, aEOFPos, + aCallback); + rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::EnumerateEntryFiles(EEnumerateMode aMode, + CacheEntriesEnumerator** aEnumerator) +{ + LOG(("CacheFileIOManager::EnumerateEntryFiles(%d)", aMode)); + + nsresult rv; + CacheFileIOManager *ioMan = gInstance; + + if (!ioMan) + return NS_ERROR_NOT_INITIALIZED; + + if (!ioMan->mCacheDirectory) + return NS_ERROR_FILE_NOT_FOUND; + + nsCOMPtr file; + rv = ioMan->mCacheDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (aMode) { + case ENTRIES: + rv = file->AppendNative(NS_LITERAL_CSTRING("entries")); + NS_ENSURE_SUCCESS(rv, rv); + + break; + + case DOOMED: + rv = file->AppendNative(NS_LITERAL_CSTRING("doomed")); + NS_ENSURE_SUCCESS(rv, rv); + + break; + + default: + return NS_ERROR_INVALID_ARG; + } + + nsAutoPtr enumerator( + new CacheEntriesEnumerator(file)); + + rv = enumerator->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + *aEnumerator = enumerator.forget(); + return NS_OK; +} + +static nsresult +TruncFile(PRFileDesc *aFD, uint32_t aEOF) +{ +#if defined(XP_UNIX) + if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET); + if (cnt == -1) + return NS_ERROR_FAILURE; + if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_OS2) + if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(aFD), aEOF) != NO_ERROR) { + NS_ERROR("DosSetFileSize failed"); + return NS_ERROR_FAILURE; + } +#else + MOZ_ASSERT(false, "Not implemented!"); +#endif + + return NS_OK; +} + +nsresult +CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle, + int64_t aTruncatePos, + int64_t aEOFPos) +{ + nsresult rv; + + if (!aHandle->mFileExists) { + rv = CreateFile(aHandle); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!aHandle->mFD) { + rv = OpenNSPRHandle(aHandle); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + NSPRHandleUsed(aHandle); + } + + // Check again, OpenNSPRHandle could figure out the file was gone. + if (!aHandle->mFileExists) { + return NS_ERROR_NOT_AVAILABLE; + } + + // This operation always invalidates the entry + aHandle->mInvalid = true; + + rv = TruncFile(aHandle->mFD, static_cast(aTruncatePos)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = TruncFile(aHandle->mFD, static_cast(aEOFPos)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +CacheFileIOManager::CreateFile(CacheFileHandle *aHandle) +{ + MOZ_ASSERT(!aHandle->mFD); + + nsresult rv; + + nsCOMPtr file; + if (aHandle->IsDoomed()) { + rv = GetDoomedFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = GetFile(aHandle->Hash(), getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + if (NS_SUCCEEDED(file->Exists(&exists)) && exists) { + NS_WARNING("Found a file that should not exist!"); + } + } + + rv = OpenNSPRHandle(aHandle, true); + NS_ENSURE_SUCCESS(rv, rv); + + aHandle->mFileSize = 0; + return NS_OK; +} + +void +CacheFileIOManager::GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval) +{ + _retval.Assign(""); + const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + for (uint32_t i=0 ; i> 4]); + _retval.Append(hexChars[(*aHash)[i] & 0xF]); + } +} + +nsresult +CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr file; + rv = mCacheDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->AppendNative(NS_LITERAL_CSTRING("entries")); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString leafName; + GetHashStr(aHash, leafName); + + rv = file->AppendNative(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + file.swap(*_retval); + return NS_OK; +} + +nsresult +CacheFileIOManager::GetDoomedFile(nsIFile **_retval) +{ + nsresult rv; + nsCOMPtr file; + rv = mCacheDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->AppendNative(NS_LITERAL_CSTRING("doomed")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf")); + NS_ENSURE_SUCCESS(rv, rv); + + srand(static_cast(PR_Now())); + nsAutoCString leafName; + uint32_t iter=0; + while (true) { + iter++; + leafName.AppendInt(rand()); + rv = file->SetNativeLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) + break; + + leafName.Truncate(); + } + +// Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter); + + file.swap(*_retval); + return NS_OK; +} + +nsresult +CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir) +{ + nsresult rv; + bool exists; + + nsCOMPtr file; + if (!aDir) { + file = aFile; + } else { + nsAutoCString dir(aDir); + rv = aFile->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + rv = file->AppendNative(dir); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = file->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot create directory"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +CacheFileIOManager::CreateCacheTree() +{ + MOZ_ASSERT(!mTreeCreated); + + if (!mCacheDirectory) + return NS_ERROR_FILE_INVALID_PATH; + + nsresult rv; + + // ensure parent directory exists + nsCOMPtr parentDir; + rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = CheckAndCreateDir(parentDir, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // ensure cache directory exists + rv = CheckAndCreateDir(mCacheDirectory, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // ensure entries directory exists + rv = CheckAndCreateDir(mCacheDirectory, "entries"); + NS_ENSURE_SUCCESS(rv, rv); + + // ensure doomed directory exists + rv = CheckAndCreateDir(mCacheDirectory, "doomed"); + NS_ENSURE_SUCCESS(rv, rv); + + mTreeCreated = true; + return NS_OK; +} + +nsresult +CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate) +{ + MOZ_ASSERT(!aHandle->mFD); + MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex); + MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit); + + nsresult rv; + + if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) { + // close handle that hasn't been used for the longest time + rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aCreate) { + rv = aHandle->mFile->OpenNSPRFileDesc( + PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD); + NS_ENSURE_SUCCESS(rv, rv); + + aHandle->mFileExists = true; + } + else { + rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD); + if (NS_ERROR_FILE_NOT_FOUND == rv) { + LOG((" file doesn't exists")); + aHandle->mFileExists = false; + aHandle->mIsDoomed = true; + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + } + + mHandlesByLastUsed.AppendElement(aHandle); + return NS_OK; +} + +void +CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle) +{ + MOZ_ASSERT(aHandle->mFD); + + DebugOnly found; + found = mHandlesByLastUsed.RemoveElement(aHandle); + MOZ_ASSERT(found); + + mHandlesByLastUsed.AppendElement(aHandle); +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h new file mode 100644 index 000000000000..3de641a76661 --- /dev/null +++ b/netwerk/cache2/CacheFileIOManager.h @@ -0,0 +1,227 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFileIOManager__h__ +#define CacheFileIOManager__h__ + +#include "CacheIOThread.h" +#include "CacheEntriesEnumerator.h" +#include "nsIEventTarget.h" +#include "nsCOMPtr.h" +#include "mozilla/SHA1.h" +#include "nsTArray.h" +#include "nsString.h" +#include "pldhash.h" +#include "prclist.h" +#include "prio.h" + +class nsIFile; + +namespace mozilla { +namespace net { + +class CacheFileHandle : public nsISupports + , public PRCList +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority); + bool IsDoomed() { return mIsDoomed; } + const SHA1Sum::Hash *Hash() { return mHash; } + int64_t FileSize() { return mFileSize; } + bool IsPriority() { return mPriority; } + bool FileExists() { return mFileExists; } + bool IsClosed() { return mClosed; } + nsCString & Key() { return mKey; } + +private: + friend class CacheFileIOManager; + friend class CacheFileHandles; + friend class ReleaseNSPRHandleEvent; + + virtual ~CacheFileHandle(); + + const SHA1Sum::Hash *mHash; + bool mIsDoomed; + bool mRemovingHandle; + bool mPriority; + bool mClosed; + bool mInvalid; + bool mFileExists; // This means that the file should exists, + // but it can be still deleted by OS/user + // and then a subsequent OpenNSPRFileDesc() + // will fail. + nsCOMPtr mFile; + int64_t mFileSize; + PRFileDesc *mFD; // if null then the file doesn't exists on the disk + nsCString mKey; +}; + +class CacheFileHandles { +public: + CacheFileHandles(); + ~CacheFileHandles(); + + nsresult Init(); + void Shutdown(); + + nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval); + nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval); + void RemoveHandle(CacheFileHandle *aHandlle); + void GetAllHandles(nsTArray > *_retval); + uint32_t HandleCount(); + +private: + static PLDHashNumber HashKey(PLDHashTable *table, const void *key); + static bool MatchEntry(PLDHashTable *table, + const PLDHashEntryHdr *entry, + const void *key); + static void MoveEntry(PLDHashTable *table, + const PLDHashEntryHdr *from, + PLDHashEntryHdr *to); + static void ClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry); + + static PLDHashTableOps mOps; + PLDHashTable mTable; + bool mInitialized; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class OpenFileEvent; +class CloseFileEvent; +class ReadEvent; +class WriteEvent; + +#define CACHEFILEIOLISTENER_IID \ +{ /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */ \ + 0xdcaf2ddc, \ + 0x17cf, \ + 0x4242, \ + {0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5} \ +} + +class CacheFileIOListener : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEIOLISTENER_IID) + + NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) = 0; + NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult) = 0; + NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, + nsresult aResult) = 0; + NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) = 0; + NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileIOListener, CACHEFILEIOLISTENER_IID) + + +class CacheFileIOManager : public nsISupports +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + enum { + OPEN = 0U, + CREATE = 1U, + CREATE_NEW = 2U, + PRIORITY = 4U, + NOHASH = 8U + }; + + CacheFileIOManager(); + + static nsresult Init(); + static nsresult Shutdown(); + static nsresult OnProfile(); + static already_AddRefed IOTarget(); + static already_AddRefed IOThread(); + + static nsresult OpenFile(const nsACString &aKey, + uint32_t aFlags, + CacheFileIOListener *aCallback); + static nsresult Read(CacheFileHandle *aHandle, int64_t aOffset, + char *aBuf, int32_t aCount, + CacheFileIOListener *aCallback); + static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset, + const char *aBuf, int32_t aCount, bool aValidate, + CacheFileIOListener *aCallback); + static nsresult DoomFile(CacheFileHandle *aHandle, + CacheFileIOListener *aCallback); + static nsresult DoomFileByKey(const nsACString &aKey, + CacheFileIOListener *aCallback); + static nsresult ReleaseNSPRHandle(CacheFileHandle *aHandle); + static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle, + int64_t aTruncatePos, int64_t aEOFPos, + CacheFileIOListener *aCallback); + + enum EEnumerateMode { + ENTRIES, + DOOMED + }; + + static nsresult EnumerateEntryFiles(EEnumerateMode aMode, + CacheEntriesEnumerator** aEnumerator); + +private: + friend class CacheFileHandle; + friend class CacheFileChunk; + friend class CacheFile; + friend class ShutdownEvent; + friend class OpenFileEvent; + friend class CloseHandleEvent; + friend class ReadEvent; + friend class WriteEvent; + friend class DoomFileEvent; + friend class DoomFileByKeyEvent; + friend class ReleaseNSPRHandleEvent; + friend class TruncateSeekSetEOFEvent; + + virtual ~CacheFileIOManager(); + + static nsresult CloseHandle(CacheFileHandle *aHandle); + + nsresult InitInternal(); + nsresult ShutdownInternal(); + + nsresult OpenFileInternal(const SHA1Sum::Hash *aHash, + uint32_t aFlags, + CacheFileHandle **_retval); + nsresult CloseHandleInternal(CacheFileHandle *aHandle); + nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset, + char *aBuf, int32_t aCount); + nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset, + const char *aBuf, int32_t aCount, bool aValidate); + nsresult DoomFileInternal(CacheFileHandle *aHandle); + nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash); + nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle); + nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle, + int64_t aTruncatePos, int64_t aEOFPos); + + nsresult CreateFile(CacheFileHandle *aHandle); + static void GetHashStr(const SHA1Sum::Hash *aHash, nsACString &_retval); + nsresult GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval); + nsresult GetDoomedFile(nsIFile **_retval); + nsresult CheckAndCreateDir(nsIFile *aFile, const char *aDir); + nsresult CreateCacheTree(); + nsresult OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate = false); + void NSPRHandleUsed(CacheFileHandle *aHandle); + + + static CacheFileIOManager *gInstance; + bool mShuttingDown; + nsRefPtr mIOThread; + nsCOMPtr mCacheDirectory; + bool mTreeCreated; + CacheFileHandles mHandles; + nsTArray mHandlesByLastUsed; +}; + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheFileInputStream.cpp b/netwerk/cache2/CacheFileInputStream.cpp new file mode 100644 index 000000000000..6be8321c6fb0 --- /dev/null +++ b/netwerk/cache2/CacheFileInputStream.cpp @@ -0,0 +1,634 @@ +/* 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 "CacheFileInputStream.h" + +#include "CacheLog.h" +#include "CacheFile.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include + +namespace mozilla { +namespace net { + +NS_IMPL_ADDREF(CacheFileInputStream) +NS_IMETHODIMP_(nsrefcnt) +CacheFileInputStream::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "CacheFileInputStream"); + + if (0 == count) { + mRefCnt = 1; + delete (this); + return 0; + } + + if (count == 1) { + mFile->RemoveInput(this); + } + + return count; +} + +NS_INTERFACE_MAP_BEGIN(CacheFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsISeekableStream) + NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END_THREADSAFE + +CacheFileInputStream::CacheFileInputStream(CacheFile *aFile) + : mFile(aFile) + , mPos(0) + , mClosed(false) + , mStatus(NS_OK) + , mWaitingForUpdate(false) + , mListeningForChunk(-1) + , mInReadSegments(false) + , mCallbackFlags(0) +{ + LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileInputStream); +} + +CacheFileInputStream::~CacheFileInputStream() +{ + LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileInputStream); +} + +// nsIInputStream +NS_IMETHODIMP +CacheFileInputStream::Close() +{ + LOG(("CacheFileInputStream::Close() [this=%p]", this)); + return CloseWithStatus(NS_OK); +} + +NS_IMETHODIMP +CacheFileInputStream::Available(uint64_t *_retval) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + if (mClosed) { + LOG(("CacheFileInputStream::Available() - Stream is closed. [this=%p, " + "status=0x%08x]", this, mStatus)); + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED; + } + + EnsureCorrectChunk(false); + *_retval = 0; + + if (mChunk) { + int64_t canRead; + const char *buf; + CanRead(&canRead, &buf); + + if (canRead > 0) + *_retval = canRead; + else if (canRead == 0 && !mFile->mOutput) + return NS_BASE_STREAM_CLOSED; + } + + LOG(("CacheFileInputStream::Available() [this=%p, retval=%lld]", + this, *_retval)); + + return NS_OK; +} + +NS_IMETHODIMP +CacheFileInputStream::Read(char *aBuf, uint32_t aCount, uint32_t *_retval) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount)); + + nsresult rv; + + if (mClosed) { + LOG(("CacheFileInputStream::Read() - Stream is closed. [this=%p, " + "status=0x%08x]", this, mStatus)); + + if NS_FAILED(mStatus) + return mStatus; + + *_retval = 0; + return NS_OK; + } + + EnsureCorrectChunk(false); + if (!mChunk) { + if (mListeningForChunk == -1) { + LOG((" no chunk, returning 0 read and NS_OK")); + *_retval = 0; + return NS_OK; + } + else { + LOG((" waiting for chuck, returning WOULD_BLOCK")); + return NS_BASE_STREAM_WOULD_BLOCK; + } + } + + int64_t canRead; + const char *buf; + CanRead(&canRead, &buf); + + if (canRead < 0) { + // file was truncated ??? + MOZ_ASSERT(false, "SetEOF is currenty not implemented?!"); + *_retval = 0; + rv = NS_OK; + } + else if (canRead > 0) { + *_retval = std::min(static_cast(canRead), aCount); + memcpy(aBuf, buf, *_retval); + mPos += *_retval; + + EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0)); + + rv = NS_OK; + } + else { + if (mFile->mOutput) + rv = NS_BASE_STREAM_WOULD_BLOCK; + else { + *_retval = 0; + rv = NS_OK; + } + } + + LOG(("CacheFileInputStream::Read() [this=%p, rv=0x%08x, retval=%d", + this, rv, *_retval)); + + return rv; +} + +NS_IMETHODIMP +CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, + uint32_t aCount, uint32_t *_retval) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]", + this, aCount)); + + nsresult rv; + + if (mClosed) { + LOG(("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, " + "status=0x%08x]", this, mStatus)); + + if NS_FAILED(mStatus) + return mStatus; + + *_retval = 0; + return NS_OK; + } + + EnsureCorrectChunk(false); + if (!mChunk) { + if (mListeningForChunk == -1) { + *_retval = 0; + return NS_OK; + } + else { + return NS_BASE_STREAM_WOULD_BLOCK; + } + } + + int64_t canRead; + const char *buf; + CanRead(&canRead, &buf); + + if (canRead < 0) { + // file was truncated ??? + MOZ_ASSERT(false, "SetEOF is currenty not implemented?!"); + *_retval = 0; + rv = NS_OK; + } + else if (canRead > 0) { + uint32_t toRead = std::min(static_cast(canRead), aCount); + + // We need to release the lock to avoid lock re-entering +#ifdef DEBUG + int64_t oldPos = mPos; +#endif + mInReadSegments = true; + lock.Unlock(); + rv = aWriter(this, aClosure, buf, 0, toRead, _retval); + lock.Lock(); + mInReadSegments = false; +#ifdef DEBUG + MOZ_ASSERT(oldPos == mPos); +#endif + + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(*_retval <= toRead, + "writer should not write more than we asked it to write"); + mPos += *_retval; + } + + EnsureCorrectChunk(!(canRead < aCount && mPos % kChunkSize == 0)); + + rv = NS_OK; + } + else { + if (mFile->mOutput) + rv = NS_BASE_STREAM_WOULD_BLOCK; + else { + *_retval = 0; + rv = NS_OK; + } + } + + LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08x, retval=%d", + this, rv, *_retval)); + + return rv; +} + +NS_IMETHODIMP +CacheFileInputStream::IsNonBlocking(bool *_retval) +{ + *_retval = true; + return NS_OK; +} + +// nsIAsyncInputStream +NS_IMETHODIMP +CacheFileInputStream::CloseWithStatus(nsresult aStatus) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]", + this, aStatus)); + + if (mClosed) { + MOZ_ASSERT(!mCallback); + return NS_OK; + } + + mClosed = true; + mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED; + + if (mChunk) + ReleaseChunk(); + + // TODO propagate error from input stream to other streams ??? + + MaybeNotifyListener(); + + return NS_OK; +} + +NS_IMETHODIMP +CacheFileInputStream::AsyncWait(nsIInputStreamCallback *aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget *aEventTarget) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, " + "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags, + aRequestedCount, aEventTarget)); + + mCallback = aCallback; + mCallbackFlags = aFlags; + + if (!mCallback) { + if (mWaitingForUpdate) { + mChunk->CancelWait(this); + mWaitingForUpdate = false; + } + return NS_OK; + } + + if (mClosed) { + NotifyListener(); + return NS_OK; + } + + EnsureCorrectChunk(false); + + MaybeNotifyListener(); + + return NS_OK; +} + +// nsISeekableStream +NS_IMETHODIMP +CacheFileInputStream::Seek(int32_t whence, int64_t offset) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%lld]", + this, whence, offset)); + + if (mClosed) { + LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this)); + return NS_BASE_STREAM_CLOSED; + } + + int64_t newPos = offset; + switch (whence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += mPos; + break; + case NS_SEEK_END: + newPos += mFile->mDataSize; + break; + default: + NS_ERROR("invalid whence"); + return NS_ERROR_INVALID_ARG; + } + mPos = newPos; + EnsureCorrectChunk(true); + + LOG(("CacheFileInputStream::Seek() [this=%p, pos=%lld]", this, mPos)); + return NS_OK; +} + +NS_IMETHODIMP +CacheFileInputStream::Tell(int64_t *_retval) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + if (mClosed) { + LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this)); + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mPos; + + LOG(("CacheFileInputStream::Tell() [this=%p, retval=%lld]", this, *_retval)); + return NS_OK; +} + +NS_IMETHODIMP +CacheFileInputStream::SetEOF() +{ + MOZ_ASSERT(false, "Don't call SetEOF on cache input stream"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +// CacheFileChunkListener +nsresult +CacheFileInputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) +{ + MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileInputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) +{ + MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileInputStream::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, + CacheFileChunk *aChunk) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08x, " + "idx=%d, chunk=%p]", this, aResult, aChunkIdx, aChunk)); + + MOZ_ASSERT(mListeningForChunk != -1); + + if (mListeningForChunk != static_cast(aChunkIdx)) { + // This is not a chunk that we're waiting for + LOG(("CacheFileInputStream::OnChunkAvailable() - Notification is for a " + "different chunk. [this=%p, listeningForChunk=%lld]", + this, mListeningForChunk)); + + return NS_OK; + } + + MOZ_ASSERT(!mChunk); + MOZ_ASSERT(!mWaitingForUpdate); + mListeningForChunk = -1; + + if (mClosed) { + MOZ_ASSERT(!mCallback); + + LOG(("CacheFileInputStream::OnChunkAvailable() - Stream is closed, " + "ignoring notification. [this=%p]", this)); + + return NS_OK; + } + + mChunk = aChunk; + MaybeNotifyListener(); + + return NS_OK; +} + +nsresult +CacheFileInputStream::OnChunkUpdated(CacheFileChunk *aChunk) +{ + CacheFileAutoLock lock(mFile); + MOZ_ASSERT(!mInReadSegments); + + LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]", + this, aChunk->Index())); + + if (!mWaitingForUpdate) { + LOG(("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since " + "mWaitingforUpdate == false. [this=%p]", this)); + + return NS_OK; + } + else { + mWaitingForUpdate = false; + } + + MOZ_ASSERT(mChunk == aChunk); + + MaybeNotifyListener(); + + return NS_OK; +} + +void +CacheFileInputStream::ReleaseChunk() +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]", + this, mChunk->Index())); + + if (mWaitingForUpdate) { + LOG(("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. " + "[this=%p]", this)); + + mChunk->CancelWait(this); + mWaitingForUpdate = false; + } + + mFile->ReleaseOutsideLock(mChunk.forget().get()); +} + +void +CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]", + this, aReleaseOnly)); + + nsresult rv; + + uint32_t chunkIdx = mPos / kChunkSize; + + if (mChunk) { + if (mChunk->Index() == chunkIdx) { + // we have a correct chunk + LOG(("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk " + "[this=%p, idx=%d]", this, chunkIdx)); + + return; + } + else { + ReleaseChunk(); + } + } + + MOZ_ASSERT(!mWaitingForUpdate); + + if (aReleaseOnly) + return; + + if (mListeningForChunk == static_cast(chunkIdx)) { + // We're already waiting for this chunk + LOG(("CacheFileInputStream::EnsureCorrectChunk() - Already listening for " + "chunk %lld [this=%p]", mListeningForChunk, this)); + + return; + } + + rv = mFile->GetChunkLocked(chunkIdx, false, this, getter_AddRefs(mChunk)); + if (NS_FAILED(rv)) { + LOG(("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. " + "[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv)); + + } + else if (!mChunk) { + mListeningForChunk = static_cast(chunkIdx); + } + + MaybeNotifyListener(); +} + +void +CacheFileInputStream::CanRead(int64_t *aCanRead, const char **aBuf) +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT(mChunk); + MOZ_ASSERT(mPos / kChunkSize == mChunk->Index()); + + uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize; + *aCanRead = mChunk->DataSize() - chunkOffset; + *aBuf = mChunk->BufForReading() + chunkOffset; + + LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%lld]", + this, *aCanRead)); +} + +void +CacheFileInputStream::NotifyListener() +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this)); + + MOZ_ASSERT(mCallback); + + if (!mCallbackTarget) + mCallbackTarget = NS_GetCurrentThread(); + + nsCOMPtr asyncCallback = + NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + + mCallback = nullptr; + mCallbackTarget = nullptr; + + asyncCallback->OnInputStreamReady(this); +} + +void +CacheFileInputStream::MaybeNotifyListener() +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, " + "mClosed=%d, mStatus=0x%08x, mChunk=%p, mListeningForChunk=%lld, " + "mWaitingForUpdate=%d]", this, mCallback.get(), mClosed, mStatus, + mChunk.get(), mListeningForChunk, mWaitingForUpdate)); + + if (!mCallback) + return; + + if (mClosed) { + NotifyListener(); + return; + } + + if (!mChunk) { + if (mListeningForChunk == -1) { + // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ?? + NotifyListener(); + } + return; + } + + MOZ_ASSERT(mPos / kChunkSize == mChunk->Index()); + + if (mWaitingForUpdate) + return; + + int64_t canRead; + const char *buf; + CanRead(&canRead, &buf); + + if (canRead > 0) { + if (!(mCallbackFlags & WAIT_CLOSURE_ONLY)) + NotifyListener(); + } + else if (canRead == 0) { + if (!mFile->mOutput) { + // EOF + NotifyListener(); + } + else { + mChunk->WaitForUpdate(this); + mWaitingForUpdate = true; + } + } + else { + // Output have set EOF before mPos? + MOZ_ASSERT(false, "SetEOF is currenty not implemented?!"); + NotifyListener(); + } +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFileInputStream.h b/netwerk/cache2/CacheFileInputStream.h new file mode 100644 index 000000000000..d060df7f7eec --- /dev/null +++ b/netwerk/cache2/CacheFileInputStream.h @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFileInputStream__h__ +#define CacheFileInputStream__h__ + +#include "nsIAsyncInputStream.h" +#include "nsISeekableStream.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "CacheFileChunk.h" + + +namespace mozilla { +namespace net { + +class CacheFile; + +class CacheFileInputStream : public nsIAsyncInputStream + , public nsISeekableStream + , public CacheFileChunkListener +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + +public: + CacheFileInputStream(CacheFile *aFile); + + NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, + CacheFileChunk *aChunk); + NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk); + +private: + virtual ~CacheFileInputStream(); + + void ReleaseChunk(); + void EnsureCorrectChunk(bool aReleaseOnly); + void CanRead(int64_t *aCanRead, const char **aBuf); + void NotifyListener(); + void MaybeNotifyListener(); + + nsRefPtr mFile; + nsRefPtr mChunk; + int64_t mPos; + bool mClosed; + nsresult mStatus; + bool mWaitingForUpdate; + int64_t mListeningForChunk; + bool mInReadSegments; + + nsCOMPtr mCallback; + uint32_t mCallbackFlags; + nsCOMPtr mCallbackTarget; +}; + + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheFileMetadata.cpp b/netwerk/cache2/CacheFileMetadata.cpp new file mode 100644 index 000000000000..769b5edea474 --- /dev/null +++ b/netwerk/cache2/CacheFileMetadata.cpp @@ -0,0 +1,682 @@ +/* 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 "CacheFileMetadata.h" + +#include "CacheLog.h" +#include "CacheFileIOManager.h" +#include "CacheHashUtils.h" +#include "CacheFileChunk.h" +#include "../cache/nsCacheUtils.h" +#include "mozilla/Telemetry.h" +#include "prnetdb.h" + + +namespace mozilla { +namespace net { + +#define kMinMetadataRead 1024 // TODO find optimal value from telemetry +#define kAlignSize 4096 + +#define NO_EXPIRATION_TIME 0xFFFFFFFF + +NS_IMPL_ISUPPORTS1(CacheFileMetadata, CacheFileIOListener) + +CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey, bool aKeyIsHash) + : mHandle(aHandle) + , mKeyIsHash(aKeyIsHash) + , mHashArray(nullptr) + , mHashArraySize(0) + , mHashCount(0) + , mOffset(-1) + , mBuf(nullptr) + , mBufSize(0) + , mWriteBuf(nullptr) + , mElementsSize(0) + , mIsDirty(false) +{ + LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]", + this, aHandle, PromiseFlatCString(aKey).get())); + + MOZ_COUNT_CTOR(CacheFileMetadata); + memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader)); + mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME; + mKey = aKey; +} + +CacheFileMetadata::~CacheFileMetadata() +{ + LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this)); + + MOZ_COUNT_DTOR(CacheFileMetadata); + MOZ_ASSERT(!mListener); + + if (mHashArray) { + free(mHashArray); + mHashArray = nullptr; + mHashArraySize = 0; + } + + if (mBuf) { + free(mBuf); + mBuf = nullptr; + mBufSize = 0; + } + + DoMemoryReport(MemoryUsage()); +} + +CacheFileMetadata::CacheFileMetadata(const nsACString &aKey) + : mHandle(nullptr) + , mKeyIsHash(false) + , mHashArray(nullptr) + , mHashArraySize(0) + , mHashCount(0) + , mOffset(0) + , mBuf(nullptr) + , mBufSize(0) + , mWriteBuf(nullptr) + , mElementsSize(0) + , mIsDirty(true) +{ + LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", + this, PromiseFlatCString(aKey).get())); + + MOZ_COUNT_CTOR(CacheFileMetadata); + memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader)); + mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME; + mMetaHdr.mFetchCount++; + mKey = aKey; + mMetaHdr.mKeySize = mKey.Length(); +} + +void +CacheFileMetadata::SetHandle(CacheFileHandle *aHandle) +{ + LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle)); + + MOZ_ASSERT(!mHandle); + + mHandle = aHandle; +} + +nsresult +CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener) +{ + LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this, aListener)); + + MOZ_ASSERT(!mListener); + MOZ_ASSERT(!mHashArray); + MOZ_ASSERT(!mBuf); + MOZ_ASSERT(!mWriteBuf); + + nsresult rv; + + int64_t size = mHandle->FileSize(); + MOZ_ASSERT(size != -1); + + if (size == 0) { + // this is a new entry + LOG(("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty " + "metadata. [this=%p]", this)); + + InitEmptyMetadata(); + aListener->OnMetadataRead(NS_OK); + return NS_OK; + } + + if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2*sizeof(uint32_t))) { + // there must be at least checksum, header and offset + LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating " + "empty metadata. [this=%p, filesize=%lld]", this, size)); + + InitEmptyMetadata(); + aListener->OnMetadataRead(NS_OK); + return NS_OK; + } + + // round offset to 4k blocks + int64_t offset = (size / kAlignSize) * kAlignSize; + + if (size - offset < kMinMetadataRead && offset > kAlignSize) + offset -= kAlignSize; + + mBufSize = size - offset; + mBuf = static_cast(moz_xmalloc(mBufSize)); + + DoMemoryReport(MemoryUsage()); + + LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying " + "offset=%lld, filesize=%lld [this=%p]", offset, size, this)); + + mListener = aListener; + rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this); + if (NS_FAILED(rv)) { + LOG(("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed" + " synchronously, creating empty metadata. [this=%p, rv=0x%08x]", + this, rv)); + + mListener = nullptr; + InitEmptyMetadata(); + aListener->OnMetadataRead(NS_OK); + return NS_OK; + } + + return NS_OK; +} + +nsresult +CacheFileMetadata::WriteMetadata(uint32_t aOffset, + CacheFileMetadataListener *aListener) +{ + LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]", + this, aOffset, aListener)); + + MOZ_ASSERT(!mListener); + MOZ_ASSERT(!mWriteBuf); + + nsresult rv; + + mIsDirty = false; + + mWriteBuf = static_cast(moz_xmalloc(sizeof(uint32_t) + + mHashCount * sizeof(CacheHashUtils::Hash16_t) + + sizeof(CacheFileMetadataHeader) + mKey.Length() + 1 + + mElementsSize + sizeof(uint32_t))); + + char *p = mWriteBuf + sizeof(uint32_t); + memcpy(p, mHashArray, mHashCount * sizeof(CacheHashUtils::Hash16_t)); + p += mHashCount * sizeof(CacheHashUtils::Hash16_t); + memcpy(p, &mMetaHdr, sizeof(CacheFileMetadataHeader)); + p += sizeof(CacheFileMetadataHeader); + memcpy(p, mKey.get(), mKey.Length()); + p += mKey.Length(); + *p = 0; + p++; + memcpy(p, mBuf, mElementsSize); + p += mElementsSize; + + CacheHashUtils::Hash32_t hash; + hash = CacheHashUtils::Hash(mWriteBuf + sizeof(uint32_t), + p - mWriteBuf - sizeof(uint32_t)); + *reinterpret_cast(mWriteBuf) = PR_htonl(hash); + + *reinterpret_cast(p) = PR_htonl(aOffset); + p += sizeof(uint32_t); + + mListener = aListener; + rv = CacheFileIOManager::Write(mHandle, aOffset, mWriteBuf, p - mWriteBuf, + true, this); + if (NS_FAILED(rv)) { + LOG(("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() " + "failed synchronously. [this=%p, rv=0x%08x]", this, rv)); + + mListener = nullptr; + free(mWriteBuf); + mWriteBuf = nullptr; + NS_ENSURE_SUCCESS(rv, rv); + } + + DoMemoryReport(MemoryUsage()); + + return NS_OK; +} + +const char * +CacheFileMetadata::GetElement(const char *aKey) +{ + const char *data = mBuf; + const char *limit = mBuf + mElementsSize; + + while (data < limit) { + // Point to the value part + const char *value = data + strlen(data) + 1; + MOZ_ASSERT(value < limit, "Metadata elements corrupted"); + if (strcmp(data, aKey) == 0) { + LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]", + this, aKey)); + return value; + } + + // Skip value part + data = value + strlen(value) + 1; + } + MOZ_ASSERT(data == limit, "Metadata elements corrupted"); + LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]", + this, aKey)); + return nullptr; +} + +nsresult +CacheFileMetadata::SetElement(const char *aKey, const char *aValue) +{ + LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", + this, aKey, aValue)); + + MarkDirty(); + + const uint32_t keySize = strlen(aKey) + 1; + char *pos = const_cast(GetElement(aKey)); + + if (!aValue) { + // No value means remove the key/value pair completely, if existing + if (pos) { + uint32_t oldValueSize = strlen(pos) + 1; + uint32_t offset = pos - mBuf; + uint32_t remainder = mElementsSize - (offset + oldValueSize); + + memmove(pos - keySize, pos + oldValueSize, remainder); + mElementsSize -= keySize + oldValueSize; + } + return NS_OK; + } + + const uint32_t valueSize = strlen(aValue) + 1; + uint32_t newSize = mElementsSize + valueSize; + if (pos) { + const uint32_t oldValueSize = strlen(pos) + 1; + const uint32_t offset = pos - mBuf; + const uint32_t remainder = mElementsSize - (offset + oldValueSize); + + // Update the value in place + newSize -= oldValueSize; + EnsureBuffer(newSize); + + // Move the remainder to the right place + pos = mBuf + offset; + memmove(pos + valueSize, pos + oldValueSize, remainder); + } else { + // allocate new meta data element + newSize += keySize; + EnsureBuffer(newSize); + + // Add after last element + pos = mBuf + mElementsSize; + memcpy(pos, aKey, keySize); + pos += keySize; + } + + // Update value + memcpy(pos, aValue, valueSize); + mElementsSize = newSize; + + return NS_OK; +} + +CacheHashUtils::Hash16_t +CacheFileMetadata::GetHash(uint32_t aIndex) +{ + MOZ_ASSERT(aIndex < mHashCount); + return PR_ntohs(mHashArray[aIndex]); +} + +nsresult +CacheFileMetadata::SetHash(uint32_t aIndex, CacheHashUtils::Hash16_t aHash) +{ + LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", + this, aIndex, aHash)); + + MarkDirty(); + + MOZ_ASSERT(aIndex <= mHashCount); + + if (aIndex > mHashCount) { + return NS_ERROR_INVALID_ARG; + } else if (aIndex == mHashCount) { + if ((aIndex + 1) * sizeof(CacheHashUtils::Hash16_t) > mHashArraySize) { + // reallocate hash array buffer + if (mHashArraySize == 0) + mHashArraySize = 32 * sizeof(CacheHashUtils::Hash16_t); + else + mHashArraySize *= 2; + mHashArray = static_cast( + moz_xrealloc(mHashArray, mHashArraySize)); + } + + mHashCount++; + } + + mHashArray[aIndex] = PR_htons(aHash); + + DoMemoryReport(MemoryUsage()); + + return NS_OK; +} + +nsresult +CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) +{ + LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]", + this, aExpirationTime)); + + MarkDirty(); + mMetaHdr.mExpirationTime = aExpirationTime; + return NS_OK; +} + +nsresult +CacheFileMetadata::GetExpirationTime(uint32_t *_retval) +{ + *_retval = mMetaHdr.mExpirationTime; + return NS_OK; +} + +nsresult +CacheFileMetadata::SetLastModified(uint32_t aLastModified) +{ + LOG(("CacheFileMetadata::SetLastModified() [this=%p, lastModified=%d]", + this, aLastModified)); + + MarkDirty(); + mMetaHdr.mLastModified = aLastModified; + return NS_OK; +} + +nsresult +CacheFileMetadata::GetLastModified(uint32_t *_retval) +{ + *_retval = mMetaHdr.mLastModified; + return NS_OK; +} + +nsresult +CacheFileMetadata::GetLastFetched(uint32_t *_retval) +{ + *_retval = mMetaHdr.mLastFetched; + return NS_OK; +} + +nsresult +CacheFileMetadata::GetFetchCount(uint32_t *_retval) +{ + *_retval = mMetaHdr.mFetchCount; + return NS_OK; +} + +nsresult +CacheFileMetadata::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileMetadata::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult) +{ + LOG(("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, result=0x%08x]", + this, aHandle, aResult)); + + MOZ_ASSERT(mListener); + MOZ_ASSERT(mWriteBuf); + + free(mWriteBuf); + mWriteBuf = nullptr; + + nsCOMPtr listener; + + mListener.swap(listener); + listener->OnMetadataWritten(aResult); + + DoMemoryReport(MemoryUsage()); + + return NS_OK; +} + +nsresult +CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf, + nsresult aResult) +{ + LOG(("CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08x]", + this, aHandle, aResult)); + + MOZ_ASSERT(mListener); + + nsresult rv; + nsCOMPtr listener; + + if (NS_FAILED(aResult)) { + LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed, " + "creating empty metadata. [this=%p, rv=0x%08x]", this, aResult)); + + InitEmptyMetadata(); + mListener.swap(listener); + listener->OnMetadataRead(NS_OK); + return NS_OK; + } + + // check whether we have read all necessary data + uint32_t realOffset = PR_ntohl(*(reinterpret_cast( + mBuf + mBufSize - sizeof(uint32_t)))); + + int64_t size = mHandle->FileSize(); + MOZ_ASSERT(size != -1); + + if (realOffset >= size) { + LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating empty " + "metadata. [this=%p, realOffset=%d, size=%lld]", this, realOffset, + size)); + + InitEmptyMetadata(); + mListener.swap(listener); + listener->OnMetadataRead(NS_OK); + return NS_OK; + } + + uint32_t usedOffset = size - mBufSize; + + if (realOffset < usedOffset) { + uint32_t missing = usedOffset - realOffset; + // we need to read more data + mBuf = static_cast(moz_xrealloc(mBuf, mBufSize + missing)); + memmove(mBuf + missing, mBuf, mBufSize); + mBufSize += missing; + + DoMemoryReport(MemoryUsage()); + + LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to " + "have full metadata. [this=%p]", missing, this)); + + rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this); + if (NS_FAILED(rv)) { + LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed" + " synchronously, creating empty metadata. [this=%p, rv=0x%08x]", + this, rv)); + + InitEmptyMetadata(); + mListener.swap(listener); + listener->OnMetadataRead(rv); + return NS_OK; + } + + return NS_OK; + } + + // We have all data according to offset information at the end of the entry. + // Try to parse it. + rv = ParseMetadata(realOffset, realOffset - usedOffset); + if (NS_FAILED(rv)) { + LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating " + "empty metadata. [this=%p]", this)); + InitEmptyMetadata(); + } + + mListener.swap(listener); + listener->OnMetadataRead(NS_OK); + + return NS_OK; +} + +nsresult +CacheFileMetadata::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileMetadata::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) +{ + MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +void +CacheFileMetadata::InitEmptyMetadata() +{ + if (mBuf) { + free(mBuf); + mBuf = nullptr; + mBufSize = 0; + } + mOffset = 0; + mMetaHdr.mFetchCount = 1; + mMetaHdr.mExpirationTime = NO_EXPIRATION_TIME; + mMetaHdr.mKeySize = mKey.Length(); + + DoMemoryReport(MemoryUsage()); +} + +nsresult +CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset) +{ + LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, " + "bufOffset=%d]", this, aMetaOffset, aBufOffset)); + + nsresult rv; + + uint32_t metaposOffset = mBufSize - sizeof(uint32_t); + uint32_t hashesOffset = aBufOffset + sizeof(uint32_t); + uint32_t hashCount = aMetaOffset / kChunkSize; + if (aMetaOffset % kChunkSize) + hashCount++; + uint32_t hashesLen = hashCount * sizeof(CacheHashUtils::Hash16_t); + uint32_t hdrOffset = hashesOffset + hashesLen; + uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader); + + LOG(("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n " + "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n " + "keyOffset=%d\n", this, metaposOffset, hashesOffset, hashCount, + hashesLen,hdrOffset, keyOffset)); + + if (keyOffset > metaposOffset) { + LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]", + this)); + return NS_ERROR_FILE_CORRUPTED; + } + + uint32_t elementsOffset = reinterpret_cast( + mBuf + hdrOffset)->mKeySize + keyOffset + 1; + + if (elementsOffset > metaposOffset) { + LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d " + "[this=%p]", elementsOffset, this)); + return NS_ERROR_FILE_CORRUPTED; + } + + // check that key ends with \0 + if (mBuf[elementsOffset - 1] != 0) { + LOG(("CacheFileMetadata::ParseMetadata() - Elements not null terminated. " + "[this=%p]", this)); + return NS_ERROR_FILE_CORRUPTED; + } + + if (!mKeyIsHash) { + uint32_t keySize = reinterpret_cast( + mBuf + hdrOffset)->mKeySize; + + if (keySize != mKey.Length()) { + LOG(("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s [this=%p]", + nsCString(mBuf + keyOffset, keySize).get(), this)); + return NS_ERROR_FILE_CORRUPTED; + } + + if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) { + LOG(("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s [this=%p]", + nsCString(mBuf + keyOffset, keySize).get(), this)); + return NS_ERROR_FILE_CORRUPTED; + } + } + + // check metadata hash (data from hashesOffset to metaposOffset) + CacheHashUtils::Hash32_t hash; + hash = CacheHashUtils::Hash(mBuf + hashesOffset, + metaposOffset - hashesOffset); + + if (hash != PR_ntohl(*(reinterpret_cast(mBuf + aBufOffset)))) { + LOG(("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of " + "the metadata is %x, hash in file is %x [this=%p]", hash, + PR_ntohl(*(reinterpret_cast(mBuf + aBufOffset))), this)); + return NS_ERROR_FILE_CORRUPTED; + } + + // check elements + rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset); + if (NS_FAILED(rv)) + return rv; + + mHashArraySize = hashesLen; + mHashCount = hashCount; + if (mHashArraySize) { + mHashArray = static_cast( + moz_xmalloc(mHashArraySize)); + memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize); + } + + memcpy(&mMetaHdr, mBuf + hdrOffset, sizeof(CacheFileMetadataHeader)); + mMetaHdr.mFetchCount++; + MarkDirty(); + + mElementsSize = metaposOffset - elementsOffset; + memmove(mBuf, mBuf + elementsOffset, mElementsSize); + mOffset = aMetaOffset; + + // TODO: shrink memory if buffer is too big + + DoMemoryReport(MemoryUsage()); + + return NS_OK; +} + +nsresult +CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize) +{ + if (aSize) { + // Check if the metadata ends with a zero byte. + if (aBuf[aSize - 1] != 0) { + NS_ERROR("Metadata elements are not null terminated"); + LOG(("CacheFileMetadata::CheckElements() - Elements are not null " + "terminated. [this=%p]", this)); + return NS_ERROR_FILE_CORRUPTED; + } + // Check that there are an even number of zero bytes + // to match the pattern { key \0 value \0 } + bool odd = false; + for (uint32_t i = 0; i < aSize; i++) { + if (aBuf[i] == 0) + odd = !odd; + } + if (odd) { + NS_ERROR("Metadata elements are malformed"); + LOG(("CacheFileMetadata::CheckElements() - Elements are malformed. " + "[this=%p]", this)); + return NS_ERROR_FILE_CORRUPTED; + } + } + return NS_OK; +} + +void +CacheFileMetadata::EnsureBuffer(uint32_t aSize) +{ + if (mBufSize < aSize) { + mBufSize = aSize; + mBuf = static_cast(moz_xrealloc(mBuf, mBufSize)); + } + + DoMemoryReport(MemoryUsage()); +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFileMetadata.h b/netwerk/cache2/CacheFileMetadata.h new file mode 100644 index 000000000000..f1d6479d9f0a --- /dev/null +++ b/netwerk/cache2/CacheFileMetadata.h @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFileMetadata__h__ +#define CacheFileMetadata__h__ + +#include "CacheFileIOManager.h" +#include "CacheStorageService.h" +#include "CacheHashUtils.h" +#include "nsAutoPtr.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +typedef struct { + uint32_t mFetchCount; + uint32_t mLastFetched; + uint32_t mLastModified; + uint32_t mExpirationTime; + uint32_t mKeySize; +} CacheFileMetadataHeader; + +#define CACHEFILEMETADATALISTENER_IID \ +{ /* a9e36125-3f01-4020-9540-9dafa8d31ba7 */ \ + 0xa9e36125, \ + 0x3f01, \ + 0x4020, \ + {0x95, 0x40, 0x9d, 0xaf, 0xa8, 0xd3, 0x1b, 0xa7} \ +} + +class CacheFileMetadataListener : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(CACHEFILEMETADATALISTENER_IID) + + NS_IMETHOD OnMetadataRead(nsresult aResult) = 0; + NS_IMETHOD OnMetadataWritten(nsresult aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileMetadataListener, + CACHEFILEMETADATALISTENER_IID) + + +class CacheFileMetadata : public CacheFileIOListener + , public CacheMemoryConsumer +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + CacheFileMetadata(CacheFileHandle *aHandle, + const nsACString &aKey, + bool aKeyIsHash); + CacheFileMetadata(const nsACString &aKey); + + void SetHandle(CacheFileHandle *aHandle); + + nsresult ReadMetadata(CacheFileMetadataListener *aListener); + nsresult WriteMetadata(uint32_t aOffset, + CacheFileMetadataListener *aListener); + + const char * GetElement(const char *aKey); + nsresult SetElement(const char *aKey, const char *aValue); + + CacheHashUtils::Hash16_t GetHash(uint32_t aIndex); + nsresult SetHash(uint32_t aIndex, + CacheHashUtils::Hash16_t aHash); + + nsresult SetExpirationTime(uint32_t aExpirationTime); + nsresult GetExpirationTime(uint32_t *_retval); + nsresult SetLastModified(uint32_t aLastModified); + nsresult GetLastModified(uint32_t *_retval); + nsresult GetLastFetched(uint32_t *_retval); + nsresult GetFetchCount(uint32_t *_retval); + + int64_t Offset() { return mOffset; } + uint32_t ElementsSize() { return mElementsSize; } + void MarkDirty() { mIsDirty = true; } + bool IsDirty() { return mIsDirty; } + uint32_t MemoryUsage() { return mHashArraySize + mBufSize; } + + NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, + nsresult aResult); + NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult); + NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult); + NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult); + +private: + virtual ~CacheFileMetadata(); + + void InitEmptyMetadata(); + nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset); + nsresult CheckElements(const char *aBuf, uint32_t aSize); + void EnsureBuffer(uint32_t aSize); + + nsRefPtr mHandle; + nsCString mKey; + bool mKeyIsHash; + CacheHashUtils::Hash16_t *mHashArray; + uint32_t mHashArraySize; + uint32_t mHashCount; + int64_t mOffset; + char *mBuf; // used for parsing, then points + // to elements + uint32_t mBufSize; + char *mWriteBuf; + CacheFileMetadataHeader mMetaHdr; + uint32_t mElementsSize; + bool mIsDirty; + nsCOMPtr mListener; +}; + + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheFileOutputStream.cpp b/netwerk/cache2/CacheFileOutputStream.cpp new file mode 100644 index 000000000000..d5ccb028536a --- /dev/null +++ b/netwerk/cache2/CacheFileOutputStream.cpp @@ -0,0 +1,382 @@ +/* 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 "CacheFileOutputStream.h" + +#include "CacheLog.h" +#include "CacheFile.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include + +namespace mozilla { +namespace net { + +NS_IMPL_ADDREF(CacheFileOutputStream) +NS_IMETHODIMP_(nsrefcnt) +CacheFileOutputStream::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "CacheFileOutputStream"); + + if (0 == count) { + mRefCnt = 1; + { + CacheFileAutoLock lock(mFile); + mFile->RemoveOutput(this); + } + delete (this); + return 0; + } + + return count; +} + +NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream) + NS_INTERFACE_MAP_ENTRY(nsISeekableStream) + NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream) +NS_INTERFACE_MAP_END_THREADSAFE + +CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile) + : mFile(aFile) + , mPos(0) + , mClosed(false) + , mStatus(NS_OK) + , mCallbackFlags(0) +{ + LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this)); + MOZ_COUNT_CTOR(CacheFileOutputStream); +} + +CacheFileOutputStream::~CacheFileOutputStream() +{ + LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this)); + MOZ_COUNT_DTOR(CacheFileOutputStream); +} + +// nsIOutputStream +NS_IMETHODIMP +CacheFileOutputStream::Close() +{ + LOG(("CacheFileOutputStream::Close() [this=%p]", this)); + return CloseWithStatus(NS_OK); +} + +NS_IMETHODIMP +CacheFileOutputStream::Flush() +{ + // TODO do we need to implement flush ??? + LOG(("CacheFileOutputStream::Flush() [this=%p]", this)); + return NS_OK; +} + +NS_IMETHODIMP +CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount, + uint32_t *_retval) +{ + CacheFileAutoLock lock(mFile); + + LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount)); + + if (mClosed) { + LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, " + "status=0x%08x]", this, mStatus)); + + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED; + } + + *_retval = aCount; + + while (aCount) { + EnsureCorrectChunk(false); + + FillHole(); + + uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize; + uint32_t canWrite = kChunkSize - chunkOffset; + uint32_t thisWrite = std::min(static_cast(canWrite), aCount); + mChunk->EnsureBufSize(chunkOffset + thisWrite); + memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite); + + mPos += thisWrite; + aBuf += thisWrite; + aCount -= thisWrite; + + mChunk->UpdateDataSize(chunkOffset, thisWrite, false); + } + + EnsureCorrectChunk(true); + + LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]", + *_retval, this)); + + return NS_OK; +} + +NS_IMETHODIMP +CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, + uint32_t *_retval) +{ + LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p" + ", count=%d]", this, aFromStream, aCount)); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure, + uint32_t aCount, uint32_t *_retval) +{ + LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, " + "count=%d]", this, aCount)); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +CacheFileOutputStream::IsNonBlocking(bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +// nsIAsyncOutputStream +NS_IMETHODIMP +CacheFileOutputStream::CloseWithStatus(nsresult aStatus) +{ + CacheFileAutoLock lock(mFile); + + LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]", + this, aStatus)); + + if (mClosed) { + MOZ_ASSERT(!mCallback); + return NS_OK; + } + + mClosed = true; + mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED; + + if (mChunk) + ReleaseChunk(); + + if (mCallback) + NotifyListener(); + + mFile->RemoveOutput(this); + + return NS_OK; +} + +NS_IMETHODIMP +CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget *aEventTarget) +{ + CacheFileAutoLock lock(mFile); + + LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, " + "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags, + aRequestedCount, aEventTarget)); + + mCallback = aCallback; + mCallbackFlags = aFlags; + + if (!mCallback) + return NS_OK; + + // The stream is blocking so it is writable at any time + if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY)) + NotifyListener(); + + return NS_OK; +} + +// nsISeekableStream +NS_IMETHODIMP +CacheFileOutputStream::Seek(int32_t whence, int64_t offset) +{ + CacheFileAutoLock lock(mFile); + + LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%lld]", + this, whence, offset)); + + if (mClosed) { + LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this)); + return NS_BASE_STREAM_CLOSED; + } + + int64_t newPos = offset; + switch (whence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += mPos; + break; + case NS_SEEK_END: + newPos += mFile->mDataSize; + break; + default: + NS_ERROR("invalid whence"); + return NS_ERROR_INVALID_ARG; + } + mPos = newPos; + EnsureCorrectChunk(true); + + LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%lld]", this, mPos)); + return NS_OK; +} + +NS_IMETHODIMP +CacheFileOutputStream::Tell(int64_t *_retval) +{ + CacheFileAutoLock lock(mFile); + + if (mClosed) { + LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this)); + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mPos; + + LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%lld]", this, *_retval)); + return NS_OK; +} + +NS_IMETHODIMP +CacheFileOutputStream::SetEOF() +{ + MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented"); + // Right now we don't use SetEOF(). If we ever need this method, we need + // to think about what to do with input streams that already points beyond + // new EOF. + return NS_ERROR_NOT_IMPLEMENTED; +} + +// CacheFileChunkListener +nsresult +CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) +{ + MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) +{ + MOZ_CRASH( + "CacheFileOutputStream::OnChunkWritten should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileOutputStream::OnChunkAvailable(nsresult aResult, + uint32_t aChunkIdx, + CacheFileChunk *aChunk) +{ + MOZ_CRASH( + "CacheFileOutputStream::OnChunkAvailable should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk) +{ + MOZ_CRASH( + "CacheFileOutputStream::OnChunkUpdated should not be called!"); + return NS_ERROR_UNEXPECTED; +} + +void +CacheFileOutputStream::ReleaseChunk() +{ + LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]", + this, mChunk->Index())); + + mFile->ReleaseOutsideLock(mChunk.forget().get()); +} + +void +CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly) +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]", + this, aReleaseOnly)); + + uint32_t chunkIdx = mPos / kChunkSize; + + if (mChunk) { + if (mChunk->Index() == chunkIdx) { + // we have a correct chunk + LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk " + "[this=%p, idx=%d]", this, chunkIdx)); + + return; + } + else { + ReleaseChunk(); + } + } + + if (aReleaseOnly) + return; + + DebugOnly rv; + rv = mFile->GetChunkLocked(chunkIdx, true, nullptr, getter_AddRefs(mChunk)); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "CacheFile::GetChunkLocked() should always succeed for writer"); +} + +void +CacheFileOutputStream::FillHole() +{ + mFile->AssertOwnsLock(); + + MOZ_ASSERT(mChunk); + MOZ_ASSERT(mPos / kChunkSize == mChunk->Index()); + + uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize; + if (mChunk->DataSize() >= pos) + return; + + LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range " + "%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this)); + + mChunk->EnsureBufSize(pos); + memset(mChunk->BufForWriting() + mChunk->DataSize(), 0, + pos - mChunk->DataSize()); + + mChunk->UpdateDataSize(mChunk->DataSize(), pos - mChunk->DataSize(), false); +} + +void +CacheFileOutputStream::NotifyListener() +{ + mFile->AssertOwnsLock(); + + LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this)); + + MOZ_ASSERT(mCallback); + + if (!mCallbackTarget) + mCallbackTarget = NS_GetCurrentThread(); + + nsCOMPtr asyncCallback = + NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget); + + mCallback = nullptr; + mCallbackTarget = nullptr; + + asyncCallback->OnOutputStreamReady(this); +} + +} // net +} // mozilla diff --git a/netwerk/cache2/CacheFileOutputStream.h b/netwerk/cache2/CacheFileOutputStream.h new file mode 100644 index 000000000000..62fdf994f235 --- /dev/null +++ b/netwerk/cache2/CacheFileOutputStream.h @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheFileOutputStream__h__ +#define CacheFileOutputStream__h__ + +#include "nsIAsyncOutputStream.h" +#include "nsISeekableStream.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "CacheFileChunk.h" + + +namespace mozilla { +namespace net { + +class CacheFile; + +class CacheFileOutputStream : public nsIAsyncOutputStream + , public nsISeekableStream + , public CacheFileChunkListener +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + +public: + CacheFileOutputStream(CacheFile *aFile); + + NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk); + NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, + CacheFileChunk *aChunk); + NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk); + +private: + virtual ~CacheFileOutputStream(); + + void ReleaseChunk(); + void EnsureCorrectChunk(bool aReleaseOnly); + void FillHole(); + void NotifyListener(); + + nsRefPtr mFile; + nsRefPtr mChunk; + int64_t mPos; + bool mClosed; + nsresult mStatus; + + nsCOMPtr mCallback; + uint32_t mCallbackFlags; + nsCOMPtr mCallbackTarget; +}; + + +} // net +} // mozilla + +#endif diff --git a/netwerk/cache2/CacheHashUtils.cpp b/netwerk/cache2/CacheHashUtils.cpp new file mode 100644 index 000000000000..601c8d0289b8 --- /dev/null +++ b/netwerk/cache2/CacheHashUtils.cpp @@ -0,0 +1,87 @@ +/* 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 "CacheHashUtils.h" + +#include "plstr.h" + +namespace mozilla { +namespace net { + +/** + * CacheHashUtils::Hash(const char * key, uint32_t initval) + * + * See http://burtleburtle.net/bob/hash/evahash.html for more information + * about this hash function. + * + * This algorithm is used to check the data integrity. + */ + +static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c) +{ + a -= b; a -= c; a ^= (c>>13); + b -= c; b -= a; b ^= (a<<8); + c -= a; c -= b; c ^= (b>>13); + a -= b; a -= c; a ^= (c>>12); + b -= c; b -= a; b ^= (a<<16); + c -= a; c -= b; c ^= (b>>5); + a -= b; a -= c; a ^= (c>>3); + b -= c; b -= a; b ^= (a<<10); + c -= a; c -= b; c ^= (b>>15); +} + +CacheHashUtils::Hash32_t +CacheHashUtils::Hash(const char *aData, uint32_t aSize, uint32_t aInitval) +{ + const uint8_t *k = reinterpret_cast(aData); + uint32_t a, b, c, len/*, length*/; + +// length = PL_strlen(key); + /* Set up the internal state */ + len = aSize; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = aInitval; /* variable initialization of internal state */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24); + b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24); + c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24); + hashmix(a, b, c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += aSize; + switch(len) { /* all the case statements fall through */ + case 11: c += (uint32_t(k[10])<<24); + case 10: c += (uint32_t(k[9])<<16); + case 9 : c += (uint32_t(k[8])<<8); + /* the low-order byte of c is reserved for the length */ + case 8 : b += (uint32_t(k[7])<<24); + case 7 : b += (uint32_t(k[6])<<16); + case 6 : b += (uint32_t(k[5])<<8); + case 5 : b += k[4]; + case 4 : a += (uint32_t(k[3])<<24); + case 3 : a += (uint32_t(k[2])<<16); + case 2 : a += (uint32_t(k[1])<<8); + case 1 : a += k[0]; + /* case 0: nothing left to add */ + } + hashmix(a, b, c); + + return c; +} + +CacheHashUtils::Hash16_t +CacheHashUtils::Hash16(const char *aData, uint32_t aSize, uint32_t aInitval) +{ + Hash32_t hash = Hash(aData, aSize, aInitval); + return (hash & 0xFFFF); +} + +} // net +} // mozilla + diff --git a/netwerk/cache2/CacheHashUtils.h b/netwerk/cache2/CacheHashUtils.h new file mode 100644 index 000000000000..26d15ca7dd5a --- /dev/null +++ b/netwerk/cache2/CacheHashUtils.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CacheHashUtils__h__ +#define CacheHashUtils__h__ + +#include "mozilla/Types.h" +#include "prnetdb.h" +#include "nsPrintfCString.h" + +#define LOGSHA1(x) \ + PR_htonl((reinterpret_cast(x))[0]), \ + PR_htonl((reinterpret_cast(x))[1]), \ + PR_htonl((reinterpret_cast(x))[2]), \ + PR_htonl((reinterpret_cast(x))[3]), \ + PR_htonl((reinterpret_cast(x))[4]) + +#define SHA1STRING(x) \ + (nsPrintfCString("%08x%08x%08x%08x%08x", LOGSHA1(x)).get()) + +namespace mozilla { +namespace net { + +class CacheHashUtils +{ +public: + typedef uint16_t Hash16_t; + typedef uint32_t Hash32_t; + + static Hash32_t Hash(const char* aData, uint32_t aSize, uint32_t aInitval=0); + static Hash16_t Hash16(const char* aData, uint32_t aSize, + uint32_t aInitval=0); +}; + + +} // net +} // mozilla + +#endif