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"
13 namespace mozilla::net
{
15 NS_IMPL_ADDREF(CacheFileInputStream
)
16 NS_IMETHODIMP_(MozExternalRefCountType
)
17 CacheFileInputStream::Release() {
18 MOZ_ASSERT(0 != mRefCnt
, "dup release");
19 nsrefcnt count
= --mRefCnt
;
20 NS_LOG_RELEASE(this, count
, "CacheFileInputStream");
29 CacheFileAutoLock
lock(mFile
);
30 mFile
->RemoveInput(this, mStatus
);
36 NS_INTERFACE_MAP_BEGIN(CacheFileInputStream
)
37 NS_INTERFACE_MAP_ENTRY(nsIInputStream
)
38 NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream
)
39 NS_INTERFACE_MAP_ENTRY(nsISeekableStream
)
40 NS_INTERFACE_MAP_ENTRY(nsITellableStream
)
41 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener
)
42 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIInputStream
)
45 CacheFileInputStream::CacheFileInputStream(CacheFile
* aFile
,
47 bool aAlternativeData
)
52 mInReadSegments(false),
53 mWaitingForUpdate(false),
54 mAlternativeData(aAlternativeData
),
55 mListeningForChunk(-1),
57 mCacheEntryHandle(aEntry
) {
58 LOG(("CacheFileInputStream::CacheFileInputStream() [this=%p]", this));
60 if (mAlternativeData
) {
61 mPos
= mFile
->mAltDataOffset
;
65 CacheFileInputStream::~CacheFileInputStream() {
66 LOG(("CacheFileInputStream::~CacheFileInputStream() [this=%p]", this));
67 MOZ_ASSERT(!mInReadSegments
);
72 CacheFileInputStream::Close() {
73 LOG(("CacheFileInputStream::Close() [this=%p]", this));
74 return CloseWithStatus(NS_OK
);
78 CacheFileInputStream::Available(uint64_t* _retval
) {
79 CacheFileAutoLock
lock(mFile
);
83 ("CacheFileInputStream::Available() - Stream is closed. [this=%p, "
84 "status=0x%08" PRIx32
"]",
85 this, static_cast<uint32_t>(mStatus
)));
86 return NS_FAILED(mStatus
) ? mStatus
: NS_BASE_STREAM_CLOSED
;
89 EnsureCorrectChunk(false);
90 if (NS_FAILED(mStatus
)) {
92 ("CacheFileInputStream::Available() - EnsureCorrectChunk failed. "
93 "[this=%p, status=0x%08" PRIx32
"]",
94 this, static_cast<uint32_t>(mStatus
)));
102 int64_t canRead
= mFile
->BytesFromChunk(mChunk
->Index(), mAlternativeData
);
103 canRead
-= (mPos
% kChunkSize
);
107 } else if (canRead
== 0 && !mFile
->OutputStreamExists(mAlternativeData
)) {
108 rv
= NS_BASE_STREAM_CLOSED
;
112 LOG(("CacheFileInputStream::Available() [this=%p, retval=%" PRIu64
113 ", rv=0x%08" PRIx32
"]",
114 this, *_retval
, static_cast<uint32_t>(rv
)));
120 CacheFileInputStream::StreamStatus() {
121 CacheFileAutoLock
lock(mFile
);
125 ("CacheFileInputStream::StreamStatus() - Stream is closed. [this=%p, "
126 "status=0x%08" PRIx32
"]",
127 this, static_cast<uint32_t>(mStatus
)));
128 return NS_FAILED(mStatus
) ? mStatus
: NS_BASE_STREAM_CLOSED
;
135 CacheFileInputStream::Read(char* aBuf
, uint32_t aCount
, uint32_t* _retval
) {
136 LOG(("CacheFileInputStream::Read() [this=%p, count=%d]", this, aCount
));
137 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, _retval
);
141 CacheFileInputStream::ReadSegments(nsWriteSegmentFun aWriter
, void* aClosure
,
142 uint32_t aCount
, uint32_t* _retval
) {
143 CacheFileAutoLock
lock(mFile
);
145 LOG(("CacheFileInputStream::ReadSegments() [this=%p, count=%d]", this,
152 if (mInReadSegments
) {
154 ("CacheFileInputStream::ReadSegments() - Cannot be called while the "
155 "stream is in ReadSegments!"));
156 return NS_ERROR_UNEXPECTED
;
161 ("CacheFileInputStream::ReadSegments() - Stream is closed. [this=%p, "
162 "status=0x%08" PRIx32
"]",
163 this, static_cast<uint32_t>(mStatus
)));
165 if (NS_FAILED(mStatus
)) {
176 EnsureCorrectChunk(false);
179 if (NS_FAILED(mStatus
)) return mStatus
;
182 if (mListeningForChunk
== -1) {
185 return NS_BASE_STREAM_WOULD_BLOCK
;
188 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
189 int64_t canRead
= CanRead(&hnd
);
190 if (NS_FAILED(mStatus
)) {
195 // file was truncated ???
196 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
198 } else if (canRead
> 0) {
199 uint32_t toRead
= std::min(static_cast<uint32_t>(canRead
), aCount
);
201 const char* buf
= hnd
.Buf() + (mPos
- hnd
.Offset());
203 mInReadSegments
= true;
206 rv
= aWriter(this, aClosure
, buf
, *_retval
, toRead
, &read
);
209 mInReadSegments
= false;
211 if (NS_SUCCEEDED(rv
)) {
212 MOZ_ASSERT(read
<= toRead
,
213 "writer should not write more than we asked it to write");
220 // The last chunk is released after the caller closes this stream.
221 EnsureCorrectChunk(false);
223 if (mChunk
&& aCount
) {
224 // Check whether there is more data available to read.
231 // The stream was closed from aWriter, do the cleanup.
237 if (*_retval
== 0 && mFile
->OutputStreamExists(mAlternativeData
)) {
238 rv
= NS_BASE_STREAM_WOULD_BLOCK
;
247 LOG(("CacheFileInputStream::ReadSegments() [this=%p, rv=0x%08" PRIx32
249 this, static_cast<uint32_t>(rv
), *_retval
));
255 CacheFileInputStream::IsNonBlocking(bool* _retval
) {
260 // nsIAsyncInputStream
262 CacheFileInputStream::CloseWithStatus(nsresult aStatus
) {
263 CacheFileAutoLock
lock(mFile
);
265 LOG(("CacheFileInputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
267 this, static_cast<uint32_t>(aStatus
)));
269 CloseWithStatusLocked(aStatus
);
273 void CacheFileInputStream::CloseWithStatusLocked(nsresult aStatus
) {
275 ("CacheFileInputStream::CloseWithStatusLocked() [this=%p, "
276 "aStatus=0x%08" PRIx32
"]",
277 this, static_cast<uint32_t>(aStatus
)));
280 // We notify listener and null out mCallback immediately after closing
281 // the stream. If we're in ReadSegments we postpone notification until we
282 // step out from ReadSegments. So if the stream is already closed the
283 // following assertion must be true.
284 MOZ_ASSERT(!mCallback
|| mInReadSegments
);
289 mStatus
= NS_FAILED(aStatus
) ? aStatus
: NS_BASE_STREAM_CLOSED
;
291 if (!mInReadSegments
) {
296 void CacheFileInputStream::CleanUp() {
297 MOZ_ASSERT(!mInReadSegments
);
304 // TODO propagate error from input stream to other streams ???
306 MaybeNotifyListener();
308 mFile
->ReleaseOutsideLock(std::move(mCacheEntryHandle
));
312 CacheFileInputStream::AsyncWait(nsIInputStreamCallback
* aCallback
,
313 uint32_t aFlags
, uint32_t aRequestedCount
,
314 nsIEventTarget
* aEventTarget
) {
315 CacheFileAutoLock
lock(mFile
);
318 ("CacheFileInputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
319 "requestedCount=%d, eventTarget=%p]",
320 this, aCallback
, aFlags
, aRequestedCount
, aEventTarget
));
322 if (mInReadSegments
) {
324 ("CacheFileInputStream::AsyncWait() - Cannot be called while the stream"
325 " is in ReadSegments!"));
327 "Unexpected call. If it's a valid usage implement it. "
328 "Otherwise fix the caller.");
329 return NS_ERROR_UNEXPECTED
;
332 mCallback
= aCallback
;
333 mCallbackFlags
= aFlags
;
334 mCallbackTarget
= aEventTarget
;
337 if (mWaitingForUpdate
) {
338 mChunk
->CancelWait(this);
339 mWaitingForUpdate
= false;
349 EnsureCorrectChunk(false);
351 MaybeNotifyListener();
358 CacheFileInputStream::Seek(int32_t whence
, int64_t offset
) {
359 CacheFileAutoLock
lock(mFile
);
360 mFile
->AssertOwnsLock(); // For thread-safety analysis
362 LOG(("CacheFileInputStream::Seek() [this=%p, whence=%d, offset=%" PRId64
"]",
363 this, whence
, offset
));
365 if (mInReadSegments
) {
367 ("CacheFileInputStream::Seek() - Cannot be called while the stream is "
368 "in ReadSegments!"));
369 return NS_ERROR_UNEXPECTED
;
373 LOG(("CacheFileInputStream::Seek() - Stream is closed. [this=%p]", this));
374 return NS_BASE_STREAM_CLOSED
;
377 int64_t newPos
= offset
;
380 if (mAlternativeData
) {
381 newPos
+= mFile
->mAltDataOffset
;
388 if (mAlternativeData
) {
389 newPos
+= mFile
->mDataSize
;
391 newPos
+= mFile
->mAltDataOffset
;
395 NS_ERROR("invalid whence");
396 return NS_ERROR_INVALID_ARG
;
399 EnsureCorrectChunk(false);
401 LOG(("CacheFileInputStream::Seek() [this=%p, pos=%" PRId64
"]", this, mPos
));
406 CacheFileInputStream::SetEOF() {
407 MOZ_ASSERT(false, "Don't call SetEOF on cache input stream");
408 return NS_ERROR_NOT_IMPLEMENTED
;
413 CacheFileInputStream::Tell(int64_t* _retval
) {
414 CacheFileAutoLock
lock(mFile
);
415 mFile
->AssertOwnsLock(); // For thread-safety analysis
418 LOG(("CacheFileInputStream::Tell() - Stream is closed. [this=%p]", this));
419 return NS_BASE_STREAM_CLOSED
;
424 if (mAlternativeData
) {
425 *_retval
-= mFile
->mAltDataOffset
;
428 LOG(("CacheFileInputStream::Tell() [this=%p, retval=%" PRId64
"]", this,
433 // CacheFileChunkListener
434 nsresult
CacheFileInputStream::OnChunkRead(nsresult aResult
,
435 CacheFileChunk
* aChunk
) {
436 MOZ_CRASH("CacheFileInputStream::OnChunkRead should not be called!");
437 return NS_ERROR_UNEXPECTED
;
440 nsresult
CacheFileInputStream::OnChunkWritten(nsresult aResult
,
441 CacheFileChunk
* aChunk
) {
442 MOZ_CRASH("CacheFileInputStream::OnChunkWritten should not be called!");
443 return NS_ERROR_UNEXPECTED
;
446 nsresult
CacheFileInputStream::OnChunkAvailable(nsresult aResult
,
448 CacheFileChunk
* aChunk
) {
449 CacheFileAutoLock
lock(mFile
);
451 LOG(("CacheFileInputStream::OnChunkAvailable() [this=%p, result=0x%08" PRIx32
454 this, static_cast<uint32_t>(aResult
), aChunkIdx
, aChunk
));
456 MOZ_ASSERT(mListeningForChunk
!= -1);
458 if (mListeningForChunk
!= static_cast<int64_t>(aChunkIdx
)) {
459 // This is not a chunk that we're waiting for
461 ("CacheFileInputStream::OnChunkAvailable() - Notification is for a "
462 "different chunk. [this=%p, listeningForChunk=%" PRId64
"]",
463 this, mListeningForChunk
));
469 MOZ_ASSERT(!mWaitingForUpdate
);
470 MOZ_ASSERT(!mInReadSegments
);
471 mListeningForChunk
= -1;
474 MOZ_ASSERT(!mCallback
);
477 ("CacheFileInputStream::OnChunkAvailable() - Stream is closed, "
478 "ignoring notification. [this=%p]",
484 if (NS_SUCCEEDED(aResult
)) {
486 } else if (aResult
!= NS_ERROR_NOT_AVAILABLE
) {
487 // Close the stream with error. The consumer will receive this error later
488 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
489 // differently since it is returned when the requested chunk is not
490 // available and there is no writer that could create it, i.e. it means that
491 // we've reached the end of the file.
492 CloseWithStatusLocked(aResult
);
497 MaybeNotifyListener();
502 nsresult
CacheFileInputStream::OnChunkUpdated(CacheFileChunk
* aChunk
) {
503 CacheFileAutoLock
lock(mFile
);
505 LOG(("CacheFileInputStream::OnChunkUpdated() [this=%p, idx=%d]", this,
508 if (!mWaitingForUpdate
) {
510 ("CacheFileInputStream::OnChunkUpdated() - Ignoring notification since "
511 "mWaitingforUpdate == false. [this=%p]",
517 mWaitingForUpdate
= false;
519 MOZ_ASSERT(mChunk
== aChunk
);
521 MaybeNotifyListener();
526 void CacheFileInputStream::ReleaseChunk() {
527 mFile
->AssertOwnsLock();
529 LOG(("CacheFileInputStream::ReleaseChunk() [this=%p, idx=%d]", this,
532 MOZ_ASSERT(!mInReadSegments
);
534 if (mWaitingForUpdate
) {
536 ("CacheFileInputStream::ReleaseChunk() - Canceling waiting for update. "
540 mChunk
->CancelWait(this);
541 mWaitingForUpdate
= false;
544 mFile
->ReleaseOutsideLock(std::move(mChunk
));
547 void CacheFileInputStream::EnsureCorrectChunk(bool aReleaseOnly
) {
548 mFile
->AssertOwnsLock();
550 LOG(("CacheFileInputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
551 this, aReleaseOnly
));
555 uint32_t chunkIdx
= mPos
/ kChunkSize
;
557 if (mInReadSegments
) {
558 // We must have correct chunk
560 MOZ_ASSERT(mChunk
->Index() == chunkIdx
);
565 if (mChunk
->Index() == chunkIdx
) {
566 // we have a correct chunk
568 ("CacheFileInputStream::EnsureCorrectChunk() - Have correct chunk "
577 MOZ_ASSERT(!mWaitingForUpdate
);
579 if (aReleaseOnly
) return;
581 if (mListeningForChunk
== static_cast<int64_t>(chunkIdx
)) {
582 // We're already waiting for this chunk
584 ("CacheFileInputStream::EnsureCorrectChunk() - Already listening for "
585 "chunk %" PRId64
" [this=%p]",
586 mListeningForChunk
, this));
591 rv
= mFile
->GetChunkLocked(chunkIdx
, CacheFile::READER
, this,
592 getter_AddRefs(mChunk
));
595 ("CacheFileInputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
596 "[this=%p, idx=%d, rv=0x%08" PRIx32
"]",
597 this, chunkIdx
, static_cast<uint32_t>(rv
)));
598 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
599 // Close the stream with error. The consumer will receive this error later
600 // in Read(), Available() etc. We need to handle NS_ERROR_NOT_AVAILABLE
601 // differently since it is returned when the requested chunk is not
602 // available and there is no writer that could create it, i.e. it means
603 // that we've reached the end of the file.
604 CloseWithStatusLocked(rv
);
608 } else if (!mChunk
) {
609 mListeningForChunk
= static_cast<int64_t>(chunkIdx
);
612 MaybeNotifyListener();
615 int64_t CacheFileInputStream::CanRead(CacheFileChunkReadHandle
* aHandle
) {
616 mFile
->AssertOwnsLock();
619 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
621 int64_t retval
= aHandle
->Offset() + aHandle
->DataSize();
623 if (!mAlternativeData
&& mFile
->mAltDataOffset
!= -1 &&
624 mFile
->mAltDataOffset
< retval
) {
625 retval
= mFile
->mAltDataOffset
;
629 if (retval
<= 0 && NS_FAILED(mChunk
->GetStatus())) {
630 CloseWithStatusLocked(mChunk
->GetStatus());
633 LOG(("CacheFileInputStream::CanRead() [this=%p, canRead=%" PRId64
"]", this,
639 void CacheFileInputStream::NotifyListener() {
640 mFile
->AssertOwnsLock();
642 LOG(("CacheFileInputStream::NotifyListener() [this=%p]", this));
644 MOZ_ASSERT(mCallback
);
645 MOZ_ASSERT(!mInReadSegments
);
647 if (!mCallbackTarget
) {
648 mCallbackTarget
= CacheFileIOManager::IOTarget();
649 if (!mCallbackTarget
) {
651 ("CacheFileInputStream::NotifyListener() - Cannot get Cache I/O "
652 "thread! Using main thread for callback."));
653 mCallbackTarget
= GetMainThreadSerialEventTarget();
657 nsCOMPtr
<nsIInputStreamCallback
> asyncCallback
= NS_NewInputStreamReadyEvent(
658 "CacheFileInputStream::NotifyListener", mCallback
, mCallbackTarget
);
661 mCallbackTarget
= nullptr;
663 asyncCallback
->OnInputStreamReady(this);
666 void CacheFileInputStream::MaybeNotifyListener() {
667 mFile
->AssertOwnsLock();
670 ("CacheFileInputStream::MaybeNotifyListener() [this=%p, mCallback=%p, "
671 "mClosed=%d, mStatus=0x%08" PRIx32
672 ", mChunk=%p, mListeningForChunk=%" PRId64
", "
673 "mWaitingForUpdate=%d]",
674 this, mCallback
.get(), mClosed
, static_cast<uint32_t>(mStatus
),
675 mChunk
.get(), mListeningForChunk
, mWaitingForUpdate
));
677 MOZ_ASSERT(!mInReadSegments
);
679 if (!mCallback
) return;
681 if (mClosed
|| NS_FAILED(mStatus
)) {
687 if (mListeningForChunk
== -1) {
688 // EOF, should we notify even if mCallbackFlags == WAIT_CLOSURE_ONLY ??
694 MOZ_ASSERT(mPos
/ kChunkSize
== mChunk
->Index());
696 if (mWaitingForUpdate
) return;
698 CacheFileChunkReadHandle hnd
= mChunk
->GetReadHandle();
699 int64_t canRead
= CanRead(&hnd
);
700 if (NS_FAILED(mStatus
)) {
701 // CanRead() called CloseWithStatusLocked() which called
702 // MaybeNotifyListener() so the listener was already notified. Stop here.
703 MOZ_ASSERT(!mCallback
);
708 if (!(mCallbackFlags
& WAIT_CLOSURE_ONLY
)) NotifyListener();
709 } else if (canRead
== 0) {
710 if (!mFile
->OutputStreamExists(mAlternativeData
)) {
714 mChunk
->WaitForUpdate(this);
715 mWaitingForUpdate
= true;
718 // Output have set EOF before mPos?
719 MOZ_ASSERT(false, "SetEOF is currenty not implemented?!");
726 size_t CacheFileInputStream::SizeOfIncludingThis(
727 mozilla::MallocSizeOf mallocSizeOf
) const {
728 // Everything the stream keeps a reference to is already reported somewhere
729 // else. mFile reports itself. mChunk reported as part of CacheFile. mCallback
730 // is usually CacheFile or a class that is reported elsewhere.
731 return mallocSizeOf(this);
734 } // namespace mozilla::net