Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache / nsDiskCacheStreams.cpp
blobe3238e58053e690d4494fc7eb2736bd3708bb742
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsCache.h"
9 #include "nsDiskCache.h"
10 #include "nsDiskCacheDevice.h"
11 #include "nsDiskCacheStreams.h"
12 #include "nsCacheService.h"
13 #include "mozilla/FileUtils.h"
14 #include "nsThreadUtils.h"
15 #include "mozilla/MemoryReporting.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/TimeStamp.h"
18 #include <algorithm>
19 #include "mozilla/VisualEventTracer.h"
21 // we pick 16k as the max buffer size because that is the threshold above which
22 // we are unable to store the data in the cache block files
23 // see nsDiskCacheMap.[cpp,h]
24 #define kMaxBufferSize (16 * 1024)
26 // Assumptions:
27 // - cache descriptors live for life of streams
28 // - streams will only be used by FileTransport,
29 // they will not be directly accessible to clients
30 // - overlapped I/O is NOT supported
33 /******************************************************************************
34 * nsDiskCacheInputStream
35 *****************************************************************************/
36 class nsDiskCacheInputStream : public nsIInputStream {
38 public:
40 nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
41 PRFileDesc * fileDesc,
42 const char * buffer,
43 uint32_t endOfStream);
45 NS_DECL_THREADSAFE_ISUPPORTS
46 NS_DECL_NSIINPUTSTREAM
48 private:
49 virtual ~nsDiskCacheInputStream();
51 nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
52 PRFileDesc * mFD;
53 const char * mBuffer;
54 uint32_t mStreamEnd;
55 uint32_t mPos; // stream position
56 bool mClosed;
60 NS_IMPL_ISUPPORTS(nsDiskCacheInputStream, nsIInputStream)
63 nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
64 PRFileDesc * fileDesc,
65 const char * buffer,
66 uint32_t endOfStream)
67 : mStreamIO(parent)
68 , mFD(fileDesc)
69 , mBuffer(buffer)
70 , mStreamEnd(endOfStream)
71 , mPos(0)
72 , mClosed(false)
74 NS_ADDREF(mStreamIO);
75 mStreamIO->IncrementInputStreamCount();
79 nsDiskCacheInputStream::~nsDiskCacheInputStream()
81 Close();
82 mStreamIO->DecrementInputStreamCount();
83 NS_RELEASE(mStreamIO);
87 NS_IMETHODIMP
88 nsDiskCacheInputStream::Close()
90 if (!mClosed) {
91 if (mFD) {
92 (void) PR_Close(mFD);
93 mFD = nullptr;
95 mClosed = true;
97 return NS_OK;
101 NS_IMETHODIMP
102 nsDiskCacheInputStream::Available(uint64_t * bytesAvailable)
104 if (mClosed) return NS_BASE_STREAM_CLOSED;
105 if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED;
107 *bytesAvailable = mStreamEnd - mPos;
108 return NS_OK;
112 NS_IMETHODIMP
113 nsDiskCacheInputStream::Read(char * buffer, uint32_t count, uint32_t * bytesRead)
115 *bytesRead = 0;
117 if (mClosed) {
118 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
119 "[stream=%p] stream was closed",
120 this, buffer, count));
121 return NS_OK;
124 if (mPos == mStreamEnd) {
125 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
126 "[stream=%p] stream at end of file",
127 this, buffer, count));
128 return NS_OK;
130 if (mPos > mStreamEnd) {
131 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
132 "[stream=%p] stream past end of file (!)",
133 this, buffer, count));
134 return NS_ERROR_UNEXPECTED;
137 if (count > mStreamEnd - mPos)
138 count = mStreamEnd - mPos;
140 if (mFD) {
141 // just read from file
142 int32_t result = PR_Read(mFD, buffer, count);
143 if (result < 0) {
144 nsresult rv = NS_ErrorAccordingToNSPR();
145 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed"
146 "[stream=%p, rv=%d, NSPR error %s",
147 this, int(rv), PR_ErrorToName(PR_GetError())));
148 return rv;
151 mPos += (uint32_t)result;
152 *bytesRead = (uint32_t)result;
154 } else if (mBuffer) {
155 // read data from mBuffer
156 memcpy(buffer, mBuffer + mPos, count);
157 mPos += count;
158 *bytesRead = count;
159 } else {
160 // no data source for input stream
163 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
164 "[stream=%p, count=%ud, byteRead=%ud] ",
165 this, unsigned(count), unsigned(*bytesRead)));
166 return NS_OK;
170 NS_IMETHODIMP
171 nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
172 void * closure,
173 uint32_t count,
174 uint32_t * bytesRead)
176 return NS_ERROR_NOT_IMPLEMENTED;
180 NS_IMETHODIMP
181 nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
183 *nonBlocking = false;
184 return NS_OK;
190 /******************************************************************************
191 * nsDiskCacheStreamIO
192 *****************************************************************************/
193 NS_IMPL_ISUPPORTS(nsDiskCacheStreamIO, nsIOutputStream)
195 nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
196 : mBinding(binding)
197 , mInStreamCount(0)
198 , mFD(nullptr)
199 , mStreamEnd(0)
200 , mBufSize(0)
201 , mBuffer(nullptr)
202 , mOutputStreamIsOpen(false)
204 mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
206 // acquire "death grip" on cache service
207 nsCacheService *service = nsCacheService::GlobalInstance();
208 NS_ADDREF(service);
212 nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
214 nsCacheService::AssertOwnsLock();
216 // Close the outputstream
217 if (mBinding && mOutputStreamIsOpen) {
218 (void)CloseOutputStream();
221 // release "death grip" on cache service
222 nsCacheService *service = nsCacheService::GlobalInstance();
223 NS_RELEASE(service);
225 // assert streams closed
226 NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
227 NS_ASSERTION(mInStreamCount == 0, "input stream still open");
228 NS_ASSERTION(!mFD, "file descriptor not closed");
230 DeleteBuffer();
234 // NOTE: called with service lock held
235 nsresult
236 nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
238 NS_ENSURE_ARG_POINTER(inputStream);
239 NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
241 *inputStream = nullptr;
243 if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
245 if (mOutputStreamIsOpen) {
246 NS_WARNING("already have an output stream open");
247 return NS_ERROR_NOT_AVAILABLE;
250 nsresult rv;
251 PRFileDesc * fd = nullptr;
253 mStreamEnd = mBinding->mCacheEntry->DataSize();
254 if (mStreamEnd == 0) {
255 // there's no data to read
256 NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
257 } else if (mBinding->mRecord.DataFile() == 0) {
258 // open file desc for data
259 rv = OpenCacheFile(PR_RDONLY, &fd);
260 if (NS_FAILED(rv)) return rv; // unable to open file
261 NS_ASSERTION(fd, "cache stream lacking open file.");
263 } else if (!mBuffer) {
264 // read block file for data
265 rv = ReadCacheBlocks(mStreamEnd);
266 if (NS_FAILED(rv)) return rv;
268 // else, mBuffer already contains all of the data (left over from a
269 // previous block-file read or write).
271 NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
273 // create a new input stream
274 nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
275 if (!inStream) return NS_ERROR_OUT_OF_MEMORY;
277 NS_ADDREF(*inputStream = inStream);
278 return NS_OK;
282 // NOTE: called with service lock held
283 nsresult
284 nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
286 NS_ENSURE_ARG_POINTER(outputStream);
287 *outputStream = nullptr;
289 if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
291 NS_ASSERTION(!mOutputStreamIsOpen, "already have an output stream open");
292 NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
293 if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
295 mStreamEnd = mBinding->mCacheEntry->DataSize();
297 // Inits file or buffer and truncate at the desired offset
298 nsresult rv = SeekAndTruncate(offset);
299 if (NS_FAILED(rv)) return rv;
301 mOutputStreamIsOpen = true;
302 NS_ADDREF(*outputStream = this);
303 return NS_OK;
306 nsresult
307 nsDiskCacheStreamIO::ClearBinding()
309 nsresult rv = NS_OK;
310 if (mBinding && mOutputStreamIsOpen)
311 rv = CloseOutputStream();
312 mBinding = nullptr;
313 return rv;
316 NS_IMETHODIMP
317 nsDiskCacheStreamIO::Close()
319 if (!mOutputStreamIsOpen) return NS_OK;
321 mozilla::TimeStamp start = mozilla::TimeStamp::Now();
323 // grab service lock
324 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM));
326 if (!mBinding) { // if we're severed, just clear member variables
327 mOutputStreamIsOpen = false;
328 return NS_ERROR_NOT_AVAILABLE;
331 nsresult rv = CloseOutputStream();
332 if (NS_FAILED(rv))
333 NS_WARNING("CloseOutputStream() failed");
335 mozilla::Telemetry::ID id;
336 if (NS_IsMainThread())
337 id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE_MAIN_THREAD;
338 else
339 id = mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE;
340 mozilla::Telemetry::AccumulateTimeDelta(id, start);
342 return rv;
345 nsresult
346 nsDiskCacheStreamIO::CloseOutputStream()
348 NS_ASSERTION(mBinding, "oops");
350 CACHE_LOG_DEBUG(("CACHE: CloseOutputStream [%x doomed=%u]\n",
351 mBinding->mRecord.HashNumber(), mBinding->mDoomed));
353 // Mark outputstream as closed, even if saving the stream fails
354 mOutputStreamIsOpen = false;
356 // When writing to a file, just close the file
357 if (mFD) {
358 (void) PR_Close(mFD);
359 mFD = nullptr;
360 return NS_OK;
363 // write data to cache blocks, or flush mBuffer to file
364 NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "stream is bigger than buffer");
366 nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference
367 nsDiskCacheRecord * record = &mBinding->mRecord;
368 nsresult rv = NS_OK;
370 // delete existing storage
371 if (record->DataLocationInitialized()) {
372 rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
373 NS_ENSURE_SUCCESS(rv, rv);
375 // Only call UpdateRecord when there is no data to write,
376 // because WriteDataCacheBlocks / FlushBufferToFile calls it.
377 if ((mStreamEnd == 0) && (!mBinding->mDoomed)) {
378 rv = cacheMap->UpdateRecord(record);
379 if (NS_FAILED(rv)) {
380 NS_WARNING("cacheMap->UpdateRecord() failed.");
381 return rv; // XXX doom cache entry
386 if (mStreamEnd == 0) return NS_OK; // nothing to write
388 // try to write to the cache blocks
389 rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
390 if (NS_FAILED(rv)) {
391 NS_WARNING("WriteDataCacheBlocks() failed.");
393 // failed to store in cacheblocks, save as separate file
394 rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
395 if (mFD) {
396 UpdateFileSize();
397 (void) PR_Close(mFD);
398 mFD = nullptr;
400 else
401 NS_WARNING("no file descriptor");
404 return rv;
408 // assumptions:
409 // only one thread writing at a time
410 // never have both output and input streams open
411 // OnDataSizeChanged() will have already been called to update entry->DataSize()
413 NS_IMETHODIMP
414 nsDiskCacheStreamIO::Write( const char * buffer,
415 uint32_t count,
416 uint32_t * bytesWritten)
418 NS_ENSURE_ARG_POINTER(buffer);
419 NS_ENSURE_ARG_POINTER(bytesWritten);
420 if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
422 *bytesWritten = 0; // always initialize to zero in case of errors
424 NS_ASSERTION(count, "Write called with count of zero");
425 if (count == 0) {
426 return NS_OK; // nothing to write
429 // grab service lock
430 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE));
431 if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
433 if (mInStreamCount) {
434 // we have open input streams already
435 // this is an error until we support overlapped I/O
436 NS_WARNING("Attempting to write to cache entry with open input streams.\n");
437 return NS_ERROR_NOT_AVAILABLE;
440 // Not writing to file, and it will fit in the cachedatablocks?
441 if (!mFD && (mStreamEnd + count <= kMaxBufferSize)) {
443 // We have more data than the current buffer size?
444 if ((mStreamEnd + count > mBufSize) && (mBufSize < kMaxBufferSize)) {
445 // Increase buffer to the maximum size.
446 mBuffer = (char *) moz_xrealloc(mBuffer, kMaxBufferSize);
447 mBufSize = kMaxBufferSize;
450 // Store in the buffer but only if it fits
451 if (mStreamEnd + count <= mBufSize) {
452 memcpy(mBuffer + mStreamEnd, buffer, count);
453 mStreamEnd += count;
454 *bytesWritten = count;
455 return NS_OK;
459 // There are more bytes than fit in the buffer/cacheblocks, switch to file
460 if (!mFD) {
461 // Opens a cache file and write the buffer to it
462 nsresult rv = FlushBufferToFile();
463 if (NS_FAILED(rv)) {
464 return rv;
467 // Write directly to the file
468 if (PR_Write(mFD, buffer, count) != (int32_t)count) {
469 NS_WARNING("failed to write all data");
470 return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
472 mStreamEnd += count;
473 *bytesWritten = count;
475 UpdateFileSize();
476 NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
478 return NS_OK;
482 void
483 nsDiskCacheStreamIO::UpdateFileSize()
485 NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
487 nsDiskCacheRecord * record = &mBinding->mRecord;
488 const uint32_t oldSizeK = record->DataFileSize();
489 uint32_t newSizeK = (mStreamEnd + 0x03FF) >> 10;
491 // make sure the size won't overflow (bug #651100)
492 if (newSizeK > kMaxDataSizeK)
493 newSizeK = kMaxDataSizeK;
495 if (newSizeK == oldSizeK) return;
497 record->SetDataFileSize(newSizeK);
499 // update cache size totals
500 nsDiskCacheMap * cacheMap = mDevice->CacheMap();
501 cacheMap->DecrementTotalSize(oldSizeK); // decrement old size
502 cacheMap->IncrementTotalSize(newSizeK); // increment new size
504 if (!mBinding->mDoomed) {
505 nsresult rv = cacheMap->UpdateRecord(record);
506 if (NS_FAILED(rv)) {
507 NS_WARNING("cacheMap->UpdateRecord() failed.");
508 // XXX doom cache entry?
514 nsresult
515 nsDiskCacheStreamIO::OpenCacheFile(int flags, PRFileDesc ** fd)
517 NS_ENSURE_ARG_POINTER(fd);
519 CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
521 nsresult rv;
522 nsDiskCacheMap * cacheMap = mDevice->CacheMap();
523 nsCOMPtr<nsIFile> localFile;
525 rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
526 nsDiskCache::kData,
527 !!(flags & PR_CREATE_FILE),
528 getter_AddRefs(localFile));
529 if (NS_FAILED(rv)) return rv;
531 // create PRFileDesc for input stream - the 00600 is just for consistency
532 return localFile->OpenNSPRFileDesc(flags, 00600, fd);
536 nsresult
537 nsDiskCacheStreamIO::ReadCacheBlocks(uint32_t bufferSize)
539 mozilla::eventtracer::AutoEventTracer readCacheBlocks(
540 mBinding->mCacheEntry,
541 mozilla::eventtracer::eExec,
542 mozilla::eventtracer::eDone,
543 "net::cache::ReadCacheBlocks");
545 NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
546 NS_ASSERTION(bufferSize <= kMaxBufferSize, "bufferSize too large for buffer");
547 NS_ASSERTION(mStreamEnd <= bufferSize, "data too large for buffer");
549 nsDiskCacheRecord * record = &mBinding->mRecord;
550 if (!record->DataLocationInitialized()) return NS_OK;
552 NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
554 if (!mBuffer) {
555 mBuffer = (char *) moz_xmalloc(bufferSize);
556 mBufSize = bufferSize;
559 // read data stored in cache block files
560 nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference
561 return map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
565 nsresult
566 nsDiskCacheStreamIO::FlushBufferToFile()
568 mozilla::eventtracer::AutoEventTracer flushBufferToFile(
569 mBinding->mCacheEntry,
570 mozilla::eventtracer::eExec,
571 mozilla::eventtracer::eDone,
572 "net::cache::FlushBufferToFile");
574 nsresult rv;
575 nsDiskCacheRecord * record = &mBinding->mRecord;
577 if (!mFD) {
578 if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
579 // remove cache block storage
580 nsDiskCacheMap * cacheMap = mDevice->CacheMap();
581 rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
582 if (NS_FAILED(rv)) return rv;
584 record->SetDataFileGeneration(mBinding->mGeneration);
586 // allocate file
587 rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
588 if (NS_FAILED(rv)) return rv;
590 int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
591 if (dataSize != -1)
592 mozilla::fallocate(mFD, std::min<int64_t>(dataSize, kPreallocateLimit));
595 // write buffer to the file when there is data in it
596 if (mStreamEnd > 0) {
597 if (!mBuffer) {
598 NS_RUNTIMEABORT("Fix me!");
600 if (PR_Write(mFD, mBuffer, mStreamEnd) != (int32_t)mStreamEnd) {
601 NS_WARNING("failed to flush all data");
602 return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
606 // buffer is no longer valid
607 DeleteBuffer();
609 return NS_OK;
613 void
614 nsDiskCacheStreamIO::DeleteBuffer()
616 if (mBuffer) {
617 free(mBuffer);
618 mBuffer = nullptr;
619 mBufSize = 0;
623 size_t
624 nsDiskCacheStreamIO::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
626 size_t usage = aMallocSizeOf(this);
628 usage += aMallocSizeOf(mFD);
629 usage += aMallocSizeOf(mBuffer);
631 return usage;
634 nsresult
635 nsDiskCacheStreamIO::SeekAndTruncate(uint32_t offset)
637 if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
639 if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
641 // Set the current end to the desired offset
642 mStreamEnd = offset;
644 // Currently stored in file?
645 if (mBinding->mRecord.DataLocationInitialized() &&
646 (mBinding->mRecord.DataFile() == 0)) {
647 if (!mFD) {
648 // we need an mFD, we better open it now
649 nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
650 if (NS_FAILED(rv)) return rv;
652 if (offset) {
653 if (PR_Seek(mFD, offset, PR_SEEK_SET) == -1)
654 return NS_ErrorAccordingToNSPR();
656 nsDiskCache::Truncate(mFD, offset);
657 UpdateFileSize();
659 // When we starting at zero again, close file and start with buffer.
660 // If offset is non-zero (and within buffer) an option would be
661 // to read the file into the buffer, but chance is high that it is
662 // rewritten to the file anyway.
663 if (offset == 0) {
664 // close file descriptor
665 (void) PR_Close(mFD);
666 mFD = nullptr;
668 return NS_OK;
671 // read data into mBuffer if not read yet.
672 if (offset && !mBuffer) {
673 nsresult rv = ReadCacheBlocks(kMaxBufferSize);
674 if (NS_FAILED(rv)) return rv;
677 // stream buffer sanity check
678 NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream");
679 return NS_OK;
683 NS_IMETHODIMP
684 nsDiskCacheStreamIO::Flush()
686 if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
687 return NS_OK;
691 NS_IMETHODIMP
692 nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
694 NS_NOTREACHED("WriteFrom");
695 return NS_ERROR_NOT_IMPLEMENTED;
699 NS_IMETHODIMP
700 nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader,
701 void * closure,
702 uint32_t count,
703 uint32_t * bytesWritten)
705 NS_NOTREACHED("WriteSegments");
706 return NS_ERROR_NOT_IMPLEMENTED;
710 NS_IMETHODIMP
711 nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
713 *nonBlocking = false;
714 return NS_OK;