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/. */
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"
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)
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
{
40 nsDiskCacheInputStream( nsDiskCacheStreamIO
* parent
,
41 PRFileDesc
* fileDesc
,
43 uint32_t endOfStream
);
45 NS_DECL_THREADSAFE_ISUPPORTS
46 NS_DECL_NSIINPUTSTREAM
49 virtual ~nsDiskCacheInputStream();
51 nsDiskCacheStreamIO
* mStreamIO
; // backpointer to parent
55 uint32_t mPos
; // stream position
60 NS_IMPL_ISUPPORTS(nsDiskCacheInputStream
, nsIInputStream
)
63 nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO
* parent
,
64 PRFileDesc
* fileDesc
,
70 , mStreamEnd(endOfStream
)
75 mStreamIO
->IncrementInputStreamCount();
79 nsDiskCacheInputStream::~nsDiskCacheInputStream()
82 mStreamIO
->DecrementInputStreamCount();
83 NS_RELEASE(mStreamIO
);
88 nsDiskCacheInputStream::Close()
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
;
113 nsDiskCacheInputStream::Read(char * buffer
, uint32_t count
, uint32_t * bytesRead
)
118 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
119 "[stream=%p] stream was closed",
120 this, buffer
, count
));
124 if (mPos
== mStreamEnd
) {
125 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
126 "[stream=%p] stream at end of file",
127 this, buffer
, count
));
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
;
141 // just read from file
142 int32_t result
= PR_Read(mFD
, buffer
, count
);
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())));
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
);
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
)));
171 nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer
,
174 uint32_t * bytesRead
)
176 return NS_ERROR_NOT_IMPLEMENTED
;
181 nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking
)
183 *nonBlocking
= false;
190 /******************************************************************************
191 * nsDiskCacheStreamIO
192 *****************************************************************************/
193 NS_IMPL_ISUPPORTS(nsDiskCacheStreamIO
, nsIOutputStream
)
195 nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding
* binding
)
202 , mOutputStreamIsOpen(false)
204 mDevice
= (nsDiskCacheDevice
*)mBinding
->mCacheEntry
->CacheDevice();
206 // acquire "death grip" on cache service
207 nsCacheService
*service
= nsCacheService::GlobalInstance();
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();
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");
234 // NOTE: called with service lock held
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
;
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
);
282 // NOTE: called with service lock held
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);
307 nsDiskCacheStreamIO::ClearBinding()
310 if (mBinding
&& mOutputStreamIsOpen
)
311 rv
= CloseOutputStream();
317 nsDiskCacheStreamIO::Close()
319 if (!mOutputStreamIsOpen
) return NS_OK
;
321 mozilla::TimeStamp start
= mozilla::TimeStamp::Now();
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();
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
;
339 id
= mozilla::Telemetry::NETWORK_DISK_CACHE_STREAMIO_CLOSE
;
340 mozilla::Telemetry::AccumulateTimeDelta(id
, start
);
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
358 (void) PR_Close(mFD
);
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
;
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
);
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
);
391 NS_WARNING("WriteDataCacheBlocks() failed.");
393 // failed to store in cacheblocks, save as separate file
394 rv
= FlushBufferToFile(); // initializes DataFileLocation() if necessary
397 (void) PR_Close(mFD
);
401 NS_WARNING("no file descriptor");
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()
414 nsDiskCacheStreamIO::Write( const char * buffer
,
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");
426 return NS_OK
; // nothing to write
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
);
454 *bytesWritten
= count
;
459 // There are more bytes than fit in the buffer/cacheblocks, switch to file
461 // Opens a cache file and write the buffer to it
462 nsresult rv
= FlushBufferToFile();
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()
473 *bytesWritten
= count
;
476 NS_ASSERTION(mBinding
->mCacheEntry
->DataSize() == mStreamEnd
, "bad stream");
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
);
507 NS_WARNING("cacheMap->UpdateRecord() failed.");
508 // XXX doom cache entry?
515 nsDiskCacheStreamIO::OpenCacheFile(int flags
, PRFileDesc
** fd
)
517 NS_ENSURE_ARG_POINTER(fd
);
519 CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
522 nsDiskCacheMap
* cacheMap
= mDevice
->CacheMap();
523 nsCOMPtr
<nsIFile
> localFile
;
525 rv
= cacheMap
->GetLocalFileForDiskCacheRecord(&mBinding
->mRecord
,
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
);
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");
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
);
566 nsDiskCacheStreamIO::FlushBufferToFile()
568 mozilla::eventtracer::AutoEventTracer
flushBufferToFile(
569 mBinding
->mCacheEntry
,
570 mozilla::eventtracer::eExec
,
571 mozilla::eventtracer::eDone
,
572 "net::cache::FlushBufferToFile");
575 nsDiskCacheRecord
* record
= &mBinding
->mRecord
;
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
);
587 rv
= OpenCacheFile(PR_RDWR
| PR_CREATE_FILE
, &mFD
);
588 if (NS_FAILED(rv
)) return rv
;
590 int64_t dataSize
= mBinding
->mCacheEntry
->PredictedDataSize();
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) {
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
614 nsDiskCacheStreamIO::DeleteBuffer()
624 nsDiskCacheStreamIO::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
)
626 size_t usage
= aMallocSizeOf(this);
628 usage
+= aMallocSizeOf(mFD
);
629 usage
+= aMallocSizeOf(mBuffer
);
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
644 // Currently stored in file?
645 if (mBinding
->mRecord
.DataLocationInitialized() &&
646 (mBinding
->mRecord
.DataFile() == 0)) {
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
;
653 if (PR_Seek(mFD
, offset
, PR_SEEK_SET
) == -1)
654 return NS_ErrorAccordingToNSPR();
656 nsDiskCache::Truncate(mFD
, offset
);
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.
664 // close file descriptor
665 (void) PR_Close(mFD
);
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");
684 nsDiskCacheStreamIO::Flush()
686 if (!mOutputStreamIsOpen
) return NS_BASE_STREAM_CLOSED
;
692 nsDiskCacheStreamIO::WriteFrom(nsIInputStream
*inStream
, uint32_t count
, uint32_t *bytesWritten
)
694 NS_NOTREACHED("WriteFrom");
695 return NS_ERROR_NOT_IMPLEMENTED
;
700 nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader
,
703 uint32_t * bytesWritten
)
705 NS_NOTREACHED("WriteSegments");
706 return NS_ERROR_NOT_IMPLEMENTED
;
711 nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking
)
713 *nonBlocking
= false;