michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsICache.h" michael@0: #include "nsCache.h" michael@0: #include "nsCacheService.h" michael@0: #include "nsCacheEntryDescriptor.h" michael@0: #include "nsCacheEntry.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsCRT.h" michael@0: #include "nsThreadUtils.h" michael@0: #include michael@0: michael@0: #define kMinDecompressReadBufLen 1024 michael@0: #define kMinCompressWriteBufLen 1024 michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsAsyncDoomEvent michael@0: *****************************************************************************/ michael@0: michael@0: class nsAsyncDoomEvent : public nsRunnable { michael@0: public: michael@0: nsAsyncDoomEvent(nsCacheEntryDescriptor *descriptor, michael@0: nsICacheListener *listener) michael@0: { michael@0: mDescriptor = descriptor; michael@0: mListener = listener; michael@0: mThread = do_GetCurrentThread(); michael@0: // We addref the listener here and release it in nsNotifyDoomListener michael@0: // on the callers thread. If posting of nsNotifyDoomListener event fails michael@0: // we leak the listener which is better than releasing it on a wrong michael@0: // thread. michael@0: NS_IF_ADDREF(mListener); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsresult status = NS_OK; michael@0: michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSASYNCDOOMEVENT_RUN)); michael@0: michael@0: if (mDescriptor->mCacheEntry) { michael@0: status = nsCacheService::gService->DoomEntry_Internal( michael@0: mDescriptor->mCacheEntry, true); michael@0: } else if (!mDescriptor->mDoomedOnClose) { michael@0: status = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: if (mListener) { michael@0: mThread->Dispatch(new nsNotifyDoomListener(mListener, status), michael@0: NS_DISPATCH_NORMAL); michael@0: // posted event will release the reference on the correct thread michael@0: mListener = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mDescriptor; michael@0: nsICacheListener *mListener; michael@0: nsCOMPtr mThread; michael@0: }; michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCacheEntryDescriptor, michael@0: nsICacheEntryDescriptor, michael@0: nsICacheEntryInfo) michael@0: michael@0: nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry, michael@0: nsCacheAccessMode accessGranted) michael@0: : mCacheEntry(entry), michael@0: mAccessGranted(accessGranted), michael@0: mOutputWrapper(nullptr), michael@0: mLock("nsCacheEntryDescriptor.mLock"), michael@0: mAsyncDoomPending(false), michael@0: mDoomedOnClose(false), michael@0: mClosingDescriptor(false) michael@0: { michael@0: PR_INIT_CLIST(this); michael@0: NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor michael@0: } michael@0: michael@0: michael@0: nsCacheEntryDescriptor::~nsCacheEntryDescriptor() michael@0: { michael@0: // No need to close if the cache entry has already been severed. This michael@0: // helps avoid a shutdown assertion (bug 285519) that is caused when michael@0: // consumers end up holding onto these objects past xpcom-shutdown. It's michael@0: // okay for them to do that because the cache service calls our Close michael@0: // method during xpcom-shutdown, so we don't need to complain about it. michael@0: if (mCacheEntry) michael@0: Close(); michael@0: michael@0: NS_ASSERTION(mInputWrappers.Count() == 0, michael@0: "We have still some input wrapper!"); michael@0: NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!"); michael@0: michael@0: nsCacheService * service = nsCacheService::GlobalInstance(); michael@0: NS_RELEASE(service); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetClientID(char ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return ClientIDFromCacheKey(*(mCacheEntry->Key()), result); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDeviceID); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: const char* deviceID = mCacheEntry->GetDeviceID(); michael@0: if (!deviceID) { michael@0: *aDeviceID = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aDeviceID = NS_strdup(deviceID); michael@0: return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetKey(nsACString &result) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETKEY)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetFetchCount(int32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->FetchCount(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetLastFetched(uint32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->LastFetched(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetLastModified(uint32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTMODIFIED)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->LastModified(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetExpirationTime(uint32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETEXPIRATIONTIME)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->ExpirationTime(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetExpirationTime(uint32_t expirationTime) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETEXPIRATIONTIME)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mCacheEntry->SetExpirationTime(expirationTime); michael@0: mCacheEntry->MarkEntryDirty(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(bool *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_ISSTREAMBASED)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->IsStreamData(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(int64_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->PredictedDataSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(int64_t michael@0: predictedSize) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mCacheEntry->SetPredictedDataSize(predictedSize); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(uint32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDATASIZE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: const char* val = mCacheEntry->GetMetaDataElement("uncompressed-len"); michael@0: if (!val) { michael@0: *result = mCacheEntry->DataSize(); michael@0: } else { michael@0: *result = atol(val); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor::GetStorageDataSize(uint32_t *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEDATASIZE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->DataSize(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsCacheEntryDescriptor::RequestDataSizeChange(int32_t deltaSize) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_REQUESTDATASIZECHANGE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult rv; michael@0: rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // XXX review for signed/unsigned math errors michael@0: uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize; michael@0: mCacheEntry->SetDataSize(newDataSize); michael@0: mCacheEntry->TouchData(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetDataSize(uint32_t dataSize) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETDATASIZE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // XXX review for signed/unsigned math errors michael@0: int32_t deltaSize = dataSize - mCacheEntry->DataSize(); michael@0: michael@0: nsresult rv; michael@0: rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize); michael@0: // this had better be NS_OK, this call instance is advisory for memory cache objects michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // XXX review for signed/unsigned math errors michael@0: uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize; michael@0: mCacheEntry->SetDataSize(newDataSize); michael@0: mCacheEntry->TouchData(); michael@0: } else { michael@0: NS_WARNING("failed SetDataSize() on memory cache object!"); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::OpenInputStream(uint32_t offset, nsIInputStream ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: nsInputStreamWrapper* cacheInput = nullptr; michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; michael@0: michael@0: // Don't open any new stream when closing descriptor or clearing entries michael@0: if (mClosingDescriptor || nsCacheService::GetClearingEntries()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // ensure valid permissions michael@0: if (!(mAccessGranted & nsICache::ACCESS_READ)) michael@0: return NS_ERROR_CACHE_READ_ACCESS_DENIED; michael@0: michael@0: const char *val; michael@0: val = mCacheEntry->GetMetaDataElement("uncompressed-len"); michael@0: if (val) { michael@0: cacheInput = new nsDecompressInputStreamWrapper(this, offset); michael@0: } else { michael@0: cacheInput = new nsInputStreamWrapper(this, offset); michael@0: } michael@0: if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mInputWrappers.AppendElement(cacheInput); michael@0: } michael@0: michael@0: NS_ADDREF(*result = cacheInput); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset, nsIOutputStream ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: nsOutputStreamWrapper* cacheOutput = nullptr; michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM; michael@0: michael@0: // Don't open any new stream when closing descriptor or clearing entries michael@0: if (mClosingDescriptor || nsCacheService::GetClearingEntries()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // ensure valid permissions michael@0: if (!(mAccessGranted & nsICache::ACCESS_WRITE)) michael@0: return NS_ERROR_CACHE_WRITE_ACCESS_DENIED; michael@0: michael@0: int32_t compressionLevel = nsCacheService::CacheCompressionLevel(); michael@0: const char *val; michael@0: val = mCacheEntry->GetMetaDataElement("uncompressed-len"); michael@0: if ((compressionLevel > 0) && val) { michael@0: cacheOutput = new nsCompressOutputStreamWrapper(this, offset); michael@0: } else { michael@0: // clear compression flag when compression disabled - see bug 715198 michael@0: if (val) { michael@0: mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr); michael@0: } michael@0: cacheOutput = new nsOutputStreamWrapper(this, offset); michael@0: } michael@0: if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mOutputWrapper = cacheOutput; michael@0: } michael@0: michael@0: NS_ADDREF(*result = cacheOutput); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCACHEELEMENT)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM; michael@0: michael@0: NS_IF_ADDREF(*result = mCacheEntry->Data()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetCacheElement(nsISupports * cacheElement) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETCACHEELEMENT)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM; michael@0: michael@0: return nsCacheService::SetCacheElement(mCacheEntry, cacheElement); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: *result = mAccessGranted; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEPOLICY)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->StoragePolicy(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSTORAGEPOLICY)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: // XXX validate policy against session? michael@0: michael@0: bool storageEnabled = false; michael@0: storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy); michael@0: if (!storageEnabled) return NS_ERROR_FAILURE; michael@0: michael@0: // Don't change the storage policy of entries we can't write michael@0: if (!(mAccessGranted & nsICache::ACCESS_WRITE)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // Don't allow a cache entry to move from memory-only to anything else michael@0: if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY && michael@0: policy != nsICache::STORE_IN_MEMORY) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mCacheEntry->SetStoragePolicy(policy); michael@0: mCacheEntry->MarkEntryDirty(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetFile(nsIFile ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFILE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return nsCacheService::GetFileForEntry(mCacheEntry, result); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetSecurityInfo(nsISupports ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSECURITYINFO)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->SecurityInfo(); michael@0: NS_IF_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetSecurityInfo(nsISupports * securityInfo) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSECURITYINFO)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mCacheEntry->SetSecurityInfo(securityInfo); michael@0: mCacheEntry->MarkEntryDirty(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::Doom() michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOM)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return nsCacheService::DoomEntry(mCacheEntry); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOMANDFAILPENDINGREQUESTS)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::AsyncDoom(nsICacheListener *listener) michael@0: { michael@0: bool asyncDoomPending; michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: asyncDoomPending = mAsyncDoomPending; michael@0: mAsyncDoomPending = true; michael@0: } michael@0: michael@0: if (asyncDoomPending) { michael@0: // AsyncDoom was already called. Notify listener if it is non-null, michael@0: // otherwise just return success. michael@0: if (listener) { michael@0: nsresult rv = NS_DispatchToCurrentThread( michael@0: new nsNotifyDoomListener(listener, NS_ERROR_NOT_AVAILABLE)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: NS_IF_ADDREF(listener); michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr event = new nsAsyncDoomEvent(this, listener); michael@0: return nsCacheService::DispatchToCacheIOThread(event); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::MarkValid() michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_MARKVALID)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult rv = nsCacheService::ValidateEntry(mCacheEntry); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::Close() michael@0: { michael@0: nsRefPtr outputWrapper; michael@0: nsTArray > inputWrappers; michael@0: michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // Make sure no other stream can be opened michael@0: mClosingDescriptor = true; michael@0: outputWrapper = mOutputWrapper; michael@0: for (int32_t i = 0 ; i < mInputWrappers.Count() ; i++) michael@0: inputWrappers.AppendElement(static_cast( michael@0: mInputWrappers[i])); michael@0: } michael@0: michael@0: // Call Close() on the streams outside the lock since it might need to call michael@0: // methods that grab the cache service lock, e.g. compressed output stream michael@0: // when it finalizes the entry michael@0: if (outputWrapper) { michael@0: if (NS_FAILED(outputWrapper->Close())) { michael@0: NS_WARNING("Dooming entry because Close() failed!!!"); michael@0: Doom(); michael@0: } michael@0: outputWrapper = nullptr; michael@0: } michael@0: michael@0: for (uint32_t i = 0 ; i < inputWrappers.Length() ; i++) michael@0: inputWrappers[i]->Close(); michael@0: michael@0: inputWrappers.Clear(); michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE)); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // XXX perhaps closing descriptors should clear/sever transports michael@0: michael@0: // tell nsCacheService we're going away michael@0: nsCacheService::CloseDescriptor(this); michael@0: NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::GetMetaDataElement(const char *key, char **result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(key); michael@0: *result = nullptr; michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETMETADATAELEMENT)); michael@0: NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: const char *value; michael@0: michael@0: value = mCacheEntry->GetMetaDataElement(key); michael@0: if (!value) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = NS_strdup(value); michael@0: if (!*result) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::SetMetaDataElement(const char *key, const char *value) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(key); michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETMETADATAELEMENT)); michael@0: NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: // XXX allow null value, for clearing key? michael@0: michael@0: nsresult rv = mCacheEntry->SetMetaDataElement(key, value); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mCacheEntry->TouchMetaData(); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor * visitor) michael@0: { michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_VISITMETADATA)); michael@0: // XXX check callers, we're calling out of module michael@0: NS_ENSURE_ARG_POINTER(visitor); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return mCacheEntry->VisitMetaDataElements(visitor); michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsCacheInputStream - a wrapper for nsIInputStream keeps the cache entry michael@0: * open while referenced. michael@0: ******************************************************************************/ michael@0: michael@0: NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsInputStreamWrapper) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsCacheEntryDescriptor::nsInputStreamWrapper::Release() michael@0: { michael@0: // Holding a reference to descriptor ensures that cache service won't go michael@0: // away. Do not grab cache service lock if there is no descriptor. michael@0: nsRefPtr desc; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: desc = mDescriptor; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_RELEASE)); michael@0: michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsInputStreamWrapper"); michael@0: michael@0: if (0 == count) { michael@0: // don't use desc here since mDescriptor might be already nulled out michael@0: if (mDescriptor) { michael@0: NS_ASSERTION(mDescriptor->mInputWrappers.IndexOf(this) != -1, michael@0: "Wrapper not found in array!"); michael@0: mDescriptor->mInputWrappers.RemoveElement(this); michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: mRefCnt = 1; michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsInputStreamWrapper) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::LazyInit() michael@0: { michael@0: // Check if we have the descriptor. If not we can't even grab the cache michael@0: // lock since it is not ensured that the cache service still exists. michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT)); michael@0: michael@0: nsCacheAccessMode mode; michael@0: nsresult rv = mDescriptor->GetAccessGranted(&mode); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCacheEntry* cacheEntry = mDescriptor->CacheEntry(); michael@0: if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode, michael@0: mStartOffset, michael@0: getter_AddRefs(mInput)); michael@0: michael@0: CACHE_LOG_DEBUG(("nsInputStreamWrapper::LazyInit " michael@0: "[entry=%p, wrapper=%p, mInput=%p, rv=%d]", michael@0: mDescriptor, this, mInput.get(), int(rv))); michael@0: michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mInitialized = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::EnsureInit() michael@0: { michael@0: if (mInitialized) { michael@0: NS_ASSERTION(mDescriptor, "Bad state"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return LazyInit(); michael@0: } michael@0: michael@0: void nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::CloseInternal() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: if (!mDescriptor) { michael@0: NS_ASSERTION(!mInitialized, "Bad state"); michael@0: NS_ASSERTION(!mInput, "Bad state"); michael@0: return; michael@0: } michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL)); michael@0: michael@0: if (mDescriptor) { michael@0: mDescriptor->mInputWrappers.RemoveElement(this); michael@0: nsCacheService::ReleaseObject_Locked(mDescriptor); michael@0: mDescriptor = nullptr; michael@0: } michael@0: mInitialized = false; michael@0: mInput = nullptr; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::Close() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: return Close_Locked(); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::Close_Locked() michael@0: { michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mInput->Close(); michael@0: } else { michael@0: NS_ASSERTION(!mInput, michael@0: "Shouldn't have mInput when EnsureInit() failed"); michael@0: } michael@0: michael@0: // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are michael@0: // closing streams with nsCacheService::CloseAllStream() michael@0: CloseInternal(); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::Available(uint64_t *avail) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return mInput->Available(avail); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::Read(char *buf, uint32_t count, uint32_t *countRead) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: return Read_Locked(buf, count, countRead); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::Read_Locked(char *buf, uint32_t count, uint32_t *countRead) michael@0: { michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mInput->Read(buf, count, countRead); michael@0: michael@0: CACHE_LOG_DEBUG(("nsInputStreamWrapper::Read " michael@0: "[entry=%p, wrapper=%p, mInput=%p, rv=%d]", michael@0: mDescriptor, this, mInput.get(), rv)); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: // cache stream not buffered michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsInputStreamWrapper::IsNonBlocking(bool *result) michael@0: { michael@0: // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK michael@0: *result = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDecompressInputStreamWrapper - an input stream wrapper that decompresses michael@0: ******************************************************************************/ michael@0: michael@0: NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Release() michael@0: { michael@0: // Holding a reference to descriptor ensures that cache service won't go michael@0: // away. Do not grab cache service lock if there is no descriptor. michael@0: nsRefPtr desc; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: desc = mDescriptor; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Lock(LOCK_TELEM( michael@0: NSDECOMPRESSINPUTSTREAMWRAPPER_RELEASE)); michael@0: michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, michael@0: "nsCacheEntryDescriptor::nsDecompressInputStreamWrapper"); michael@0: michael@0: if (0 == count) { michael@0: // don't use desc here since mDescriptor might be already nulled out michael@0: if (mDescriptor) { michael@0: NS_ASSERTION(mDescriptor->mInputWrappers.IndexOf(this) != -1, michael@0: "Wrapper not found in array!"); michael@0: mDescriptor->mInputWrappers.RemoveElement(this); michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: mRefCnt = 1; michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsDecompressInputStreamWrapper::Read(char * buf, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: int zerr = Z_OK; michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mStreamInitialized) { michael@0: rv = InitZstream(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: mZstream.next_out = (Bytef*)buf; michael@0: mZstream.avail_out = count; michael@0: michael@0: if (mReadBufferLen < count) { michael@0: // Allocate a buffer for reading from the input stream. This will michael@0: // determine the max number of compressed bytes read from the michael@0: // input stream at one time. Making the buffer size proportional michael@0: // to the request size is not necessary, but helps minimize the michael@0: // number of read requests to the input stream. michael@0: uint32_t newBufLen = std::max(count, (uint32_t)kMinDecompressReadBufLen); michael@0: unsigned char* newBuf; michael@0: newBuf = (unsigned char*)nsMemory::Realloc(mReadBuffer, michael@0: newBufLen); michael@0: if (newBuf) { michael@0: mReadBuffer = newBuf; michael@0: mReadBufferLen = newBufLen; michael@0: } michael@0: if (!mReadBuffer) { michael@0: mReadBufferLen = 0; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: // read and inflate data until the output buffer is full, or michael@0: // there is no more data to read michael@0: while (NS_SUCCEEDED(rv) && michael@0: zerr == Z_OK && michael@0: mZstream.avail_out > 0 && michael@0: count > 0) { michael@0: if (mZstream.avail_in == 0) { michael@0: rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer, michael@0: mReadBufferLen, michael@0: &mZstream.avail_in); michael@0: if (NS_FAILED(rv) || !mZstream.avail_in) { michael@0: break; michael@0: } michael@0: mZstream.next_in = mReadBuffer; michael@0: } michael@0: zerr = inflate(&mZstream, Z_NO_FLUSH); michael@0: if (zerr == Z_STREAM_END) { michael@0: // The compressed data may have been stored in multiple michael@0: // chunks/streams. To allow for this case, re-initialize michael@0: // the inflate stream and continue decompressing from michael@0: // the next byte. michael@0: Bytef * saveNextIn = mZstream.next_in; michael@0: unsigned int saveAvailIn = mZstream.avail_in; michael@0: Bytef * saveNextOut = mZstream.next_out; michael@0: unsigned int saveAvailOut = mZstream.avail_out; michael@0: inflateReset(&mZstream); michael@0: mZstream.next_in = saveNextIn; michael@0: mZstream.avail_in = saveAvailIn; michael@0: mZstream.next_out = saveNextOut; michael@0: mZstream.avail_out = saveAvailOut; michael@0: zerr = Z_OK; michael@0: } else if (zerr != Z_OK) { michael@0: rv = NS_ERROR_INVALID_CONTENT_ENCODING; michael@0: } michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *countRead = count - mZstream.avail_out; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsDecompressInputStreamWrapper::Close() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: EndZstream(); michael@0: if (mReadBuffer) { michael@0: nsMemory::Free(mReadBuffer); michael@0: mReadBuffer = 0; michael@0: mReadBufferLen = 0; michael@0: } michael@0: return nsInputStreamWrapper::Close_Locked(); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsDecompressInputStreamWrapper::InitZstream() michael@0: { michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (mStreamEnded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Initialize zlib inflate stream michael@0: mZstream.zalloc = Z_NULL; michael@0: mZstream.zfree = Z_NULL; michael@0: mZstream.opaque = Z_NULL; michael@0: mZstream.next_out = Z_NULL; michael@0: mZstream.avail_out = 0; michael@0: mZstream.next_in = Z_NULL; michael@0: mZstream.avail_in = 0; michael@0: if (inflateInit(&mZstream) != Z_OK) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mStreamInitialized = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsDecompressInputStreamWrapper::EndZstream() michael@0: { michael@0: if (mStreamInitialized && !mStreamEnded) { michael@0: inflateEnd(&mZstream); michael@0: mStreamInitialized = false; michael@0: mStreamEnded = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of michael@0: * data written to a cache entry. michael@0: * - also keeps the cache entry open while referenced. michael@0: ******************************************************************************/ michael@0: michael@0: NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsOutputStreamWrapper) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsCacheEntryDescriptor::nsOutputStreamWrapper::Release() michael@0: { michael@0: // Holding a reference to descriptor ensures that cache service won't go michael@0: // away. Do not grab cache service lock if there is no descriptor. michael@0: nsRefPtr desc; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: desc = mDescriptor; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_RELEASE)); michael@0: michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, michael@0: "nsCacheEntryDescriptor::nsOutputStreamWrapper"); michael@0: michael@0: if (0 == count) { michael@0: // don't use desc here since mDescriptor might be already nulled out michael@0: if (mDescriptor) michael@0: mDescriptor->mOutputWrapper = nullptr; michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: mRefCnt = 1; michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsOutputStreamWrapper) michael@0: NS_INTERFACE_MAP_ENTRY(nsIOutputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::LazyInit() michael@0: { michael@0: // Check if we have the descriptor. If not we can't even grab the cache michael@0: // lock since it is not ensured that the cache service still exists. michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT)); michael@0: michael@0: nsCacheAccessMode mode; michael@0: nsresult rv = mDescriptor->GetAccessGranted(&mode); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCacheEntry* cacheEntry = mDescriptor->CacheEntry(); michael@0: if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: NS_ASSERTION(mOutput == nullptr, "mOutput set in LazyInit"); michael@0: michael@0: nsCOMPtr stream; michael@0: rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset, michael@0: getter_AddRefs(stream)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCacheDevice* device = cacheEntry->CacheDevice(); michael@0: if (device) { michael@0: // the entry has been truncated to mStartOffset bytes, inform device michael@0: int32_t size = cacheEntry->DataSize(); michael@0: rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size); michael@0: if (NS_SUCCEEDED(rv)) michael@0: cacheEntry->SetDataSize(mStartOffset); michael@0: } else { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // If anything above failed, clean up internal state and get out of here michael@0: // (see bug #654926)... michael@0: if (NS_FAILED(rv)) { michael@0: nsCacheService::ReleaseObject_Locked(stream.forget().take()); michael@0: mDescriptor->mOutputWrapper = nullptr; michael@0: nsCacheService::ReleaseObject_Locked(mDescriptor); michael@0: mDescriptor = nullptr; michael@0: mInitialized = false; michael@0: return rv; michael@0: } michael@0: michael@0: mOutput = stream; michael@0: mInitialized = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::EnsureInit() michael@0: { michael@0: if (mInitialized) { michael@0: NS_ASSERTION(mDescriptor, "Bad state"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return LazyInit(); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::OnWrite(uint32_t count) michael@0: { michael@0: if (count > INT32_MAX) return NS_ERROR_UNEXPECTED; michael@0: return mDescriptor->RequestDataSizeChange((int32_t)count); michael@0: } michael@0: michael@0: void nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::CloseInternal() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: if (!mDescriptor) { michael@0: NS_ASSERTION(!mInitialized, "Bad state"); michael@0: NS_ASSERTION(!mOutput, "Bad state"); michael@0: return; michael@0: } michael@0: michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL)); michael@0: michael@0: if (mDescriptor) { michael@0: mDescriptor->mOutputWrapper = nullptr; michael@0: nsCacheService::ReleaseObject_Locked(mDescriptor); michael@0: mDescriptor = nullptr; michael@0: } michael@0: mInitialized = false; michael@0: mOutput = nullptr; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::Close() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: return Close_Locked(); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::Close_Locked() michael@0: { michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mOutput->Close(); michael@0: } else { michael@0: NS_ASSERTION(!mOutput, michael@0: "Shouldn't have mOutput when EnsureInit() failed"); michael@0: } michael@0: michael@0: // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are michael@0: // closing streams with nsCacheService::CloseAllStream() michael@0: CloseInternal(); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::Flush() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return mOutput->Flush(); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::Write(const char * buf, michael@0: uint32_t count, michael@0: uint32_t * result) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: return Write_Locked(buf, count, result); michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::Write_Locked(const char * buf, michael@0: uint32_t count, michael@0: uint32_t * result) michael@0: { michael@0: nsresult rv = EnsureInit(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = OnWrite(count); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return mOutput->Write(buf, count, result); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::WriteFrom(nsIInputStream * inStr, michael@0: uint32_t count, michael@0: uint32_t * result) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::WriteSegments(nsReadSegmentFun reader, michael@0: void * closure, michael@0: uint32_t count, michael@0: uint32_t * result) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsOutputStreamWrapper::IsNonBlocking(bool *result) michael@0: { michael@0: // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK michael@0: *result = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsCompressOutputStreamWrapper - an output stream wrapper that compresses michael@0: * data before it is written michael@0: ******************************************************************************/ michael@0: michael@0: NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper) michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Release() michael@0: { michael@0: // Holding a reference to descriptor ensures that cache service won't go michael@0: // away. Do not grab cache service lock if there is no descriptor. michael@0: nsRefPtr desc; michael@0: michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: desc = mDescriptor; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Lock(LOCK_TELEM(NSCOMPRESSOUTPUTSTREAMWRAPPER_RELEASE)); michael@0: michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, michael@0: "nsCacheEntryDescriptor::nsCompressOutputStreamWrapper"); michael@0: michael@0: if (0 == count) { michael@0: // don't use desc here since mDescriptor might be already nulled out michael@0: if (mDescriptor) michael@0: mDescriptor->mOutputWrapper = nullptr; michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: mRefCnt = 1; michael@0: delete (this); michael@0: return 0; michael@0: } michael@0: michael@0: if (desc) michael@0: nsCacheService::Unlock(); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper) michael@0: NS_INTERFACE_MAP_ENTRY(nsIOutputStream) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsCompressOutputStreamWrapper::Write(const char * buf, michael@0: uint32_t count, michael@0: uint32_t * result) michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: int zerr = Z_OK; michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mStreamInitialized) { michael@0: rv = InitZstream(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (!mWriteBuffer) { michael@0: // Once allocated, this buffer is referenced by the zlib stream and michael@0: // cannot be grown. We use 2x(initial write request) to approximate michael@0: // a stream buffer size proportional to request buffers. michael@0: mWriteBufferLen = std::max(count*2, (uint32_t)kMinCompressWriteBufLen); michael@0: mWriteBuffer = (unsigned char*)nsMemory::Alloc(mWriteBufferLen); michael@0: if (!mWriteBuffer) { michael@0: mWriteBufferLen = 0; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: mZstream.next_out = mWriteBuffer; michael@0: mZstream.avail_out = mWriteBufferLen; michael@0: } michael@0: michael@0: // Compress (deflate) the requested buffer. Keep going michael@0: // until the entire buffer has been deflated. michael@0: mZstream.avail_in = count; michael@0: mZstream.next_in = (Bytef*)buf; michael@0: while (mZstream.avail_in > 0) { michael@0: zerr = deflate(&mZstream, Z_NO_FLUSH); michael@0: if (zerr == Z_STREAM_ERROR) { michael@0: deflateEnd(&mZstream); michael@0: mStreamEnded = true; michael@0: mStreamInitialized = false; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // Note: Z_BUF_ERROR is non-fatal and sometimes expected here. michael@0: michael@0: // If the compression stream output buffer is filled, write michael@0: // it out to the underlying stream wrapper. michael@0: if (mZstream.avail_out == 0) { michael@0: rv = WriteBuffer(); michael@0: if (NS_FAILED(rv)) { michael@0: deflateEnd(&mZstream); michael@0: mStreamEnded = true; michael@0: mStreamInitialized = false; michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: *result = count; michael@0: mUncompressedCount += *result; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsCacheEntryDescriptor:: michael@0: nsCompressOutputStreamWrapper::Close() michael@0: { michael@0: mozilla::MutexAutoLock lock(mLock); michael@0: michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult retval = NS_OK; michael@0: nsresult rv; michael@0: int zerr = 0; michael@0: michael@0: if (mStreamInitialized) { michael@0: // complete compression of any data remaining in the zlib stream michael@0: do { michael@0: zerr = deflate(&mZstream, Z_FINISH); michael@0: rv = WriteBuffer(); michael@0: if (NS_FAILED(rv)) michael@0: retval = rv; michael@0: } while (zerr == Z_OK && rv == NS_OK); michael@0: deflateEnd(&mZstream); michael@0: mStreamInitialized = false; michael@0: } michael@0: // Do not allow to initialize stream after calling Close(). michael@0: mStreamEnded = true; michael@0: michael@0: if (mDescriptor->CacheEntry()) { michael@0: nsAutoCString uncompressedLenStr; michael@0: rv = mDescriptor->GetMetaDataElement("uncompressed-len", michael@0: getter_Copies(uncompressedLenStr)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: int32_t oldCount = uncompressedLenStr.ToInteger(&rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mUncompressedCount += oldCount; michael@0: } michael@0: } michael@0: uncompressedLenStr.Adopt(0); michael@0: uncompressedLenStr.AppendInt(mUncompressedCount); michael@0: rv = mDescriptor->SetMetaDataElement("uncompressed-len", michael@0: uncompressedLenStr.get()); michael@0: if (NS_FAILED(rv)) michael@0: retval = rv; michael@0: } michael@0: michael@0: if (mWriteBuffer) { michael@0: nsMemory::Free(mWriteBuffer); michael@0: mWriteBuffer = 0; michael@0: mWriteBufferLen = 0; michael@0: } michael@0: michael@0: rv = nsOutputStreamWrapper::Close_Locked(); michael@0: if (NS_FAILED(rv)) michael@0: retval = rv; michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsCompressOutputStreamWrapper::InitZstream() michael@0: { michael@0: if (!mDescriptor) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (mStreamEnded) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Determine compression level: Aggressive compression michael@0: // may impact performance on mobile devices, while a michael@0: // lower compression level still provides substantial michael@0: // space savings for many text streams. michael@0: int32_t compressionLevel = nsCacheService::CacheCompressionLevel(); michael@0: michael@0: // Initialize zlib deflate stream michael@0: mZstream.zalloc = Z_NULL; michael@0: mZstream.zfree = Z_NULL; michael@0: mZstream.opaque = Z_NULL; michael@0: if (deflateInit2(&mZstream, compressionLevel, Z_DEFLATED, michael@0: MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mZstream.next_in = Z_NULL; michael@0: mZstream.avail_in = 0; michael@0: michael@0: mStreamInitialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsCacheEntryDescriptor:: michael@0: nsCompressOutputStreamWrapper::WriteBuffer() michael@0: { michael@0: uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out; michael@0: uint32_t result = 0; michael@0: nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked( michael@0: (const char *)mWriteBuffer, bytesToWrite, &result); michael@0: mZstream.next_out = mWriteBuffer; michael@0: mZstream.avail_out = mWriteBufferLen; michael@0: return rv; michael@0: } michael@0: