/* 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 "CacheFileIOManager.h" #include "../cache/nsCacheUtils.h" #include "CacheHashUtils.h" #include "CacheStorageService.h" #include "CacheIndex.h" #include "CacheFileUtils.h" #include "nsThreadUtils.h" #include "CacheFile.h" #include "CacheObserver.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 #else // XXX add necessary include file for ftruncate (or equivalent) #endif namespace mozilla { namespace net { #define kOpenHandlesLimit 64 #define kMetadataWriteDelay 5000 #define kEvictionLoopLimit 40 // in milliseconds bool CacheFileHandle::DispatchRelease() { if (CacheFileIOManager::IsOnIOThreadOrCeased()) return false; nsCOMPtr ioTarget = CacheFileIOManager::IOTarget(); if (!ioTarget) return false; nsRefPtr > event = NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release); nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); if (NS_FAILED(rv)) return false; return true; } NS_IMPL_ADDREF(CacheFileHandle) NS_IMETHODIMP_(nsrefcnt) CacheFileHandle::Release() { nsrefcnt count = mRefCnt - 1; if (DispatchRelease()) { // Redispatched to the IO thread. return count; } MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get())); NS_PRECONDITION(0 != mRefCnt, "dup release"); count = --mRefCnt; NS_LOG_RELEASE(this, count, "CacheFileHandle"); if (0 == count) { mRefCnt = 1; delete (this); return 0; } return count; } NS_INTERFACE_MAP_BEGIN(CacheFileHandle) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END_THREADSAFE CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority) : mHash(aHash) , mIsDoomed(false) , mPriority(aPriority) , mClosed(false) , mInvalid(false) , mFileExists(false) , mFileSize(-1) , mFD(nullptr) { LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]" , this, LOGSHA1(aHash))); } CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority) : mHash(nullptr) , mIsDoomed(false) , mPriority(aPriority) , mClosed(false) , mInvalid(false) , mFileExists(false) , mFileSize(-1) , mFD(nullptr) , mKey(aKey) { LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this, PromiseFlatCString(aKey).get())); } CacheFileHandle::~CacheFileHandle() { LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this)); MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); nsRefPtr ioMan = CacheFileIOManager::gInstance; if (ioMan) { ioMan->CloseHandleInternal(this); } } void CacheFileHandle::Log() { nsAutoCString leafName; if (mFile) { mFile->GetNativeLeafName(leafName); } if (!mHash) { // special file LOG(("CacheFileHandle::Log() [this=%p, hash=nullptr, isDoomed=%d, " "priority=%d, closed=%d, invalid=%d, " "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]", this, mIsDoomed, mPriority, mClosed, mInvalid, mFileExists, mFileSize, leafName.get(), mKey.get())); } else { LOG(("CacheFileHandle::Log() [this=%p, hash=%08x%08x%08x%08x%08x, " "isDoomed=%d, priority=%d, closed=%d, invalid=%d, " "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]", this, LOGSHA1(mHash), mIsDoomed, mPriority, mClosed, mInvalid, mFileExists, mFileSize, leafName.get(), mKey.get())); } } uint32_t CacheFileHandle::FileSizeInK() { MOZ_ASSERT(mFileSize != -1); uint64_t size64 = mFileSize; size64 += 0x3FF; size64 >>= 10; uint32_t size; if (size64 >> 32) { NS_WARNING("CacheFileHandle::FileSizeInK() - FileSize is too large, " "truncating to PR_UINT32_MAX"); size = PR_UINT32_MAX; } else { size = static_cast(size64); } return size; } /****************************************************************************** * CacheFileHandles::HandleHashKey *****************************************************************************/ void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); mHandles.InsertElementAt(0, aHandle); } void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); DebugOnly found; found = mHandles.RemoveElement(aHandle); MOZ_ASSERT(found); } already_AddRefed CacheFileHandles::HandleHashKey::GetNewestHandle() { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); nsRefPtr handle; if (mHandles.Length()) handle = mHandles[0]; return handle.forget(); } void CacheFileHandles::HandleHashKey::GetHandles(nsTArray > &aResult) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); for (uint32_t i = 0; i < mHandles.Length(); ++i) { CacheFileHandle* handle = mHandles[i]; aResult.AppendElement(handle); } } #ifdef DEBUG void CacheFileHandles::HandleHashKey::AssertHandlesState() { for (uint32_t i = 0; i < mHandles.Length(); ++i) { CacheFileHandle* handle = mHandles[i]; MOZ_ASSERT(handle->IsDoomed()); } } #endif /****************************************************************************** * CacheFileHandles *****************************************************************************/ CacheFileHandles::CacheFileHandles() { LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this)); MOZ_COUNT_CTOR(CacheFileHandles); } CacheFileHandles::~CacheFileHandles() { LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this)); MOZ_COUNT_DTOR(CacheFileHandles); } nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, bool aReturnDoomed, CacheFileHandle **_retval) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); MOZ_ASSERT(aHash); #ifdef DEBUG_HANDLES LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash))); #endif // find hash entry for key HandleHashKey *entry = mTable.GetEntry(*aHash); if (!entry) { LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " "no handle entries found", LOGSHA1(aHash))); return NS_ERROR_NOT_AVAILABLE; } #ifdef DEBUG_HANDLES Log(entry); #endif // Check if the entry is doomed nsRefPtr handle = entry->GetNewestHandle(); if (!handle) { LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " "no handle found %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); return NS_ERROR_NOT_AVAILABLE; } if (handle->IsDoomed()) { LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " "found doomed handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); // If the consumer doesn't want doomed handles, exit with NOT_AVAIL. if (!aReturnDoomed) return NS_ERROR_NOT_AVAILABLE; } else { LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x " "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry)); } handle.forget(_retval); return NS_OK; } nsresult CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); MOZ_ASSERT(aHash); #ifdef DEBUG_HANDLES LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash))); #endif // find hash entry for key HandleHashKey *entry = mTable.PutEntry(*aHash); #ifdef DEBUG_HANDLES Log(entry); #endif #ifdef DEBUG entry->AssertHandlesState(); #endif nsRefPtr handle = new CacheFileHandle(entry->Hash(), aPriority); entry->AddHandle(handle); LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x " "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry)); handle.forget(_retval); return NS_OK; } void CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); MOZ_ASSERT(aHandle); if (!aHandle) return; #ifdef DEBUG_HANDLES LOG(("CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]" , aHandle, LOGSHA1(aHandle->Hash()))); #endif // find hash entry for key HandleHashKey *entry = mTable.GetEntry(*aHandle->Hash()); if (!entry) { MOZ_ASSERT(CacheFileIOManager::IsShutdown(), "Should find entry when removing a handle before shutdown"); LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " "no entries found", LOGSHA1(aHandle->Hash()))); return; } #ifdef DEBUG_HANDLES Log(entry); #endif LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " "removing handle %p", LOGSHA1(entry->Hash()), aHandle)); entry->RemoveHandle(aHandle); if (entry->IsEmpty()) { LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x " "list is empty, removing entry %p", LOGSHA1(entry->Hash()), entry)); mTable.RemoveEntry(*entry->Hash()); } } static PLDHashOperator GetAllHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure) { nsTArray > *array = static_cast > *>(aClosure); aEntry->GetHandles(*array); return PL_DHASH_NEXT; } void CacheFileHandles::GetAllHandles(nsTArray > *_retval) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); mTable.EnumerateEntries(&GetAllHandlesEnum, _retval); } void CacheFileHandles::ClearAll() { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); mTable.Clear(); } uint32_t CacheFileHandles::HandleCount() { return mTable.Count(); } #ifdef DEBUG_HANDLES void CacheFileHandles::Log(CacheFileHandlesEntry *entry) { LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry)); nsTArray > array; aEntry->GetHandles(array); for (uint32_t i = 0; i < array.Length(); ++i) { CacheFileHandle *handle = array[i]; handle->Log(); } LOG(("CacheFileHandles::Log() END [entry=%p]", entry)); } #endif // 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::SPECIAL_FILE)) { 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 { if (mFlags & CacheFileIOManager::SPECIAL_FILE) mRV = mIOMan->OpenSpecialFileInternal(mKey, mFlags, getter_AddRefs(mHandle)); else mRV = mIOMan->OpenFileInternal(&mHash, mFlags, getter_AddRefs(mHandle)); mIOMan = nullptr; if (mHandle) { MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get()); if (mHandle->Key().IsEmpty()) 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 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); if (!mCallback && mBuf) { free(const_cast(mBuf)); } } 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); else { free(const_cast(mBuf)); mBuf = nullptr; } 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; }; class RenameFileEvent : public nsRunnable { public: RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName, CacheFileIOListener *aCallback) : mHandle(aHandle) , mNewName(aNewName) , mCallback(aCallback) , mRV(NS_ERROR_FAILURE) { MOZ_COUNT_CTOR(RenameFileEvent); mTarget = static_cast(NS_GetCurrentThread()); } ~RenameFileEvent() { MOZ_COUNT_DTOR(RenameFileEvent); } NS_IMETHOD Run() { if (mTarget) { if (mHandle->IsClosed()) mRV = NS_ERROR_NOT_INITIALIZED; else mRV = CacheFileIOManager::gInstance->RenameFileInternal(mHandle, mNewName); nsCOMPtr target; mTarget.swap(target); target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); } else { if (mCallback) mCallback->OnFileRenamed(mHandle, mRV); } return NS_OK; } protected: nsRefPtr mHandle; nsCString mNewName; nsCOMPtr mCallback; nsCOMPtr mTarget; nsresult mRV; }; class InitIndexEntryEvent : public nsRunnable { public: InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId, bool aAnonymous, bool aInBrowser) : mHandle(aHandle) , mAppId(aAppId) , mAnonymous(aAnonymous) , mInBrowser(aInBrowser) { MOZ_COUNT_CTOR(InitIndexEntryEvent); } ~InitIndexEntryEvent() { MOZ_COUNT_DTOR(InitIndexEntryEvent); } NS_IMETHOD Run() { if (mHandle->IsClosed() || mHandle->IsDoomed()) return NS_OK; CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser); // We cannot set the filesize before we init the entry. If we're opening // an existing entry file, frecency and expiration time will be set after // parsing the entry file, but we must set the filesize here since nobody is // going to set it if there is no write to the file. uint32_t sizeInK = mHandle->FileSizeInK(); CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK); return NS_OK; } protected: nsRefPtr mHandle; uint32_t mAppId; bool mAnonymous; bool mInBrowser; }; class UpdateIndexEntryEvent : public nsRunnable { public: UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency, const uint32_t *aExpirationTime) : mHandle(aHandle) , mHasFrecency(false) , mHasExpirationTime(false) { MOZ_COUNT_CTOR(UpdateIndexEntryEvent); if (aFrecency) { mHasFrecency = true; mFrecency = *aFrecency; } if (aExpirationTime) { mHasExpirationTime = true; mExpirationTime = *aExpirationTime; } } ~UpdateIndexEntryEvent() { MOZ_COUNT_DTOR(UpdateIndexEntryEvent); } NS_IMETHOD Run() { if (mHandle->IsClosed() || mHandle->IsDoomed()) return NS_OK; CacheIndex::UpdateEntry(mHandle->Hash(), mHasFrecency ? &mFrecency : nullptr, mHasExpirationTime ? &mExpirationTime : nullptr, nullptr); return NS_OK; } protected: nsRefPtr mHandle; bool mHasFrecency; bool mHasExpirationTime; uint32_t mFrecency; uint32_t mExpirationTime; }; class MetadataWriteScheduleEvent : public nsRunnable { public: enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode; nsRefPtr mFile; nsRefPtr mIOMan; MetadataWriteScheduleEvent(CacheFileIOManager * aManager, CacheFile * aFile, EMode aMode) : mMode(aMode) , mFile(aFile) , mIOMan(aManager) { } virtual ~MetadataWriteScheduleEvent() { } NS_IMETHOD Run() { nsRefPtr ioMan = CacheFileIOManager::gInstance; if (!ioMan) { NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()"); return NS_OK; } switch (mMode) { case SCHEDULE: ioMan->ScheduleMetadataWriteInternal(mFile); break; case UNSCHEDULE: ioMan->UnscheduleMetadataWriteInternal(mFile); break; case SHUTDOWN: ioMan->ShutdownMetadataWriteSchedulingInternal(); break; } return NS_OK; } }; CacheFileIOManager * CacheFileIOManager::gInstance = nullptr; NS_IMPL_ISUPPORTS1(CacheFileIOManager, nsITimerCallback) CacheFileIOManager::CacheFileIOManager() : mShuttingDown(false) , mTreeCreated(false) , mOverLimitEvicting(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); return NS_OK; } nsresult CacheFileIOManager::Shutdown() { LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance)); MOZ_ASSERT(NS_IsMainThread()); if (!gInstance) return NS_ERROR_NOT_INITIALIZED; Telemetry::AutoTimer shutdownTimer; CacheIndex::PreShutdown(); ShutdownMetadataWriteScheduling(); { 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); if (gInstance->mIOThread) gInstance->mIOThread->Shutdown(); CacheIndex::Shutdown(); nsRefPtr ioMan; ioMan.swap(gInstance); return NS_OK; } nsresult CacheFileIOManager::ShutdownInternal() { LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this)); MOZ_ASSERT(mIOThread->IsCurrentThread()); // No new handles can be created after this flag is set mShuttingDown = true; // close all handles and delete all associated files nsTArray > handles; mHandles.GetAllHandles(&handles); handles.AppendElements(mSpecialHandles); for (uint32_t i=0 ; imClosed = true; h->Log(); // Close file handle if (h->mFD) { ReleaseNSPRHandleInternal(h); } // Remove file if entry is doomed or invalid if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) { LOG(("CacheFileIOManager::ShutdownInternal() - Removing file from disk")); h->mFile->Remove(false); } if (!h->IsSpecialFile() && !h->mIsDoomed && (h->mInvalid || !h->mFileExists)) { CacheIndex::RemoveEntry(h->Hash()); } // Remove the handle from mHandles/mSpecialHandles if (h->IsSpecialFile()) mSpecialHandles.RemoveElement(h); else mHandles.RemoveHandle(h); } // Assert the table is empty. When we are here, no new handles can be added // and handles will no longer remove them self from this table and we don't // want to keep invalid handles here. Also, there is no lookup after this // point to happen. MOZ_ASSERT(mHandles.HandleCount() == 0); return NS_OK; } nsresult CacheFileIOManager::OnProfile() { LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance)); nsRefPtr ioMan = gInstance; if (!ioMan) { // CacheFileIOManager::Init() failed, probably could not create the IO // thread, just go with it... return NS_ERROR_NOT_INITIALIZED; } nsresult rv; nsCOMPtr 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->Append(NS_LITERAL_STRING("cache2")); NS_ENSURE_SUCCESS(rv, rv); rv = directory->Clone(getter_AddRefs(ioMan->mCacheDirectory)); NS_ENSURE_SUCCESS(rv, rv); CacheIndex::Init(directory); } return NS_OK; } // static already_AddRefed CacheFileIOManager::IOTarget() { nsCOMPtr target; if (gInstance && gInstance->mIOThread) target = gInstance->mIOThread->Target(); return target.forget(); } // static already_AddRefed CacheFileIOManager::IOThread() { nsRefPtr thread; if (gInstance) thread = gInstance->mIOThread; return thread.forget(); } // static bool CacheFileIOManager::IsOnIOThread() { nsRefPtr ioMan = gInstance; if (ioMan && ioMan->mIOThread) return ioMan->mIOThread->IsCurrentThread(); return false; } // static bool CacheFileIOManager::IsOnIOThreadOrCeased() { nsRefPtr ioMan = gInstance; if (ioMan && ioMan->mIOThread) return ioMan->mIOThread->IsCurrentThread(); // Ceased... return true; } // static bool CacheFileIOManager::IsShutdown() { if (!gInstance) return true; return gInstance->mShuttingDown; } // static nsresult CacheFileIOManager::ScheduleMetadataWrite(CacheFile * aFile) { nsRefPtr ioMan = gInstance; NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); nsRefPtr event = new MetadataWriteScheduleEvent( ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE); nsCOMPtr target = ioMan->IOTarget(); NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); } nsresult CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile * aFile) { MOZ_ASSERT(IsOnIOThreadOrCeased()); nsresult rv; if (!mMetadataWritesTimer) { mMetadataWritesTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); NS_ENSURE_SUCCESS(rv, rv); rv = mMetadataWritesTimer->InitWithCallback( this, kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT); NS_ENSURE_SUCCESS(rv, rv); } if (mScheduledMetadataWrites.IndexOf(aFile) != mScheduledMetadataWrites.NoIndex) { return NS_OK; } mScheduledMetadataWrites.AppendElement(aFile); return NS_OK; } // static nsresult CacheFileIOManager::UnscheduleMetadataWrite(CacheFile * aFile) { nsRefPtr ioMan = gInstance; NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED); nsRefPtr event = new MetadataWriteScheduleEvent( ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE); nsCOMPtr target = ioMan->IOTarget(); NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); } nsresult CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile * aFile) { MOZ_ASSERT(IsOnIOThreadOrCeased()); mScheduledMetadataWrites.RemoveElement(aFile); if (mScheduledMetadataWrites.Length() == 0 && mMetadataWritesTimer) { mMetadataWritesTimer->Cancel(); mMetadataWritesTimer = nullptr; } return NS_OK; } // static nsresult CacheFileIOManager::ShutdownMetadataWriteScheduling() { nsRefPtr ioMan = gInstance; NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED); nsRefPtr event = new MetadataWriteScheduleEvent( ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN); nsCOMPtr target = ioMan->IOTarget(); NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); } nsresult CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() { MOZ_ASSERT(IsOnIOThreadOrCeased()); nsTArray > files; files.SwapElements(mScheduledMetadataWrites); for (uint32_t i = 0; i < files.Length(); ++i) { CacheFile * file = files[i]; file->WriteMetadataIfNeeded(); } if (mMetadataWritesTimer) { mMetadataWritesTimer->Cancel(); mMetadataWritesTimer = nullptr; } return NS_OK; } NS_IMETHODIMP CacheFileIOManager::Notify(nsITimer * aTimer) { MOZ_ASSERT(IsOnIOThreadOrCeased()); MOZ_ASSERT(mMetadataWritesTimer == aTimer); mMetadataWritesTimer = nullptr; nsTArray > files; files.SwapElements(mScheduledMetadataWrites); for (uint32_t i = 0; i < files.Length(); ++i) { CacheFile * file = files[i]; file->WriteMetadataIfNeeded(); } return NS_OK; } 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; nsRefPtr 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) { LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, " "flags=%d]", LOGSHA1(aHash), aFlags)); MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); nsresult rv; if (mShuttingDown) return NS_ERROR_NOT_INITIALIZED; if (!mTreeCreated) { rv = CreateCacheTree(); if (NS_FAILED(rv)) return rv; } nsCOMPtr file; rv = GetFile(aHash, getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr handle; mHandles.GetHandle(aHash, false, getter_AddRefs(handle)); if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == 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) { CacheIndex::RemoveEntry(aHash); LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from " "disk")); rv = file->Remove(false); if (NS_FAILED(rv)) { NS_WARNING("Cannot remove old entry from the disk"); LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed" ". [rv=0x%08x]", rv)); } } CacheIndex::AddEntry(aHash); handle->mFile.swap(file); handle->mFileSize = 0; } if (handle) { handle.swap(*_retval); return NS_OK; } bool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) return NS_ERROR_NOT_AVAILABLE; 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; CacheIndex::EnsureEntryExists(aHash); } else { handle->mFileSize = 0; CacheIndex::AddEntry(aHash); } handle->mFile.swap(file); handle.swap(*_retval); return NS_OK; } nsresult CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey, uint32_t aFlags, CacheFileHandle **_retval) { LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]", PromiseFlatCString(aKey).get(), aFlags)); MOZ_ASSERT(CacheFileIOManager::IsOnIOThread()); nsresult rv; if (mShuttingDown) return NS_ERROR_NOT_INITIALIZED; if (!mTreeCreated) { rv = CreateCacheTree(); if (NS_FAILED(rv)) return rv; } nsCOMPtr file; rv = GetSpecialFile(aKey, getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr handle; for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) { if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) { handle = mSpecialHandles[i]; break; } } if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) { if (handle) { rv = DoomFileInternal(handle); NS_ENSURE_SUCCESS(rv, rv); handle = nullptr; } handle = new CacheFileHandle(aKey, aFlags & PRIORITY); mSpecialHandles.AppendElement(handle); bool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from " "disk")); rv = file->Remove(false); if (NS_FAILED(rv)) { NS_WARNING("Cannot remove old entry from the disk"); LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file " "failed. [rv=0x%08x]", rv)); } } handle->mFile.swap(file); handle->mFileSize = 0; } if (handle) { handle.swap(*_retval); return NS_OK; } bool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) return NS_ERROR_NOT_AVAILABLE; handle = new CacheFileHandle(aKey, aFlags & PRIORITY); mSpecialHandles.AppendElement(handle); 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::CloseHandleInternal(CacheFileHandle *aHandle) { LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle)); aHandle->Log(); MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); // Close file handle if (aHandle->mFD) { ReleaseNSPRHandleInternal(aHandle); } // Delete the file if the entry was doomed or invalid if (aHandle->mIsDoomed || aHandle->mInvalid) { LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from " "disk")); aHandle->mFile->Remove(false); } if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed && (aHandle->mInvalid || !aHandle->mFileExists)) { CacheIndex::RemoveEntry(aHandle->Hash()); } // Remove the handle from hashtable if (aHandle->IsSpecialFile()) mSpecialHandles.RemoveElement(aHandle); else if (!mShuttingDown) // Don't touch after shutdown 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; nsRefPtr 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) { LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%lld, count=%d]", aHandle, aOffset, 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; nsRefPtr 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) { LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%lld, count=%d, " "validate=%d]", aHandle, aOffset, aCount, 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 (!aHandle->IsDoomed() && !aHandle->IsSpecialFile()) { uint32_t size = aHandle->FileSizeInK(); CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, &size); EvictIfOverLimitInternal(); } } 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; nsRefPtr 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::OPEN_PRIORITY : CacheIOThread::OPEN); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle) { LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle)); aHandle->Log(); 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); } } if (!aHandle->IsSpecialFile()) CacheIndex::RemoveEntry(aHandle->Hash()); aHandle->mIsDoomed = true; if (!aHandle->IsSpecialFile()) { nsRefPtr storageService = CacheStorageService::Self(); if (storageService) { nsAutoCString url; nsCOMPtr info; rv = CacheFileUtils::ParseKey(aHandle->Key(), getter_AddRefs(info), &url); MOZ_ASSERT(NS_SUCCEEDED(rv)); if (NS_SUCCEEDED(rv)) { storageService->CacheFileDoomed(info, url); } } } return NS_OK; } nsresult CacheFileIOManager::DoomFileByKey(const nsACString &aKey, CacheFileIOListener *aCallback) { LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]", PromiseFlatCString(aKey).get(), aCallback)); nsresult rv; nsRefPtr ioMan = gInstance; if (!ioMan) return NS_ERROR_NOT_INITIALIZED; nsRefPtr ev = new DoomFileByKeyEvent(aKey, aCallback); rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::OPEN); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash) { LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]" , LOGSHA1(aHash))); MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); nsresult rv; if (mShuttingDown) return NS_ERROR_NOT_INITIALIZED; if (!mCacheDirectory) return NS_ERROR_FILE_INVALID_PATH; // Find active handle nsRefPtr handle; mHandles.GetHandle(aHash, true, getter_AddRefs(handle)); if (handle) { handle->Log(); 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; LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from " "disk")); rv = file->Remove(false); if (NS_FAILED(rv)) { NS_WARNING("Cannot remove old entry from the disk"); LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. " "[rv=0x%08x]", rv)); } CacheIndex::RemoveEntry(aHash); return NS_OK; } nsresult CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle) { LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle)); nsresult rv; nsRefPtr 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) { LOG(("CacheFileIOManager::ReleaseNSPRHandleInternal() [handle=%p]", aHandle)); MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); 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; nsRefPtr 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; } // static void CacheFileIOManager::GetCacheDirectory(nsIFile** result) { *result = nullptr; nsRefPtr ioMan = gInstance; if (!ioMan) return; nsCOMPtr file = ioMan->mCacheDirectory; file.forget(result); } 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; } #else MOZ_ASSERT(false, "Not implemented!"); #endif return NS_OK; } nsresult CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle, int64_t aTruncatePos, int64_t aEOFPos) { LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, " "truncatePos=%lld, EOFPos=%lld]", aHandle, aTruncatePos, 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::RenameFile(CacheFileHandle *aHandle, const nsACString &aNewName, CacheFileIOListener *aCallback) { LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]", aHandle, PromiseFlatCString(aNewName).get(), aCallback)); nsresult rv; nsRefPtr ioMan = gInstance; if (aHandle->IsClosed() || !ioMan) return NS_ERROR_NOT_INITIALIZED; if (!aHandle->IsSpecialFile()) return NS_ERROR_UNEXPECTED; nsRefPtr ev = new RenameFileEvent(aHandle, aNewName, aCallback); rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CacheFileIOManager::RenameFileInternal(CacheFileHandle *aHandle, const nsACString &aNewName) { LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]", aHandle, PromiseFlatCString(aNewName).get())); nsresult rv; MOZ_ASSERT(aHandle->IsSpecialFile()); if (aHandle->IsDoomed()) return NS_ERROR_NOT_AVAILABLE; // Doom old handle if it exists and is not doomed for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) { if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aNewName) { MOZ_ASSERT(aHandle != mSpecialHandles[i]); rv = DoomFileInternal(mSpecialHandles[i]); NS_ENSURE_SUCCESS(rv, rv); break; } } nsCOMPtr file; rv = GetSpecialFile(aNewName, getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); bool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file from " "disk")); rv = file->Remove(false); if (NS_FAILED(rv)) { NS_WARNING("Cannot remove file from the disk"); LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file failed" ". [rv=0x%08x]", rv)); } } if (!aHandle->FileExists()) { aHandle->mKey = aNewName; return NS_OK; } if (aHandle->mFD) ReleaseNSPRHandleInternal(aHandle); rv = aHandle->mFile->MoveToNative(nullptr, aNewName); NS_ENSURE_SUCCESS(rv, rv); aHandle->mKey = aNewName; return NS_OK; } nsresult CacheFileIOManager::EvictIfOverLimit() { LOG(("CacheFileIOManager::EvictIfOverLimit()")); nsresult rv; nsRefPtr ioMan = gInstance; if (!ioMan) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr ev; ev = NS_NewRunnableMethod(ioMan, &CacheFileIOManager::EvictIfOverLimitInternal); rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CacheFileIOManager::EvictIfOverLimitInternal() { LOG(("CacheFileIOManager::EvictIfOverLimitInternal()")); nsresult rv; MOZ_ASSERT(mIOThread->IsCurrentThread()); if (mShuttingDown) return NS_ERROR_NOT_INITIALIZED; if (mOverLimitEvicting) { LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already " "running.")); return NS_OK; } uint32_t cacheUsage; rv = CacheIndex::GetCacheSize(&cacheUsage); NS_ENSURE_SUCCESS(rv, rv); uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10; if (cacheUsage <= cacheLimit) { LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size under " "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); return NS_OK; } LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded " "limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); nsCOMPtr ev; ev = NS_NewRunnableMethod(this, &CacheFileIOManager::OverLimitEvictionInternal); rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); NS_ENSURE_SUCCESS(rv, rv); mOverLimitEvicting = true; return NS_OK; } nsresult CacheFileIOManager::OverLimitEvictionInternal() { LOG(("CacheFileIOManager::OverLimitEvictionInternal()")); nsresult rv; // mOverLimitEvicting is accessed only on IO thread, so we can set it to false // here and set ti to true again once we dispatch another event that will // continue with the eviction. The reason why we do so is that we can fail // early anywhere in this method and the variable will contain a correct // value. Otherwise we would need to set it to false on every failing place. mOverLimitEvicting = false; if (mShuttingDown) return NS_ERROR_NOT_INITIALIZED; TimeStamp start; while (true) { uint32_t cacheUsage; rv = CacheIndex::GetCacheSize(&cacheUsage); NS_ENSURE_SUCCESS(rv, rv); uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10; if (cacheUsage <= cacheLimit) { LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size under " "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); return NS_OK; } LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over " "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit)); if (start.IsNull()) { start = TimeStamp::NowLoRes(); } else { TimeDuration elapsed = TimeStamp::NowLoRes() - start; if (elapsed.ToMilliseconds() >= kEvictionLoopLimit) { LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop " "after %u ms.", static_cast(elapsed.ToMilliseconds()))); break; } } SHA1Sum::Hash hash; uint32_t cnt; static uint32_t consecutiveFailures = 0; rv = CacheIndex::GetEntryForEviction(&hash, &cnt); NS_ENSURE_SUCCESS(rv, rv); rv = DoomFileByKeyInternal(&hash); if (NS_SUCCEEDED(rv)) { consecutiveFailures = 0; } else if (rv == NS_ERROR_NOT_AVAILABLE) { LOG(("CacheFileIOManager::OverLimitEvictionInternal() - " "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv)); // TODO index is outdated, start update // Make sure index won't return the same entry again CacheIndex::RemoveEntry(&hash); consecutiveFailures = 0; } else { // This shouldn't normally happen, but the eviction must not fail // completely if we ever encounter this problem. NS_WARNING("CacheFileIOManager::OverLimitEvictionInternal() - Unexpected " "failure of DoomFileByKeyInternal()"); LOG(("CacheFileIOManager::OverLimitEvictionInternal() - " "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv)); // Normally, CacheIndex::UpdateEntry() is called only to update newly // created/opened entries which are always fresh and UpdateEntry() expects // and checks this flag. The way we use UpdateEntry() here is a kind of // hack and we must make sure the flag is set by calling // EnsureEntryExists(). rv = CacheIndex::EnsureEntryExists(&hash); NS_ENSURE_SUCCESS(rv, rv); // Move the entry at the end of both lists to make sure we won't end up // failing on one entry forever. uint32_t frecency = 0; uint32_t expTime = nsICacheEntry::NO_EXPIRATION_TIME; rv = CacheIndex::UpdateEntry(&hash, &frecency, &expTime, nullptr); NS_ENSURE_SUCCESS(rv, rv); consecutiveFailures++; if (consecutiveFailures >= cnt) { // This doesn't necessarily mean that we've tried to doom every entry // but we've reached a sane number of tries. It is likely that another // eviction will start soon. And as said earlier, this normally doesn't // happen at all. return NS_OK; } } } nsCOMPtr ev; ev = NS_NewRunnableMethod(this, &CacheFileIOManager::OverLimitEvictionInternal); rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT); NS_ENSURE_SUCCESS(rv, rv); mOverLimitEvicting = true; return NS_OK; } nsresult CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle, uint32_t aAppId, bool aAnonymous, bool aInBrowser) { LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d" ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser)); nsresult rv; nsRefPtr ioMan = gInstance; if (aHandle->IsClosed() || !ioMan) return NS_ERROR_NOT_INITIALIZED; if (aHandle->IsSpecialFile()) return NS_ERROR_UNEXPECTED; nsRefPtr ev = new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser); rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle *aHandle, const uint32_t *aFrecency, const uint32_t *aExpirationTime) { LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, " "expirationTime=%s]", aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "", aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : "")); nsresult rv; nsRefPtr ioMan = gInstance; if (aHandle->IsClosed() || !ioMan) return NS_ERROR_NOT_INITIALIZED; if (aHandle->IsSpecialFile()) return NS_ERROR_UNEXPECTED; nsRefPtr ev = new UpdateIndexEntryEvent(aHandle, aFrecency, aExpirationTime); 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; nsRefPtr 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(kEntriesDir)); NS_ENSURE_SUCCESS(rv, rv); break; case DOOMED: rv = file->AppendNative(NS_LITERAL_CSTRING(kDoomedDir)); 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; } nsresult CacheFileIOManager::CreateFile(CacheFileHandle *aHandle) { MOZ_ASSERT(!aHandle->mFD); MOZ_ASSERT(aHandle->mFile); nsresult rv; if (aHandle->IsDoomed()) { nsCOMPtr file; rv = GetDoomedFile(getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); aHandle->mFile.swap(file); } else { bool exists; if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) { NS_WARNING("Found a file that should not exist!"); } } rv = OpenNSPRHandle(aHandle, true); NS_ENSURE_SUCCESS(rv, rv); aHandle->mFileSize = 0; return NS_OK; } void CacheFileIOManager::HashToStr(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::StrToHash(const nsACString &aHash, SHA1Sum::Hash *_retval) { if (aHash.Length() != 2*sizeof(SHA1Sum::Hash)) return NS_ERROR_INVALID_ARG; for (uint32_t i=0 ; i= '0' && aHash[i] <= '9') value = aHash[i] - '0'; else if (aHash[i] >= 'A' && aHash[i] <= 'F') value = aHash[i] - 'A' + 10; else if (aHash[i] >= 'a' && aHash[i] <= 'f') value = aHash[i] - 'a' + 10; else return NS_ERROR_INVALID_ARG; if (i%2 == 0) (reinterpret_cast(_retval))[i/2] = value << 4; else (reinterpret_cast(_retval))[i/2] += value; } return NS_OK; } 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(kEntriesDir)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString leafName; HashToStr(aHash, leafName); rv = file->AppendNative(leafName); NS_ENSURE_SUCCESS(rv, rv); file.swap(*_retval); return NS_OK; } nsresult CacheFileIOManager::GetSpecialFile(const nsACString &aKey, nsIFile **_retval) { nsresult rv; nsCOMPtr file; rv = mCacheDirectory->Clone(getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); rv = file->AppendNative(aKey); NS_ENSURE_SUCCESS(rv, rv); file.swap(*_retval); return NS_OK; } nsresult CacheFileIOManager::GetDoomedFile(nsIFile **_retval) { nsresult rv; nsCOMPtr file; rv = mCacheDirectory->Clone(getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); rv = file->AppendNative(NS_LITERAL_CSTRING(kDoomedDir)); 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, kEntriesDir); NS_ENSURE_SUCCESS(rv, rv); // ensure doomed directory exists rv = CheckAndCreateDir(mCacheDirectory, kDoomedDir); NS_ENSURE_SUCCESS(rv, rv); mTreeCreated = true; return NS_OK; } nsresult CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); MOZ_ASSERT(!aHandle->mFD); MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex); MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit); MOZ_ASSERT((aCreate && !aHandle->mFileExists) || (!aCreate && aHandle->mFileExists)); nsresult rv; if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) { // close handle that hasn't been used for the longest time rv = 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; return DoomFileInternal(aHandle); } NS_ENSURE_SUCCESS(rv, rv); } mHandlesByLastUsed.AppendElement(aHandle); return NS_OK; } void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle) { MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); MOZ_ASSERT(aHandle->mFD); DebugOnly found; found = mHandlesByLastUsed.RemoveElement(aHandle); MOZ_ASSERT(found); mHandlesByLastUsed.AppendElement(aHandle); } } // net } // mozilla