1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CacheFileInputStream.h"
9 #include "nsStreamUtils.h"
10 #include "nsThreadUtils.h"
11 #include "mozilla/IntegerPrintfMacros.h"
14 namespace mozilla::net
{
16 NS_IMPL_ADDREF(CacheFileInputStream
)
17 NS_IMETHODIMP_(MozExternalRefCountType
)
18 CacheFileInputStream::Release() {
19 MOZ_ASSERT(0 != mRefCnt
, "dup release");
20 nsrefcnt count
= --mRefCnt
;
21 NS_LOG_RELEASE(this, count
, "CacheFileInputStream");
30 CacheFileAutoLock
lock(mFile
);
31 mFile
->RemoveInput(this, mStatus
);
37 NS_INTERFACE_MAP_BEGIN(CacheFileInputStream
)
38 NS_INTERFACE_MAP_ENTRY(nsIInputStream
)
39 NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream
)
40 NS_INTERFACE_MAP_ENTRY(nsISeekableStream
)
41 NS_INTERFACE_MAP_ENTRY(nsITellableStream
)
42 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener
)
43 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIInputStream
)
46 CacheFileInputStream::CacheFileInputStream(CacheFile
* aFile
,
48 bool aAlternativeData
)
53 mInReadSegments(false),
54 mWaitingForUpdate(false),
55 mAlternativeData(aAlternativeData
),
56 mListeningForChunk(-1),
58 mCacheEntryHandle(aEntry
) {
59 LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
61 if (mAlternativeData
) {
62 mPos
= mFile
->mAltDataOffset
;
66 CacheFileInputStream::~CacheFileInputStream() {
67 LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
68 MOZ_ASSERT(!mInReadSegments
);
73 CacheFileInputStream::Close() {
74 LOG(("CacheFileInputStream::Close() [this=%p]", this));
75 return CloseWithStatus(NS_OK
);
79 CacheFileInputStream::Available(uint64_t* _retval
) {
80 CacheFileAutoLock
lock(mFile
);
84 ("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
85 "status=0x%08" PRIx32
"]",
86 this, static_cast<uint32_t>(mStatus
)));
87 return NS_FAILED(mStatus
) ? mStatus
: NS_BASE_STREAM_CLOSED
;
90 EnsureCorrectChunk(false);
91 if (NS_FAILED(mStatus
)) {
93 ("CacheFileInputStream::Available() - EnsureCorrectChunk failed. "
94 "[this=%p, status=0x%08" PRIx32
"]",
95 this, static_cast<uint32_t>(mStatus
)));
103 int64_t canRead
= mFile
->BytesFromChunk(mChunk
->Index(), mAlternativeData
);
104 canRead
-= (mPos
% kChunkSize
);
108 } else if (canRead
== 0 && !mFile
->OutputStreamExists(mAlternativeData
)) {
109 rv
= NS_BASE_STREAM_CLOSED
;
113 LOG(("CacheFileInputStream::Available() [this=%p, retval=%" PRIu64
114 ", rv=0x%08" PRIx32
"]",
115 this, *_retval
, static_cast<uint32_t>(rv
)));
121 CacheFileInputStream::StreamStatus() {
122 CacheFileAutoLock
lock(mFile
);
126 ("CacheFileInputStream::StreamStatus() - Stream is closed. [this=%p, "
127 "status=0x%08" PRIx32
"]",
128 this, static_cast<uint32_t>(mStatus
)));
129 return NS_FAILED(mStatus
) ? mStatus
: NS_BASE_STREAM_CLOSED
;
136 CacheFileInputStream::Read(char* aBuf
, uint32_t aCount
, uint32_t* _retval
) {
137 LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount
));
138 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, _retval
);
142 CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter
, void* aClosure
,
143 uint32_t aCount
, uint32_t* _retval
) {
144 CacheFileAutoLock
lock(mFile
);
146 LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]", this,
153 if (mInReadSegments
) {
155 ("CacheFileInputStream::ReadSegments() - Cannot be called while the "
156 "stream is in ReadSegments!"));
157 return NS_ERROR_UNEXPECTED
;
162 ("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
163 "status=0x%08" PRIx32
"]",
164 this, static_cast<uint32_t>(mStatus
)));
166 if (NS_FAILED(mStatus
)) {
177 EnsureCorrectChunk(false);
180 if (NS_FAILED(mStatus
)) return mStatus
;
183 if (mListeningForChunk
== -1) {
186 return NS_BASE_STREAM_WOULD_BLOCK
;
189 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
190 int64_t canRead
= CanRead(&hnd
);
191 if (NS_FAILED(mStatus
)) {
196 // file was truncated ???
197 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
199 } else if (canRead
> 0) {
200 uint32_t toRead
= std::min(static_cast<uint32_t>(canRead
), aCount
);
202 const char* buf
= hnd
.Buf() + (mPos
- hnd
.Offset());
204 mInReadSegments
= true;
207 rv
= aWriter(this, aClosure
, buf
, *_retval
, toRead
, &read
);
210 mInReadSegments
= false;
212 if (NS_SUCCEEDED(rv
)) {
213 MOZ_ASSERT(read
<= toRead
,
214 "writer should not write more than we asked it to write");
221 // The last chunk is released after the caller closes this stream.
222 EnsureCorrectChunk(false);
224 if (mChunk
&& aCount
) {
225 // Check whether there is more data available to read.
232 // The stream was closed from aWriter, do the cleanup.
238 if (*_retval
== 0 && mFile
->OutputStreamExists(mAlternativeData
)) {
239 rv
= NS_BASE_STREAM_WOULD_BLOCK
;
248 LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08" PRIx32
250 this, static_cast<uint32_t>(rv
), *_retval
));
256 CacheFileInputStream::IsNonBlocking(bool* _retval
) {
261 // nsIAsyncInputStream
263 CacheFileInputStream::CloseWithStatus(nsresult aStatus
) {
264 CacheFileAutoLock
lock(mFile
);
266 LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
268 this, static_cast<uint32_t>(aStatus
)));
270 CloseWithStatusLocked(aStatus
);
274 void CacheFileInputStream::CloseWithStatusLocked(nsresult aStatus
) {
276 ("CacheFileInputStream::CloseWithStatusLocked() [this=%p, "
277 "aStatus=0x%08" PRIx32
"]",
278 this, static_cast<uint32_t>(aStatus
)));
281 // We notify listener and null out mCallback immediately after closing
282 // the stream. If we're in ReadSegments we postpone notification until we
283 // step out from ReadSegments. So if the stream is already closed the
284 // following assertion must be true.
285 MOZ_ASSERT(!mCallback
|| mInReadSegments
);
290 mStatus
= NS_FAILED(aStatus
) ? aStatus
: NS_BASE_STREAM_CLOSED
;
292 if (!mInReadSegments
) {
297 void CacheFileInputStream::CleanUp() {
298 MOZ_ASSERT(!mInReadSegments
);
305 // TODO propagate error from input stream to other streams ???
307 MaybeNotifyListener();
309 mFile
->ReleaseOutsideLock(std::move(mCacheEntryHandle
));
313 CacheFileInputStream::AsyncWait(nsIInputStreamCallback
* aCallback
,
314 uint32_t aFlags
, uint32_t aRequestedCount
,
315 nsIEventTarget
* aEventTarget
) {
316 CacheFileAutoLock
lock(mFile
);
319 ("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
320 "requestedCount=%d, eventTarget=%p]",
321 this, aCallback
, aFlags
, aRequestedCount
, aEventTarget
));
323 if (mInReadSegments
) {
325 ("CacheFileInputStream::AsyncWait() - Cannot be called while the stream"
326 " is in ReadSegments!"));
328 "Unexpected call. If it's a valid usage implement it. "
329 "Otherwise fix the caller.");
330 return NS_ERROR_UNEXPECTED
;
333 mCallback
= aCallback
;
334 mCallbackFlags
= aFlags
;
335 mCallbackTarget
= aEventTarget
;
338 if (mWaitingForUpdate
) {
339 mChunk
->CancelWait(this);
340 mWaitingForUpdate
= false;
350 EnsureCorrectChunk(false);
352 MaybeNotifyListener();
359 CacheFileInputStream::Seek(int32_t whence
, int64_t offset
) {
360 CacheFileAutoLock
lock(mFile
);
361 mFile
->AssertOwnsLock(); // For thread-safety analysis
363 LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%" PRId64
"]",
364 this, whence
, offset
));
366 if (mInReadSegments
) {
368 ("CacheFileInputStream::Seek() - Cannot be called while the stream is "
369 "in ReadSegments!"));
370 return NS_ERROR_UNEXPECTED
;
374 LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
375 return NS_BASE_STREAM_CLOSED
;
378 int64_t newPos
= offset
;
381 if (mAlternativeData
) {
382 newPos
+= mFile
->mAltDataOffset
;
389 if (mAlternativeData
) {
390 newPos
+= mFile
->mDataSize
;
392 newPos
+= mFile
->mAltDataOffset
;
396 NS_ERROR("invalid whence");
397 return NS_ERROR_INVALID_ARG
;
400 EnsureCorrectChunk(false);
402 LOG(("CacheFileInputStream::Seek() [this=%p, pos=%" PRId64
"]", this, mPos
));
407 CacheFileInputStream::SetEOF() {
408 MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
409 return NS_ERROR_NOT_IMPLEMENTED
;
414 CacheFileInputStream::Tell(int64_t* _retval
) {
415 CacheFileAutoLock
lock(mFile
);
416 mFile
->AssertOwnsLock(); // For thread-safety analysis
419 LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
420 return NS_BASE_STREAM_CLOSED
;
425 if (mAlternativeData
) {
426 *_retval
-= mFile
->mAltDataOffset
;
429 LOG(("CacheFileInputStream::Tell() [this=%p, retval=%" PRId64
"]", this,
434 // CacheFileChunkListener
435 nsresult
CacheFileInputStream::OnChunkRead(nsresult aResult
,
436 CacheFileChunk
* aChunk
) {
437 MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
438 return NS_ERROR_UNEXPECTED
;
441 nsresult
CacheFileInputStream::OnChunkWritten(nsresult aResult
,
442 CacheFileChunk
* aChunk
) {
443 MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
444 return NS_ERROR_UNEXPECTED
;
447 nsresult
CacheFileInputStream::OnChunkAvailable(nsresult aResult
,
449 CacheFileChunk
* aChunk
) {
450 CacheFileAutoLock
lock(mFile
);
452 LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08" PRIx32
455 this, static_cast<uint32_t>(aResult
), aChunkIdx
, aChunk
));
457 MOZ_ASSERT(mListeningForChunk
!= -1);
459 if (mListeningForChunk
!= static_cast<int64_t>(aChunkIdx
)) {
460 // This is not a chunk that we're waiting for
462 ("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
463 "different chunk. [this=%p, listeningForChunk=%" PRId64
"]",
464 this, mListeningForChunk
));
470 MOZ_ASSERT(!mWaitingForUpdate
);
471 MOZ_ASSERT(!mInReadSegments
);
472 mListeningForChunk
= -1;
475 MOZ_ASSERT(!mCallback
);
478 ("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
479 "ignoring notification. [this=%p]",
485 if (NS_SUCCEEDED(aResult
)) {
487 } else if (aResult
!= NS_ERROR_NOT_AVAILABLE
) {
488 // Close the stream with error. The consumer will receive this error later
489 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
490 // differently since it is returned when the requested chunk is not
491 // available and there is no writer that could create it, i.e. it means that
492 // we've reached the end of the file.
493 CloseWithStatusLocked(aResult
);
498 MaybeNotifyListener();
503 nsresult
CacheFileInputStream::OnChunkUpdated(CacheFileChunk
* aChunk
) {
504 CacheFileAutoLock
lock(mFile
);
506 LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]", this,
509 if (!mWaitingForUpdate
) {
511 ("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
512 "mWaitingforUpdate == false. [this=%p]",
518 mWaitingForUpdate
= false;
520 MOZ_ASSERT(mChunk
== aChunk
);
522 MaybeNotifyListener();
527 void CacheFileInputStream::ReleaseChunk() {
528 mFile
->AssertOwnsLock();
530 LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]", this,
533 MOZ_ASSERT(!mInReadSegments
);
535 if (mWaitingForUpdate
) {
537 ("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
541 mChunk
->CancelWait(this);
542 mWaitingForUpdate
= false;
545 mFile
->ReleaseOutsideLock(std::move(mChunk
));
548 void CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly
) {
549 mFile
->AssertOwnsLock();
551 LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
552 this, aReleaseOnly
));
556 uint32_t chunkIdx
= mPos
/ kChunkSize
;
558 if (mInReadSegments
) {
559 // We must have correct chunk
561 MOZ_ASSERT(mChunk
->Index() == chunkIdx
);
566 if (mChunk
->Index() == chunkIdx
) {
567 // we have a correct chunk
569 ("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
578 MOZ_ASSERT(!mWaitingForUpdate
);
580 if (aReleaseOnly
) return;
582 if (mListeningForChunk
== static_cast<int64_t>(chunkIdx
)) {
583 // We're already waiting for this chunk
585 ("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
586 "chunk %" PRId64
" [this=%p]",
587 mListeningForChunk
, this));
592 rv
= mFile
->GetChunkLocked(chunkIdx
, CacheFile::READER
, this,
593 getter_AddRefs(mChunk
));
596 ("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
597 "[this=%p, idx=%d, rv=0x%08" PRIx32
"]",
598 this, chunkIdx
, static_cast<uint32_t>(rv
)));
599 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
600 // Close the stream with error. The consumer will receive this error later
601 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
602 // differently since it is returned when the requested chunk is not
603 // available and there is no writer that could create it, i.e. it means
604 // that we've reached the end of the file.
605 CloseWithStatusLocked(rv
);
609 } else if (!mChunk
) {
610 mListeningForChunk
= static_cast<int64_t>(chunkIdx
);
613 MaybeNotifyListener();
616 int64_t CacheFileInputStream::CanRead(CacheFileChunkReadHandle
* aHandle
) {
617 mFile
->AssertOwnsLock();
620 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
622 int64_t retval
= aHandle
->Offset() + aHandle
->DataSize();
624 if (!mAlternativeData
&& mFile
->mAltDataOffset
!= -1 &&
625 mFile
->mAltDataOffset
< retval
) {
626 retval
= mFile
->mAltDataOffset
;
630 if (retval
<= 0 && NS_FAILED(mChunk
->GetStatus())) {
631 CloseWithStatusLocked(mChunk
->GetStatus());
634 LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%" PRId64
"]", this,
640 void CacheFileInputStream::NotifyListener() {
641 mFile
->AssertOwnsLock();
643 LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
645 MOZ_ASSERT(mCallback
);
646 MOZ_ASSERT(!mInReadSegments
);
648 if (!mCallbackTarget
) {
649 mCallbackTarget
= CacheFileIOManager::IOTarget();
650 if (!mCallbackTarget
) {
652 ("CacheFileInputStream::NotifyListener() - Cannot get Cache I/O "
653 "thread! Using main thread for callback."));
654 mCallbackTarget
= GetMainThreadSerialEventTarget();
658 nsCOMPtr
<nsIInputStreamCallback
> asyncCallback
= NS_NewInputStreamReadyEvent(
659 "CacheFileInputStream::NotifyListener", mCallback
, mCallbackTarget
);
662 mCallbackTarget
= nullptr;
664 asyncCallback
->OnInputStreamReady(this);
667 void CacheFileInputStream::MaybeNotifyListener() {
668 mFile
->AssertOwnsLock();
671 ("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
672 "mClosed=%d, mStatus=0x%08" PRIx32
673 ", mChunk=%p, mListeningForChunk=%" PRId64
", "
674 "mWaitingForUpdate=%d]",
675 this, mCallback
.get(), mClosed
, static_cast<uint32_t>(mStatus
),
676 mChunk
.get(), mListeningForChunk
, mWaitingForUpdate
));
678 MOZ_ASSERT(!mInReadSegments
);
680 if (!mCallback
) return;
682 if (mClosed
|| NS_FAILED(mStatus
)) {
688 if (mListeningForChunk
== -1) {
689 // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
695 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
697 if (mWaitingForUpdate
) return;
699 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
700 int64_t canRead
= CanRead(&hnd
);
701 if (NS_FAILED(mStatus
)) {
702 // CanRead() called CloseWithStatusLocked() which called
703 // MaybeNotifyListener() so the listener was already notified. Stop here.
704 MOZ_ASSERT(!mCallback
);
709 if (!(mCallbackFlags
& WAIT_CLOSURE_ONLY
)) NotifyListener();
710 } else if (canRead
== 0) {
711 if (!mFile
->OutputStreamExists(mAlternativeData
)) {
715 mChunk
->WaitForUpdate(this);
716 mWaitingForUpdate
= true;
719 // Output have set EOF before mPos?
720 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
727 size_t CacheFileInputStream::SizeOfIncludingThis(
728 mozilla::MallocSizeOf mallocSizeOf
) const {
729 // Everything the stream keeps a reference to is already reported somewhere
730 // else. mFile reports itself. mChunk reported as part of CacheFile. mCallback
731 // is usually CacheFile or a class that is reported elsewhere.
732 return mallocSizeOf(this);
735 } // namespace mozilla::net