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: michael@0: #include "nsCache.h" michael@0: #include "nsDiskCache.h" michael@0: #include "nsDiskCacheDevice.h" michael@0: #include "nsDiskCacheStreams.h" michael@0: #include "nsCacheService.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include michael@0: #include "mozilla/VisualEventTracer.h" michael@0: michael@0: // we pick 16k as the max buffer size because that is the threshold above which michael@0: // we are unable to store the data in the cache block files michael@0: // see nsDiskCacheMap.[cpp,h] michael@0: #define kMaxBufferSize (16 * 1024) michael@0: michael@0: // Assumptions: michael@0: // - cache descriptors live for life of streams michael@0: // - streams will only be used by FileTransport, michael@0: // they will not be directly accessible to clients michael@0: // - overlapped I/O is NOT supported michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheInputStream michael@0: *****************************************************************************/ michael@0: class nsDiskCacheInputStream : public nsIInputStream { michael@0: michael@0: public: michael@0: michael@0: nsDiskCacheInputStream( nsDiskCacheStreamIO * parent, michael@0: PRFileDesc * fileDesc, michael@0: const char * buffer, michael@0: uint32_t endOfStream); michael@0: michael@0: virtual ~nsDiskCacheInputStream(); michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIINPUTSTREAM michael@0: michael@0: private: michael@0: nsDiskCacheStreamIO * mStreamIO; // backpointer to parent michael@0: PRFileDesc * mFD; michael@0: const char * mBuffer; michael@0: uint32_t mStreamEnd; michael@0: uint32_t mPos; // stream position michael@0: bool mClosed; michael@0: }; michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDiskCacheInputStream, nsIInputStream) michael@0: michael@0: michael@0: nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent, michael@0: PRFileDesc * fileDesc, michael@0: const char * buffer, michael@0: uint32_t endOfStream) michael@0: : mStreamIO(parent) michael@0: , mFD(fileDesc) michael@0: , mBuffer(buffer) michael@0: , mStreamEnd(endOfStream) michael@0: , mPos(0) michael@0: , mClosed(false) michael@0: { michael@0: NS_ADDREF(mStreamIO); michael@0: mStreamIO->IncrementInputStreamCount(); michael@0: } michael@0: michael@0: michael@0: nsDiskCacheInputStream::~nsDiskCacheInputStream() michael@0: { michael@0: Close(); michael@0: mStreamIO->DecrementInputStreamCount(); michael@0: NS_RELEASE(mStreamIO); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheInputStream::Close() michael@0: { michael@0: if (!mClosed) { michael@0: if (mFD) { michael@0: (void) PR_Close(mFD); michael@0: mFD = nullptr; michael@0: } michael@0: mClosed = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheInputStream::Available(uint64_t * bytesAvailable) michael@0: { michael@0: if (mClosed) return NS_BASE_STREAM_CLOSED; michael@0: if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED; michael@0: michael@0: *bytesAvailable = mStreamEnd - mPos; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheInputStream::Read(char * buffer, uint32_t count, uint32_t * bytesRead) michael@0: { michael@0: *bytesRead = 0; michael@0: michael@0: if (mClosed) { michael@0: CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read " michael@0: "[stream=%p] stream was closed", michael@0: this, buffer, count)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mPos == mStreamEnd) { michael@0: CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read " michael@0: "[stream=%p] stream at end of file", michael@0: this, buffer, count)); michael@0: return NS_OK; michael@0: } michael@0: if (mPos > mStreamEnd) { michael@0: CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read " michael@0: "[stream=%p] stream past end of file (!)", michael@0: this, buffer, count)); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (count > mStreamEnd - mPos) michael@0: count = mStreamEnd - mPos; michael@0: michael@0: if (mFD) { michael@0: // just read from file michael@0: int32_t result = PR_Read(mFD, buffer, count); michael@0: if (result < 0) { michael@0: nsresult rv = NS_ErrorAccordingToNSPR(); michael@0: CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed" michael@0: "[stream=%p, rv=%d, NSPR error %s", michael@0: this, int(rv), PR_ErrorToName(PR_GetError()))); michael@0: return rv; michael@0: } michael@0: michael@0: mPos += (uint32_t)result; michael@0: *bytesRead = (uint32_t)result; michael@0: michael@0: } else if (mBuffer) { michael@0: // read data from mBuffer michael@0: memcpy(buffer, mBuffer + mPos, count); michael@0: mPos += count; michael@0: *bytesRead = count; michael@0: } else { michael@0: // no data source for input stream michael@0: } michael@0: michael@0: CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read " michael@0: "[stream=%p, count=%ud, byteRead=%ud] ", michael@0: this, unsigned(count), unsigned(*bytesRead))); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer, michael@0: void * closure, michael@0: uint32_t count, michael@0: uint32_t * bytesRead) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking) michael@0: { michael@0: *nonBlocking = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheStreamIO michael@0: *****************************************************************************/ michael@0: NS_IMPL_ISUPPORTS(nsDiskCacheStreamIO, nsIOutputStream) michael@0: michael@0: nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding) michael@0: : mBinding(binding) michael@0: , mInStreamCount(0) michael@0: , mFD(nullptr) michael@0: , mStreamEnd(0) michael@0: , mBufSize(0) michael@0: , mBuffer(nullptr) michael@0: , mOutputStreamIsOpen(false) michael@0: { michael@0: mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice(); michael@0: michael@0: // acquire "death grip" on cache service michael@0: nsCacheService *service = nsCacheService::GlobalInstance(); michael@0: NS_ADDREF(service); michael@0: } michael@0: michael@0: michael@0: nsDiskCacheStreamIO::~nsDiskCacheStreamIO() michael@0: { michael@0: nsCacheService::AssertOwnsLock(); michael@0: michael@0: // Close the outputstream michael@0: if (mBinding && mOutputStreamIsOpen) { michael@0: (void)CloseOutputStream(); michael@0: } michael@0: michael@0: // release "death grip" on cache service michael@0: nsCacheService *service = nsCacheService::GlobalInstance(); michael@0: NS_RELEASE(service); michael@0: michael@0: // assert streams closed michael@0: NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open"); michael@0: NS_ASSERTION(mInStreamCount == 0, "input stream still open"); michael@0: NS_ASSERTION(!mFD, "file descriptor not closed"); michael@0: michael@0: DeleteBuffer(); michael@0: } michael@0: michael@0: michael@0: // NOTE: called with service lock held michael@0: nsresult michael@0: nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(inputStream); michael@0: NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED); michael@0: michael@0: *inputStream = nullptr; michael@0: michael@0: if (!mBinding) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (mOutputStreamIsOpen) { michael@0: NS_WARNING("already have an output stream open"); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult rv; michael@0: PRFileDesc * fd = nullptr; michael@0: michael@0: mStreamEnd = mBinding->mCacheEntry->DataSize(); michael@0: if (mStreamEnd == 0) { michael@0: // there's no data to read michael@0: NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size"); michael@0: } else if (mBinding->mRecord.DataFile() == 0) { michael@0: // open file desc for data michael@0: rv = OpenCacheFile(PR_RDONLY, &fd); michael@0: if (NS_FAILED(rv)) return rv; // unable to open file michael@0: NS_ASSERTION(fd, "cache stream lacking open file."); michael@0: michael@0: } else if (!mBuffer) { michael@0: // read block file for data michael@0: rv = ReadCacheBlocks(mStreamEnd); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: // else, mBuffer already contains all of the data (left over from a michael@0: // previous block-file read or write). michael@0: michael@0: NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream"); michael@0: michael@0: // create a new input stream michael@0: nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd); michael@0: if (!inStream) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*inputStream = inStream); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // NOTE: called with service lock held michael@0: nsresult michael@0: nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(outputStream); michael@0: *outputStream = nullptr; michael@0: michael@0: if (!mBinding) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: NS_ASSERTION(!mOutputStreamIsOpen, "already have an output stream open"); michael@0: NS_ASSERTION(mInStreamCount == 0, "we already have input streams open"); michael@0: if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mStreamEnd = mBinding->mCacheEntry->DataSize(); michael@0: michael@0: // Inits file or buffer and truncate at the desired offset michael@0: nsresult rv = SeekAndTruncate(offset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mOutputStreamIsOpen = true; michael@0: NS_ADDREF(*outputStream = this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::ClearBinding() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (mBinding && mOutputStreamIsOpen) michael@0: rv = CloseOutputStream(); michael@0: mBinding = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::Close() michael@0: { michael@0: if (!mOutputStreamIsOpen) return NS_OK; michael@0: michael@0: mozilla::TimeStamp start = mozilla::TimeStamp::Now(); michael@0: michael@0: // grab service lock michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); michael@0: michael@0: if (!mBinding) { // if we're severed, just clear member variables michael@0: mOutputStreamIsOpen = false; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsresult rv = CloseOutputStream(); michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("CloseOutputStream() failed"); michael@0: michael@0: mozilla::Telemetry::ID id; michael@0: if (NS_IsMainThread()) michael@0: id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE_MAIN_THREAD; michael@0: else michael@0: id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE; michael@0: mozilla::Telemetry::AccumulateTimeDelta(id, start); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::CloseOutputStream() michael@0: { michael@0: NS_ASSERTION(mBinding, "oops"); michael@0: michael@0: CACHE_LOG_DEBUG(("CACHE: CloseOutputStream [%x doomed=%u]\n", michael@0: mBinding->mRecord.HashNumber(), mBinding->mDoomed)); michael@0: michael@0: // Mark outputstream as closed, even if saving the stream fails michael@0: mOutputStreamIsOpen = false; michael@0: michael@0: // When writing to a file, just close the file michael@0: if (mFD) { michael@0: (void) PR_Close(mFD); michael@0: mFD = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // write data to cache blocks, or flush mBuffer to file michael@0: NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "stream is bigger than buffer"); michael@0: michael@0: nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference michael@0: nsDiskCacheRecord * record = &mBinding->mRecord; michael@0: nsresult rv = NS_OK; michael@0: michael@0: // delete existing storage michael@0: if (record->DataLocationInitialized()) { michael@0: rv = cacheMap->DeleteStorage(record, nsDiskCache::kData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Only call UpdateRecord when there is no data to write, michael@0: // because WriteDataCacheBlocks / FlushBufferToFile calls it. michael@0: if ((mStreamEnd == 0) && (!mBinding->mDoomed)) { michael@0: rv = cacheMap->UpdateRecord(record); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("cacheMap->UpdateRecord() failed."); michael@0: return rv; // XXX doom cache entry michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mStreamEnd == 0) return NS_OK; // nothing to write michael@0: michael@0: // try to write to the cache blocks michael@0: rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mStreamEnd); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("WriteDataCacheBlocks() failed."); michael@0: michael@0: // failed to store in cacheblocks, save as separate file michael@0: rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary michael@0: if (mFD) { michael@0: UpdateFileSize(); michael@0: (void) PR_Close(mFD); michael@0: mFD = nullptr; michael@0: } michael@0: else michael@0: NS_WARNING("no file descriptor"); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // assumptions: michael@0: // only one thread writing at a time michael@0: // never have both output and input streams open michael@0: // OnDataSizeChanged() will have already been called to update entry->DataSize() michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::Write( const char * buffer, michael@0: uint32_t count, michael@0: uint32_t * bytesWritten) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(buffer); michael@0: NS_ENSURE_ARG_POINTER(bytesWritten); michael@0: if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED; michael@0: michael@0: *bytesWritten = 0; // always initialize to zero in case of errors michael@0: michael@0: NS_ASSERTION(count, "Write called with count of zero"); michael@0: if (count == 0) { michael@0: return NS_OK; // nothing to write michael@0: } michael@0: michael@0: // grab service lock michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE)); michael@0: if (!mBinding) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (mInStreamCount) { michael@0: // we have open input streams already michael@0: // this is an error until we support overlapped I/O michael@0: NS_WARNING("Attempting to write to cache entry with open input streams.\n"); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Not writing to file, and it will fit in the cachedatablocks? michael@0: if (!mFD && (mStreamEnd + count <= kMaxBufferSize)) { michael@0: michael@0: // We have more data than the current buffer size? michael@0: if ((mStreamEnd + count > mBufSize) && (mBufSize < kMaxBufferSize)) { michael@0: // Increase buffer to the maximum size. michael@0: mBuffer = (char *) moz_xrealloc(mBuffer, kMaxBufferSize); michael@0: mBufSize = kMaxBufferSize; michael@0: } michael@0: michael@0: // Store in the buffer but only if it fits michael@0: if (mStreamEnd + count <= mBufSize) { michael@0: memcpy(mBuffer + mStreamEnd, buffer, count); michael@0: mStreamEnd += count; michael@0: *bytesWritten = count; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // There are more bytes than fit in the buffer/cacheblocks, switch to file michael@0: if (!mFD) { michael@0: // Opens a cache file and write the buffer to it michael@0: nsresult rv = FlushBufferToFile(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: // Write directly to the file michael@0: if (PR_Write(mFD, buffer, count) != (int32_t)count) { michael@0: NS_WARNING("failed to write all data"); michael@0: return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR() michael@0: } michael@0: mStreamEnd += count; michael@0: *bytesWritten = count; michael@0: michael@0: UpdateFileSize(); michael@0: NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsDiskCacheStreamIO::UpdateFileSize() michael@0: { michael@0: NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called"); michael@0: michael@0: nsDiskCacheRecord * record = &mBinding->mRecord; michael@0: const uint32_t oldSizeK = record->DataFileSize(); michael@0: uint32_t newSizeK = (mStreamEnd + 0x03FF) >> 10; michael@0: michael@0: // make sure the size won't overflow (bug #651100) michael@0: if (newSizeK > kMaxDataSizeK) michael@0: newSizeK = kMaxDataSizeK; michael@0: michael@0: if (newSizeK == oldSizeK) return; michael@0: michael@0: record->SetDataFileSize(newSizeK); michael@0: michael@0: // update cache size totals michael@0: nsDiskCacheMap * cacheMap = mDevice->CacheMap(); michael@0: cacheMap->DecrementTotalSize(oldSizeK); // decrement old size michael@0: cacheMap->IncrementTotalSize(newSizeK); // increment new size michael@0: michael@0: if (!mBinding->mDoomed) { michael@0: nsresult rv = cacheMap->UpdateRecord(record); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("cacheMap->UpdateRecord() failed."); michael@0: // XXX doom cache entry? michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::OpenCacheFile(int flags, PRFileDesc ** fd) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(fd); michael@0: michael@0: CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile")); michael@0: michael@0: nsresult rv; michael@0: nsDiskCacheMap * cacheMap = mDevice->CacheMap(); michael@0: nsCOMPtr localFile; michael@0: michael@0: rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord, michael@0: nsDiskCache::kData, michael@0: !!(flags & PR_CREATE_FILE), michael@0: getter_AddRefs(localFile)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // create PRFileDesc for input stream - the 00600 is just for consistency michael@0: return localFile->OpenNSPRFileDesc(flags, 00600, fd); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::ReadCacheBlocks(uint32_t bufferSize) michael@0: { michael@0: mozilla::eventtracer::AutoEventTracer readCacheBlocks( michael@0: mBinding->mCacheEntry, michael@0: mozilla::eventtracer::eExec, michael@0: mozilla::eventtracer::eDone, michael@0: "net::cache::ReadCacheBlocks"); michael@0: michael@0: NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream"); michael@0: NS_ASSERTION(bufferSize <= kMaxBufferSize, "bufferSize too large for buffer"); michael@0: NS_ASSERTION(mStreamEnd <= bufferSize, "data too large for buffer"); michael@0: michael@0: nsDiskCacheRecord * record = &mBinding->mRecord; michael@0: if (!record->DataLocationInitialized()) return NS_OK; michael@0: michael@0: NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file"); michael@0: michael@0: if (!mBuffer) { michael@0: mBuffer = (char *) moz_xmalloc(bufferSize); michael@0: mBufSize = bufferSize; michael@0: } michael@0: michael@0: // read data stored in cache block files michael@0: nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference michael@0: return map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::FlushBufferToFile() michael@0: { michael@0: mozilla::eventtracer::AutoEventTracer flushBufferToFile( michael@0: mBinding->mCacheEntry, michael@0: mozilla::eventtracer::eExec, michael@0: mozilla::eventtracer::eDone, michael@0: "net::cache::FlushBufferToFile"); michael@0: michael@0: nsresult rv; michael@0: nsDiskCacheRecord * record = &mBinding->mRecord; michael@0: michael@0: if (!mFD) { michael@0: if (record->DataLocationInitialized() && (record->DataFile() > 0)) { michael@0: // remove cache block storage michael@0: nsDiskCacheMap * cacheMap = mDevice->CacheMap(); michael@0: rv = cacheMap->DeleteStorage(record, nsDiskCache::kData); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: record->SetDataFileGeneration(mBinding->mGeneration); michael@0: michael@0: // allocate file michael@0: rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize(); michael@0: if (dataSize != -1) michael@0: mozilla::fallocate(mFD, std::min(dataSize, kPreallocateLimit)); michael@0: } michael@0: michael@0: // write buffer to the file when there is data in it michael@0: if (mStreamEnd > 0) { michael@0: if (!mBuffer) { michael@0: NS_RUNTIMEABORT("Fix me!"); michael@0: } michael@0: if (PR_Write(mFD, mBuffer, mStreamEnd) != (int32_t)mStreamEnd) { michael@0: NS_WARNING("failed to flush all data"); michael@0: return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR() michael@0: } michael@0: } michael@0: michael@0: // buffer is no longer valid michael@0: DeleteBuffer(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsDiskCacheStreamIO::DeleteBuffer() michael@0: { michael@0: if (mBuffer) { michael@0: free(mBuffer); michael@0: mBuffer = nullptr; michael@0: mBufSize = 0; michael@0: } michael@0: } michael@0: michael@0: size_t michael@0: nsDiskCacheStreamIO::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) michael@0: { michael@0: size_t usage = aMallocSizeOf(this); michael@0: michael@0: usage += aMallocSizeOf(mFD); michael@0: usage += aMallocSizeOf(mBuffer); michael@0: michael@0: return usage; michael@0: } michael@0: michael@0: nsresult michael@0: nsDiskCacheStreamIO::SeekAndTruncate(uint32_t offset) michael@0: { michael@0: if (!mBinding) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE; michael@0: michael@0: // Set the current end to the desired offset michael@0: mStreamEnd = offset; michael@0: michael@0: // Currently stored in file? michael@0: if (mBinding->mRecord.DataLocationInitialized() && michael@0: (mBinding->mRecord.DataFile() == 0)) { michael@0: if (!mFD) { michael@0: // we need an mFD, we better open it now michael@0: nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: if (offset) { michael@0: if (PR_Seek(mFD, offset, PR_SEEK_SET) == -1) michael@0: return NS_ErrorAccordingToNSPR(); michael@0: } michael@0: nsDiskCache::Truncate(mFD, offset); michael@0: UpdateFileSize(); michael@0: michael@0: // When we starting at zero again, close file and start with buffer. michael@0: // If offset is non-zero (and within buffer) an option would be michael@0: // to read the file into the buffer, but chance is high that it is michael@0: // rewritten to the file anyway. michael@0: if (offset == 0) { michael@0: // close file descriptor michael@0: (void) PR_Close(mFD); michael@0: mFD = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // read data into mBuffer if not read yet. michael@0: if (offset && !mBuffer) { michael@0: nsresult rv = ReadCacheBlocks(kMaxBufferSize); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // stream buffer sanity check michael@0: NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::Flush() michael@0: { michael@0: if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten) michael@0: { michael@0: NS_NOTREACHED("WriteFrom"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader, michael@0: void * closure, michael@0: uint32_t count, michael@0: uint32_t * bytesWritten) michael@0: { michael@0: NS_NOTREACHED("WriteSegments"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking) michael@0: { michael@0: *nonBlocking = false; michael@0: return NS_OK; michael@0: }