Bumping manifests a=b2g-bump
[gecko.git] / xpcom / io / nsStorageStream.cpp
blob1c34640a35f61ac1bba598706f384a79c30db893
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/. */
7 /*
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"
19 #include "nsCOMPtr.h"
20 #include "nsIInputStream.h"
21 #include "nsIIPCSerializableInputStream.h"
22 #include "nsISeekableStream.h"
23 #include "prlog.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
42 // the file nspr.log
44 static PRLogModuleInfo*
45 GetStorageStreamLog()
47 static PRLogModuleInfo* sLog;
48 if (!sLog) {
49 sLog = PR_NewLogModule("nsStorageStream");
51 return sLog;
53 #endif
54 #ifdef LOG
55 #undef LOG
56 #endif
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,
72 nsIStorageStream,
73 nsIOutputStream)
75 NS_IMETHODIMP
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);
94 NS_IMETHODIMP
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);
110 if (NS_FAILED(rv)) {
111 return rv;
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);
121 if (NS_FAILED(rv)) {
122 return rv;
126 NS_ADDREF(this);
127 *aOutputStream = static_cast<nsIOutputStream*>(this);
128 mWriteInProgress = true;
129 return NS_OK;
132 NS_IMETHODIMP
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.
145 if (segmentOffset) {
146 mSegmentedBuffer->ReallocLastSegment(segmentOffset);
149 mWriteCursor = 0;
150 mSegmentEnd = 0;
152 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
153 this, mWriteCursor, mSegmentEnd));
155 return NS_OK;
158 NS_IMETHODIMP
159 nsStorageStream::Flush()
161 return NS_OK;
164 NS_IMETHODIMP
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;
177 nsresult rv = NS_OK;
179 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
180 this, mWriteCursor, mSegmentEnd, aCount));
182 remaining = 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)) {
192 firstTime = false;
193 availableInSegment = mSegmentEnd - mWriteCursor;
194 if (!availableInSegment) {
195 mWriteCursor = mSegmentedBuffer->AppendNewSegment();
196 if (!mWriteCursor) {
197 mSegmentEnd = 0;
198 rv = NS_ERROR_OUT_OF_MEMORY;
199 goto out;
201 mLastSegmentNum++;
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);
210 remaining -= count;
211 readCursor += count;
212 mWriteCursor += count;
213 LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
214 this, mWriteCursor, mSegmentEnd, count));
217 out:
218 *aNumWritten = aCount - remaining;
219 mLogicalLength += *aNumWritten;
221 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
222 this, mWriteCursor, mSegmentEnd, *aNumWritten));
223 return rv;
226 NS_IMETHODIMP
227 nsStorageStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount,
228 uint32_t* aResult)
230 return NS_ERROR_NOT_IMPLEMENTED;
233 NS_IMETHODIMP
234 nsStorageStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
235 uint32_t aCount, uint32_t* aResult)
237 return NS_ERROR_NOT_IMPLEMENTED;
240 NS_IMETHODIMP
241 nsStorageStream::IsNonBlocking(bool* aNonBlocking)
243 *aNonBlocking = false;
244 return NS_OK;
247 NS_IMETHODIMP
248 nsStorageStream::GetLength(uint32_t* aLength)
250 *aLength = mLogicalLength;
251 return NS_OK;
254 // Truncate the buffer by deleting the end segments
255 NS_IMETHODIMP
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) {
273 newLastSegmentNum--;
276 while (newLastSegmentNum < mLastSegmentNum) {
277 mSegmentedBuffer->DeleteLastSegment();
278 mLastSegmentNum--;
281 mLogicalLength = aLength;
282 return NS_OK;
285 NS_IMETHODIMP
286 nsStorageStream::GetWriteInProgress(bool* aWriteInProgress)
288 *aWriteInProgress = mWriteInProgress;
289 return NS_OK;
292 NS_METHOD
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) {
314 mWriteCursor = 0;
315 mSegmentEnd = 0;
316 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
317 this, mWriteCursor, mSegmentEnd));
318 return NS_OK;
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;
332 } else {
333 mWriteCursor += segmentOffset;
336 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
337 this, mWriteCursor, mSegmentEnd));
338 return NS_OK;
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
349 public:
350 nsStorageInputStream(nsStorageStream* aStorageStream, uint32_t aSegmentSize)
351 : mStorageStream(aStorageStream), mReadCursor(0),
352 mSegmentEnd(0), mSegmentNum(0),
353 mSegmentSize(aSegmentSize), mLogicalCursor(0),
354 mStatus(NS_OK)
356 NS_ADDREF(mStorageStream);
359 NS_DECL_THREADSAFE_ISUPPORTS
360 NS_DECL_NSIINPUTSTREAM
361 NS_DECL_NSISEEKABLESTREAM
362 NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
364 private:
365 ~nsStorageInputStream()
367 NS_IF_RELEASE(mStorageStream);
370 protected:
371 NS_METHOD Seek(uint32_t aPosition);
373 friend class nsStorageStream;
375 private:
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
382 nsresult mStatus;
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,
395 nsIInputStream,
396 nsISeekableStream,
397 nsIIPCSerializableInputStream)
399 NS_IMETHODIMP
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);
409 if (!inputStream) {
410 return NS_ERROR_OUT_OF_MEMORY;
413 NS_ADDREF(inputStream);
415 nsresult rv = inputStream->Seek(aStartingOffset);
416 if (NS_FAILED(rv)) {
417 NS_RELEASE(inputStream);
418 return rv;
421 *aInputStream = inputStream;
422 return NS_OK;
425 NS_IMETHODIMP
426 nsStorageInputStream::Close()
428 mStatus = NS_BASE_STREAM_CLOSED;
429 return NS_OK;
432 NS_IMETHODIMP
433 nsStorageInputStream::Available(uint64_t* aAvailable)
435 if (NS_FAILED(mStatus)) {
436 return mStatus;
439 *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor;
440 return NS_OK;
443 NS_IMETHODIMP
444 nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead)
446 return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead);
449 NS_IMETHODIMP
450 nsStorageInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
451 uint32_t aCount, uint32_t* aNumRead)
453 *aNumRead = 0;
454 if (mStatus == NS_BASE_STREAM_CLOSED) {
455 return NS_OK;
457 if (NS_FAILED(mStatus)) {
458 return mStatus;
461 uint32_t count, availableInSegment, remainingCapacity, bytesConsumed;
462 nsresult rv;
464 remainingCapacity = aCount;
465 while (remainingCapacity) {
466 availableInSegment = mSegmentEnd - mReadCursor;
467 if (!availableInSegment) {
468 uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor;
469 if (!available) {
470 goto out;
473 mSegmentNum++;
474 mReadCursor = 0;
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)) {
484 break;
486 remainingCapacity -= bytesConsumed;
487 mReadCursor += bytesConsumed;
488 mLogicalCursor += bytesConsumed;
491 out:
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;
503 return NS_OK;
506 NS_IMETHODIMP
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;
513 return NS_OK;
516 NS_IMETHODIMP
517 nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset)
519 if (NS_FAILED(mStatus)) {
520 return mStatus;
523 int64_t pos = aOffset;
525 switch (aWhence) {
526 case NS_SEEK_SET:
527 break;
528 case NS_SEEK_CUR:
529 pos += mLogicalCursor;
530 break;
531 case NS_SEEK_END:
532 pos += mStorageStream->mLogicalLength;
533 break;
534 default:
535 NS_NOTREACHED("unexpected whence value");
536 return NS_ERROR_UNEXPECTED;
538 if (pos == int64_t(mLogicalCursor)) {
539 return NS_OK;
542 return Seek(pos);
545 NS_IMETHODIMP
546 nsStorageInputStream::Tell(int64_t* aResult)
548 if (NS_FAILED(mStatus)) {
549 return mStatus;
552 *aResult = mLogicalCursor;
553 return NS_OK;
556 NS_IMETHODIMP
557 nsStorageInputStream::SetEOF()
559 NS_NOTREACHED("nsStorageInputStream::SetEOF");
560 return NS_ERROR_NOT_IMPLEMENTED;
563 NS_METHOD
564 nsStorageInputStream::Seek(uint32_t aPosition)
566 uint32_t length = mStorageStream->mLogicalLength;
567 if (aPosition > length) {
568 return NS_ERROR_INVALID_ARG;
571 if (length == 0) {
572 return NS_OK;
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;
580 return NS_OK;
583 void
584 nsStorageInputStream::Serialize(InputStreamParams& aParams, FileDescriptorArray&)
586 nsCString combined;
587 int64_t offset;
588 mozilla::DebugOnly<nsresult> rv = Tell(&offset);
589 MOZ_ASSERT(NS_SUCCEEDED(rv));
591 uint64_t remaining;
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;
608 aParams = params;
611 bool
612 nsStorageInputStream::Deserialize(const InputStreamParams& aParams,
613 const FileDescriptorArray&)
615 NS_NOTREACHED("We should never attempt to deserialize a storage input stream.");
616 return false;
619 nsresult
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);
630 if (NS_FAILED(rv)) {
631 NS_RELEASE(storageStream);
632 return rv;
634 *aResult = storageStream;
635 return NS_OK;
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.
640 #undef LOG