1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 * The storage stream provides an internal buffer that can be filled by a
9 * client using a single output stream. One or more independent input streams
10 * can be created to read the data out non-destructively. The implementation
11 * uses a segmented buffer internally to avoid realloc'ing of large buffers,
12 * with the attendant performance loss and heap fragmentation.
15 #include "nsAlgorithm.h"
16 #include "nsStorageStream.h"
17 #include "nsSegmentedBuffer.h"
18 #include "nsStreamUtils.h"
20 #include "nsIInputStream.h"
21 #include "nsIIPCSerializableInputStream.h"
22 #include "nsISeekableStream.h"
24 #include "mozilla/Attributes.h"
25 #include "mozilla/Likely.h"
26 #include "mozilla/MathAlgorithms.h"
27 #include "mozilla/ipc/InputStreamUtils.h"
29 using mozilla::ipc::InputStreamParams
;
30 using mozilla::ipc::StringInputStreamParams
;
32 #if defined(PR_LOGGING)
34 // Log module for StorageStream logging...
36 // To enable logging (see prlog.h for full details):
38 // set NSPR_LOG_MODULES=StorageStreamLog:5
39 // set NSPR_LOG_FILE=nspr.log
41 // this enables PR_LOG_DEBUG level information and places all output in
44 static PRLogModuleInfo
*
47 static PRLogModuleInfo
* sLog
;
49 sLog
= PR_NewLogModule("nsStorageStream");
57 #define LOG(args) PR_LOG(GetStorageStreamLog(), PR_LOG_DEBUG, args)
59 nsStorageStream::nsStorageStream()
60 : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false),
61 mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
63 LOG(("Creating nsStorageStream [%p].\n", this));
66 nsStorageStream::~nsStorageStream()
68 delete mSegmentedBuffer
;
71 NS_IMPL_ISUPPORTS(nsStorageStream
,
76 nsStorageStream::Init(uint32_t aSegmentSize
, uint32_t aMaxSize
)
78 mSegmentedBuffer
= new nsSegmentedBuffer();
79 if (!mSegmentedBuffer
) {
80 return NS_ERROR_OUT_OF_MEMORY
;
83 mSegmentSize
= aSegmentSize
;
84 mSegmentSizeLog2
= mozilla::FloorLog2(aSegmentSize
);
86 // Segment size must be a power of two
87 if (mSegmentSize
!= ((uint32_t)1 << mSegmentSizeLog2
)) {
88 return NS_ERROR_INVALID_ARG
;
91 return mSegmentedBuffer
->Init(aSegmentSize
, aMaxSize
);
95 nsStorageStream::GetOutputStream(int32_t aStartingOffset
,
96 nsIOutputStream
** aOutputStream
)
98 if (NS_WARN_IF(!aOutputStream
)) {
99 return NS_ERROR_INVALID_ARG
;
101 if (NS_WARN_IF(!mSegmentedBuffer
)) {
102 return NS_ERROR_NOT_INITIALIZED
;
105 if (mWriteInProgress
) {
106 return NS_ERROR_NOT_AVAILABLE
;
109 nsresult rv
= Seek(aStartingOffset
);
114 // Enlarge the last segment in the buffer so that it is the same size as
115 // all the other segments in the buffer. (It may have been realloc'ed
116 // smaller in the Close() method.)
117 if (mLastSegmentNum
>= 0)
118 if (mSegmentedBuffer
->ReallocLastSegment(mSegmentSize
)) {
119 // Need to re-Seek, since realloc changed segment base pointer
120 rv
= Seek(aStartingOffset
);
127 *aOutputStream
= static_cast<nsIOutputStream
*>(this);
128 mWriteInProgress
= true;
133 nsStorageStream::Close()
135 if (NS_WARN_IF(!mSegmentedBuffer
)) {
136 return NS_ERROR_NOT_INITIALIZED
;
139 mWriteInProgress
= false;
141 int32_t segmentOffset
= SegOffset(mLogicalLength
);
143 // Shrink the final segment in the segmented buffer to the minimum size
144 // needed to contain the data, so as to conserve memory.
146 mSegmentedBuffer
->ReallocLastSegment(segmentOffset
);
152 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
153 this, mWriteCursor
, mSegmentEnd
));
159 nsStorageStream::Flush()
165 nsStorageStream::Write(const char* aBuffer
, uint32_t aCount
,
166 uint32_t* aNumWritten
)
168 if (NS_WARN_IF(!aNumWritten
) || NS_WARN_IF(!aBuffer
)) {
169 return NS_ERROR_INVALID_ARG
;
171 if (NS_WARN_IF(!mSegmentedBuffer
)) {
172 return NS_ERROR_NOT_INITIALIZED
;
175 const char* readCursor
;
176 uint32_t count
, availableInSegment
, remaining
;
179 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
180 this, mWriteCursor
, mSegmentEnd
, aCount
));
183 readCursor
= aBuffer
;
184 // If no segments have been created yet, create one even if we don't have
185 // to write any data; this enables creating an input stream which reads from
186 // the very end of the data for any amount of data in the stream (i.e.
187 // this stream contains N bytes of data and newInputStream(N) is called),
188 // even for N=0 (with the caveat that we require .write("", 0) be called to
189 // initialize internal buffers).
190 bool firstTime
= mSegmentedBuffer
->GetSegmentCount() == 0;
191 while (remaining
|| MOZ_UNLIKELY(firstTime
)) {
193 availableInSegment
= mSegmentEnd
- mWriteCursor
;
194 if (!availableInSegment
) {
195 mWriteCursor
= mSegmentedBuffer
->AppendNewSegment();
198 rv
= NS_ERROR_OUT_OF_MEMORY
;
202 mSegmentEnd
= mWriteCursor
+ mSegmentSize
;
203 availableInSegment
= mSegmentEnd
- mWriteCursor
;
204 LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n",
205 this, mWriteCursor
, mSegmentEnd
));
208 count
= XPCOM_MIN(availableInSegment
, remaining
);
209 memcpy(mWriteCursor
, readCursor
, count
);
212 mWriteCursor
+= count
;
213 LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
214 this, mWriteCursor
, mSegmentEnd
, count
));
218 *aNumWritten
= aCount
- remaining
;
219 mLogicalLength
+= *aNumWritten
;
221 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
222 this, mWriteCursor
, mSegmentEnd
, *aNumWritten
));
227 nsStorageStream::WriteFrom(nsIInputStream
* aInStr
, uint32_t aCount
,
230 return NS_ERROR_NOT_IMPLEMENTED
;
234 nsStorageStream::WriteSegments(nsReadSegmentFun aReader
, void* aClosure
,
235 uint32_t aCount
, uint32_t* aResult
)
237 return NS_ERROR_NOT_IMPLEMENTED
;
241 nsStorageStream::IsNonBlocking(bool* aNonBlocking
)
243 *aNonBlocking
= false;
248 nsStorageStream::GetLength(uint32_t* aLength
)
250 *aLength
= mLogicalLength
;
254 // Truncate the buffer by deleting the end segments
256 nsStorageStream::SetLength(uint32_t aLength
)
258 if (NS_WARN_IF(!mSegmentedBuffer
)) {
259 return NS_ERROR_NOT_INITIALIZED
;
262 if (mWriteInProgress
) {
263 return NS_ERROR_NOT_AVAILABLE
;
266 if (aLength
> mLogicalLength
) {
267 return NS_ERROR_INVALID_ARG
;
270 int32_t newLastSegmentNum
= SegNum(aLength
);
271 int32_t segmentOffset
= SegOffset(aLength
);
272 if (segmentOffset
== 0) {
276 while (newLastSegmentNum
< mLastSegmentNum
) {
277 mSegmentedBuffer
->DeleteLastSegment();
281 mLogicalLength
= aLength
;
286 nsStorageStream::GetWriteInProgress(bool* aWriteInProgress
)
288 *aWriteInProgress
= mWriteInProgress
;
293 nsStorageStream::Seek(int32_t aPosition
)
295 if (NS_WARN_IF(!mSegmentedBuffer
)) {
296 return NS_ERROR_NOT_INITIALIZED
;
299 // An argument of -1 means "seek to end of stream"
300 if (aPosition
== -1) {
301 aPosition
= mLogicalLength
;
304 // Seeking beyond the buffer end is illegal
305 if ((uint32_t)aPosition
> mLogicalLength
) {
306 return NS_ERROR_INVALID_ARG
;
309 // Seeking backwards in the write stream results in truncation
310 SetLength(aPosition
);
312 // Special handling for seek to start-of-buffer
313 if (aPosition
== 0) {
316 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
317 this, mWriteCursor
, mSegmentEnd
));
321 // Segment may have changed, so reset pointers
322 mWriteCursor
= mSegmentedBuffer
->GetSegment(mLastSegmentNum
);
323 NS_ASSERTION(mWriteCursor
, "null mWriteCursor");
324 mSegmentEnd
= mWriteCursor
+ mSegmentSize
;
326 // Adjust write cursor for current segment offset. This test is necessary
327 // because SegNum may reference the next-to-be-allocated segment, in which
328 // case we need to be pointing at the end of the last segment.
329 int32_t segmentOffset
= SegOffset(aPosition
);
330 if (segmentOffset
== 0 && (SegNum(aPosition
) > (uint32_t) mLastSegmentNum
)) {
331 mWriteCursor
= mSegmentEnd
;
333 mWriteCursor
+= segmentOffset
;
336 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
337 this, mWriteCursor
, mSegmentEnd
));
341 ////////////////////////////////////////////////////////////////////////////////
343 // There can be many nsStorageInputStreams for a single nsStorageStream
344 class nsStorageInputStream MOZ_FINAL
345 : public nsIInputStream
346 , public nsISeekableStream
347 , public nsIIPCSerializableInputStream
350 nsStorageInputStream(nsStorageStream
* aStorageStream
, uint32_t aSegmentSize
)
351 : mStorageStream(aStorageStream
), mReadCursor(0),
352 mSegmentEnd(0), mSegmentNum(0),
353 mSegmentSize(aSegmentSize
), mLogicalCursor(0),
356 NS_ADDREF(mStorageStream
);
359 NS_DECL_THREADSAFE_ISUPPORTS
360 NS_DECL_NSIINPUTSTREAM
361 NS_DECL_NSISEEKABLESTREAM
362 NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
365 ~nsStorageInputStream()
367 NS_IF_RELEASE(mStorageStream
);
371 NS_METHOD
Seek(uint32_t aPosition
);
373 friend class nsStorageStream
;
376 nsStorageStream
* mStorageStream
;
377 uint32_t mReadCursor
; // Next memory location to read byte, or 0
378 uint32_t mSegmentEnd
; // One byte past end of current buffer segment
379 uint32_t mSegmentNum
; // Segment number containing read cursor
380 uint32_t mSegmentSize
; // All segments, except the last, are of this size
381 uint32_t mLogicalCursor
; // Logical offset into stream
384 uint32_t SegNum(uint32_t aPosition
)
386 return aPosition
>> mStorageStream
->mSegmentSizeLog2
;
388 uint32_t SegOffset(uint32_t aPosition
)
390 return aPosition
& (mSegmentSize
- 1);
394 NS_IMPL_ISUPPORTS(nsStorageInputStream
,
397 nsIIPCSerializableInputStream
)
400 nsStorageStream::NewInputStream(int32_t aStartingOffset
,
401 nsIInputStream
** aInputStream
)
403 if (NS_WARN_IF(!mSegmentedBuffer
)) {
404 return NS_ERROR_NOT_INITIALIZED
;
407 nsStorageInputStream
* inputStream
=
408 new nsStorageInputStream(this, mSegmentSize
);
410 return NS_ERROR_OUT_OF_MEMORY
;
413 NS_ADDREF(inputStream
);
415 nsresult rv
= inputStream
->Seek(aStartingOffset
);
417 NS_RELEASE(inputStream
);
421 *aInputStream
= inputStream
;
426 nsStorageInputStream::Close()
428 mStatus
= NS_BASE_STREAM_CLOSED
;
433 nsStorageInputStream::Available(uint64_t* aAvailable
)
435 if (NS_FAILED(mStatus
)) {
439 *aAvailable
= mStorageStream
->mLogicalLength
- mLogicalCursor
;
444 nsStorageInputStream::Read(char* aBuffer
, uint32_t aCount
, uint32_t* aNumRead
)
446 return ReadSegments(NS_CopySegmentToBuffer
, aBuffer
, aCount
, aNumRead
);
450 nsStorageInputStream::ReadSegments(nsWriteSegmentFun aWriter
, void* aClosure
,
451 uint32_t aCount
, uint32_t* aNumRead
)
454 if (mStatus
== NS_BASE_STREAM_CLOSED
) {
457 if (NS_FAILED(mStatus
)) {
461 uint32_t count
, availableInSegment
, remainingCapacity
, bytesConsumed
;
464 remainingCapacity
= aCount
;
465 while (remainingCapacity
) {
466 availableInSegment
= mSegmentEnd
- mReadCursor
;
467 if (!availableInSegment
) {
468 uint32_t available
= mStorageStream
->mLogicalLength
- mLogicalCursor
;
475 mSegmentEnd
= XPCOM_MIN(mSegmentSize
, available
);
476 availableInSegment
= mSegmentEnd
;
478 const char* cur
= mStorageStream
->mSegmentedBuffer
->GetSegment(mSegmentNum
);
480 count
= XPCOM_MIN(availableInSegment
, remainingCapacity
);
481 rv
= aWriter(this, aClosure
, cur
+ mReadCursor
, aCount
- remainingCapacity
,
482 count
, &bytesConsumed
);
483 if (NS_FAILED(rv
) || (bytesConsumed
== 0)) {
486 remainingCapacity
-= bytesConsumed
;
487 mReadCursor
+= bytesConsumed
;
488 mLogicalCursor
+= bytesConsumed
;
492 *aNumRead
= aCount
- remainingCapacity
;
494 bool isWriteInProgress
= false;
495 if (NS_FAILED(mStorageStream
->GetWriteInProgress(&isWriteInProgress
))) {
496 isWriteInProgress
= false;
499 if (*aNumRead
== 0 && isWriteInProgress
) {
500 return NS_BASE_STREAM_WOULD_BLOCK
;
507 nsStorageInputStream::IsNonBlocking(bool* aNonBlocking
)
509 // TODO: This class should implement nsIAsyncInputStream so that callers
510 // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors.
512 *aNonBlocking
= true;
517 nsStorageInputStream::Seek(int32_t aWhence
, int64_t aOffset
)
519 if (NS_FAILED(mStatus
)) {
523 int64_t pos
= aOffset
;
529 pos
+= mLogicalCursor
;
532 pos
+= mStorageStream
->mLogicalLength
;
535 NS_NOTREACHED("unexpected whence value");
536 return NS_ERROR_UNEXPECTED
;
538 if (pos
== int64_t(mLogicalCursor
)) {
546 nsStorageInputStream::Tell(int64_t* aResult
)
548 if (NS_FAILED(mStatus
)) {
552 *aResult
= mLogicalCursor
;
557 nsStorageInputStream::SetEOF()
559 NS_NOTREACHED("nsStorageInputStream::SetEOF");
560 return NS_ERROR_NOT_IMPLEMENTED
;
564 nsStorageInputStream::Seek(uint32_t aPosition
)
566 uint32_t length
= mStorageStream
->mLogicalLength
;
567 if (aPosition
> length
) {
568 return NS_ERROR_INVALID_ARG
;
575 mSegmentNum
= SegNum(aPosition
);
576 mReadCursor
= SegOffset(aPosition
);
577 uint32_t available
= length
- aPosition
;
578 mSegmentEnd
= mReadCursor
+ XPCOM_MIN(mSegmentSize
- mReadCursor
, available
);
579 mLogicalCursor
= aPosition
;
584 nsStorageInputStream::Serialize(InputStreamParams
& aParams
, FileDescriptorArray
&)
588 mozilla::DebugOnly
<nsresult
> rv
= Tell(&offset
);
589 MOZ_ASSERT(NS_SUCCEEDED(rv
));
592 rv
= Available(&remaining
);
593 MOZ_ASSERT(NS_SUCCEEDED(rv
));
595 combined
.SetCapacity(remaining
);
596 uint32_t numRead
= 0;
598 rv
= Read(combined
.BeginWriting(), remaining
, &numRead
);
599 MOZ_ASSERT(NS_SUCCEEDED(rv
));
600 MOZ_ASSERT(numRead
== remaining
);
601 combined
.SetLength(numRead
);
603 rv
= Seek(NS_SEEK_SET
, offset
);
604 MOZ_ASSERT(NS_SUCCEEDED(rv
));
606 StringInputStreamParams params
;
607 params
.data() = combined
;
612 nsStorageInputStream::Deserialize(const InputStreamParams
& aParams
,
613 const FileDescriptorArray
&)
615 NS_NOTREACHED("We should never attempt to deserialize a storage input stream.");
620 NS_NewStorageStream(uint32_t aSegmentSize
, uint32_t aMaxSize
,
621 nsIStorageStream
** aResult
)
623 nsStorageStream
* storageStream
= new nsStorageStream();
624 if (!storageStream
) {
625 return NS_ERROR_OUT_OF_MEMORY
;
628 NS_ADDREF(storageStream
);
629 nsresult rv
= storageStream
->Init(aSegmentSize
, aMaxSize
);
631 NS_RELEASE(storageStream
);
634 *aResult
= storageStream
;
638 // Undefine LOG, so that other .cpp files (or their includes) won't complain
639 // about it already being defined, when we build in unified mode.