On x86 compilers without fastcall, simulate it when invoking traces and un-simulate...
[wine-gecko.git] / xpcom / io / nsStorageStream.cpp
bloba987282ea36766c72ab4badde64661ffc0d3efe1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sts=4 sw=4 cin et: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998-1999
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Pierre Phaneuf <pp@ludusdesign.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * The storage stream provides an internal buffer that can be filled by a
43 * client using a single output stream. One or more independent input streams
44 * can be created to read the data out non-destructively. The implementation
45 * uses a segmented buffer internally to avoid realloc'ing of large buffers,
46 * with the attendant performance loss and heap fragmentation.
49 #include "nsStorageStream.h"
50 #include "nsSegmentedBuffer.h"
51 #include "nsStreamUtils.h"
52 #include "nsCOMPtr.h"
53 #include "prbit.h"
54 #include "nsIInputStream.h"
55 #include "nsISeekableStream.h"
56 #include "prlog.h"
57 #include "nsInt64.h"
59 #if defined(PR_LOGGING)
61 // Log module for StorageStream logging...
63 // To enable logging (see prlog.h for full details):
65 // set NSPR_LOG_MODULES=StorageStreamLog:5
66 // set NSPR_LOG_FILE=nspr.log
68 // this enables PR_LOG_DEBUG level information and places all output in
69 // the file nspr.log
71 static PRLogModuleInfo* sLog = PR_NewLogModule("nsStorageStream");
72 #endif
73 #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
75 nsStorageStream::nsStorageStream()
76 : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(PR_FALSE),
77 mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
79 LOG(("Creating nsStorageStream [%p].\n", this));
82 nsStorageStream::~nsStorageStream()
84 if (mSegmentedBuffer)
85 delete mSegmentedBuffer;
88 NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageStream,
89 nsIStorageStream,
90 nsIOutputStream)
92 NS_IMETHODIMP
93 nsStorageStream::Init(PRUint32 segmentSize, PRUint32 maxSize,
94 nsIMemory *segmentAllocator)
96 mSegmentedBuffer = new nsSegmentedBuffer();
97 if (!mSegmentedBuffer)
98 return NS_ERROR_OUT_OF_MEMORY;
100 mSegmentSize = segmentSize;
101 mSegmentSizeLog2 = PR_FloorLog2(segmentSize);
103 // Segment size must be a power of two
104 if (mSegmentSize != ((PRUint32)1 << mSegmentSizeLog2))
105 return NS_ERROR_INVALID_ARG;
107 return mSegmentedBuffer->Init(segmentSize, maxSize, segmentAllocator);
110 NS_IMETHODIMP
111 nsStorageStream::GetOutputStream(PRInt32 aStartingOffset,
112 nsIOutputStream * *aOutputStream)
114 NS_ENSURE_ARG(aOutputStream);
115 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
117 if (mWriteInProgress)
118 return NS_ERROR_NOT_AVAILABLE;
120 nsresult rv = Seek(aStartingOffset);
121 if (NS_FAILED(rv)) return rv;
123 // Enlarge the last segment in the buffer so that it is the same size as
124 // all the other segments in the buffer. (It may have been realloc'ed
125 // smaller in the Close() method.)
126 if (mLastSegmentNum >= 0)
127 mSegmentedBuffer->ReallocLastSegment(mSegmentSize);
129 // Need to re-Seek, since realloc might have changed segment base pointer
130 rv = Seek(aStartingOffset);
131 if (NS_FAILED(rv)) return rv;
133 NS_ADDREF(this);
134 *aOutputStream = static_cast<nsIOutputStream*>(this);
135 mWriteInProgress = PR_TRUE;
136 return NS_OK;
139 NS_IMETHODIMP
140 nsStorageStream::Close()
142 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
144 mWriteInProgress = PR_FALSE;
146 PRInt32 segmentOffset = SegOffset(mLogicalLength);
148 // Shrink the final segment in the segmented buffer to the minimum size
149 // needed to contain the data, so as to conserve memory.
150 if (segmentOffset)
151 mSegmentedBuffer->ReallocLastSegment(segmentOffset);
153 mWriteCursor = 0;
154 mSegmentEnd = 0;
156 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
157 this, mWriteCursor, mSegmentEnd));
159 return NS_OK;
162 NS_IMETHODIMP
163 nsStorageStream::Flush()
165 return NS_OK;
168 NS_IMETHODIMP
169 nsStorageStream::Write(const char *aBuffer, PRUint32 aCount, PRUint32 *aNumWritten)
171 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
173 const char* readCursor;
174 PRUint32 count, availableInSegment, remaining;
175 nsresult rv = NS_OK;
177 NS_ENSURE_ARG_POINTER(aNumWritten);
178 NS_ENSURE_ARG(aBuffer);
180 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
181 this, mWriteCursor, mSegmentEnd, aCount));
183 remaining = aCount;
184 readCursor = aBuffer;
185 // If no segments have been created yet, create one even if we don't have
186 // to write any data; this enables creating an input stream which reads from
187 // the very end of the data for any amount of data in the stream (i.e.
188 // this stream contains N bytes of data and newInputStream(N) is called),
189 // even for N=0 (with the caveat that we require .write("", 0) be called to
190 // initialize internal buffers).
191 PRBool firstTime = mSegmentedBuffer->GetSegmentCount() == 0;
192 while (remaining || NS_UNLIKELY(firstTime)) {
193 firstTime = PR_FALSE;
194 availableInSegment = mSegmentEnd - mWriteCursor;
195 if (!availableInSegment) {
196 mWriteCursor = mSegmentedBuffer->AppendNewSegment();
197 if (!mWriteCursor) {
198 mSegmentEnd = 0;
199 rv = NS_ERROR_OUT_OF_MEMORY;
200 goto out;
202 mLastSegmentNum++;
203 mSegmentEnd = mWriteCursor + mSegmentSize;
204 availableInSegment = mSegmentEnd - mWriteCursor;
205 LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n",
206 this, mWriteCursor, mSegmentEnd));
209 count = PR_MIN(availableInSegment, remaining);
210 memcpy(mWriteCursor, readCursor, count);
211 remaining -= count;
212 readCursor += count;
213 mWriteCursor += count;
214 LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
215 this, mWriteCursor, mSegmentEnd, count));
218 out:
219 *aNumWritten = aCount - remaining;
220 mLogicalLength += *aNumWritten;
222 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
223 this, mWriteCursor, mSegmentEnd, *aNumWritten));
224 return rv;
227 NS_IMETHODIMP
228 nsStorageStream::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
230 NS_NOTREACHED("WriteFrom");
231 return NS_ERROR_NOT_IMPLEMENTED;
234 NS_IMETHODIMP
235 nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, PRUint32 count, PRUint32 *_retval)
237 NS_NOTREACHED("WriteSegments");
238 return NS_ERROR_NOT_IMPLEMENTED;
241 NS_IMETHODIMP
242 nsStorageStream::IsNonBlocking(PRBool *aNonBlocking)
244 *aNonBlocking = PR_TRUE;
245 return NS_OK;
248 NS_IMETHODIMP
249 nsStorageStream::GetLength(PRUint32 *aLength)
251 NS_ENSURE_ARG(aLength);
252 *aLength = mLogicalLength;
253 return NS_OK;
256 // Truncate the buffer by deleting the end segments
257 NS_IMETHODIMP
258 nsStorageStream::SetLength(PRUint32 aLength)
260 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
262 if (mWriteInProgress)
263 return NS_ERROR_NOT_AVAILABLE;
265 if (aLength > mLogicalLength)
266 return NS_ERROR_INVALID_ARG;
268 PRInt32 newLastSegmentNum = SegNum(aLength);
269 PRInt32 segmentOffset = SegOffset(aLength);
270 if (segmentOffset == 0)
271 newLastSegmentNum--;
273 while (newLastSegmentNum < mLastSegmentNum) {
274 mSegmentedBuffer->DeleteLastSegment();
275 mLastSegmentNum--;
278 mLogicalLength = aLength;
279 return NS_OK;
282 NS_IMETHODIMP
283 nsStorageStream::GetWriteInProgress(PRBool *aWriteInProgress)
285 NS_ENSURE_ARG(aWriteInProgress);
287 *aWriteInProgress = mWriteInProgress;
288 return NS_OK;
291 NS_METHOD
292 nsStorageStream::Seek(PRInt32 aPosition)
294 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
296 // An argument of -1 means "seek to end of stream"
297 if (aPosition == -1)
298 aPosition = mLogicalLength;
300 // Seeking beyond the buffer end is illegal
301 if ((PRUint32)aPosition > mLogicalLength)
302 return NS_ERROR_INVALID_ARG;
304 // Seeking backwards in the write stream results in truncation
305 SetLength(aPosition);
307 // Special handling for seek to start-of-buffer
308 if (aPosition == 0) {
309 mWriteCursor = 0;
310 mSegmentEnd = 0;
311 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
312 this, mWriteCursor, mSegmentEnd));
313 return NS_OK;
316 // Segment may have changed, so reset pointers
317 mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum);
318 NS_ASSERTION(mWriteCursor, "null mWriteCursor");
319 mSegmentEnd = mWriteCursor + mSegmentSize;
321 // Adjust write cursor for current segment offset. This test is necessary
322 // because SegNum may reference the next-to-be-allocated segment, in which
323 // case we need to be pointing at the end of the last segment.
324 PRInt32 segmentOffset = SegOffset(aPosition);
325 if (segmentOffset == 0 && (SegNum(aPosition) > (PRUint32) mLastSegmentNum))
326 mWriteCursor = mSegmentEnd;
327 else
328 mWriteCursor += segmentOffset;
330 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
331 this, mWriteCursor, mSegmentEnd));
332 return NS_OK;
335 ////////////////////////////////////////////////////////////////////////////////
337 // There can be many nsStorageInputStreams for a single nsStorageStream
338 class nsStorageInputStream : public nsIInputStream
339 , public nsISeekableStream
341 public:
342 nsStorageInputStream(nsStorageStream *aStorageStream, PRUint32 aSegmentSize)
343 : mStorageStream(aStorageStream), mReadCursor(0),
344 mSegmentEnd(0), mSegmentNum(0),
345 mSegmentSize(aSegmentSize), mLogicalCursor(0),
346 mStatus(NS_OK)
348 NS_ADDREF(mStorageStream);
351 NS_DECL_ISUPPORTS
352 NS_DECL_NSIINPUTSTREAM
353 NS_DECL_NSISEEKABLESTREAM
355 private:
356 ~nsStorageInputStream()
358 NS_IF_RELEASE(mStorageStream);
361 protected:
362 NS_METHOD Seek(PRUint32 aPosition);
364 friend class nsStorageStream;
366 private:
367 nsStorageStream* mStorageStream;
368 const char* mReadCursor; // Next memory location to read byte, or NULL
369 const char* mSegmentEnd; // One byte past end of current buffer segment
370 PRUint32 mSegmentNum; // Segment number containing read cursor
371 PRUint32 mSegmentSize; // All segments, except the last, are of this size
372 PRUint32 mLogicalCursor; // Logical offset into stream
373 nsresult mStatus;
375 PRUint32 SegNum(PRUint32 aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;}
376 PRUint32 SegOffset(PRUint32 aPosition) {return aPosition & (mSegmentSize - 1);}
379 NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageInputStream,
380 nsIInputStream,
381 nsISeekableStream)
383 NS_IMETHODIMP
384 nsStorageStream::NewInputStream(PRInt32 aStartingOffset, nsIInputStream* *aInputStream)
386 NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED);
388 nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize);
389 if (!inputStream)
390 return NS_ERROR_OUT_OF_MEMORY;
392 NS_ADDREF(inputStream);
394 nsresult rv = inputStream->Seek(aStartingOffset);
395 if (NS_FAILED(rv)) {
396 NS_RELEASE(inputStream);
397 return rv;
400 *aInputStream = inputStream;
401 return NS_OK;
404 NS_IMETHODIMP
405 nsStorageInputStream::Close()
407 mStatus = NS_BASE_STREAM_CLOSED;
408 return NS_OK;
411 NS_IMETHODIMP
412 nsStorageInputStream::Available(PRUint32 *aAvailable)
414 if (NS_FAILED(mStatus))
415 return mStatus;
417 *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor;
418 return NS_OK;
421 NS_IMETHODIMP
422 nsStorageInputStream::Read(char* aBuffer, PRUint32 aCount, PRUint32 *aNumRead)
424 return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead);
427 NS_IMETHODIMP
428 nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 aCount, PRUint32 *aNumRead)
430 *aNumRead = 0;
431 if (mStatus == NS_BASE_STREAM_CLOSED)
432 return NS_OK;
433 if (NS_FAILED(mStatus))
434 return mStatus;
436 PRUint32 count, availableInSegment, remainingCapacity, bytesConsumed;
437 nsresult rv;
439 remainingCapacity = aCount;
440 while (remainingCapacity) {
441 availableInSegment = mSegmentEnd - mReadCursor;
442 if (!availableInSegment) {
443 PRUint32 available = mStorageStream->mLogicalLength - mLogicalCursor;
444 if (!available)
445 goto out;
447 mReadCursor = mStorageStream->mSegmentedBuffer->GetSegment(++mSegmentNum);
448 mSegmentEnd = mReadCursor + PR_MIN(mSegmentSize, available);
449 availableInSegment = mSegmentEnd - mReadCursor;
452 count = PR_MIN(availableInSegment, remainingCapacity);
453 rv = writer(this, closure, mReadCursor, aCount - remainingCapacity,
454 count, &bytesConsumed);
455 if (NS_FAILED(rv) || (bytesConsumed == 0))
456 break;
457 remainingCapacity -= bytesConsumed;
458 mReadCursor += bytesConsumed;
459 mLogicalCursor += bytesConsumed;
462 out:
463 *aNumRead = aCount - remainingCapacity;
465 PRBool isWriteInProgress = PR_FALSE;
466 if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress)))
467 isWriteInProgress = PR_FALSE;
469 if (*aNumRead == 0 && isWriteInProgress)
470 return NS_BASE_STREAM_WOULD_BLOCK;
472 return NS_OK;
475 NS_IMETHODIMP
476 nsStorageInputStream::IsNonBlocking(PRBool *aNonBlocking)
478 // TODO: This class should implement nsIAsyncInputStream so that callers
479 // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors.
481 *aNonBlocking = PR_TRUE;
482 return NS_OK;
485 NS_IMETHODIMP
486 nsStorageInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
488 if (NS_FAILED(mStatus))
489 return mStatus;
491 PRInt64 pos = aOffset;
493 switch (aWhence) {
494 case NS_SEEK_SET:
495 break;
496 case NS_SEEK_CUR:
497 pos += mLogicalCursor;
498 break;
499 case NS_SEEK_END:
500 pos += mStorageStream->mLogicalLength;
501 break;
502 default:
503 NS_NOTREACHED("unexpected whence value");
504 return NS_ERROR_UNEXPECTED;
506 if (pos == PRInt64(mLogicalCursor))
507 return NS_OK;
509 return Seek(pos);
512 NS_IMETHODIMP
513 nsStorageInputStream::Tell(PRInt64 *aResult)
515 if (NS_FAILED(mStatus))
516 return mStatus;
518 LL_UI2L(*aResult, mLogicalCursor);
519 return NS_OK;
522 NS_IMETHODIMP
523 nsStorageInputStream::SetEOF()
525 NS_NOTREACHED("nsStorageInputStream::SetEOF");
526 return NS_ERROR_NOT_IMPLEMENTED;
529 NS_METHOD
530 nsStorageInputStream::Seek(PRUint32 aPosition)
532 PRUint32 length = mStorageStream->mLogicalLength;
533 if (aPosition > length)
534 return NS_ERROR_INVALID_ARG;
536 if (length == 0)
537 return NS_OK;
539 mSegmentNum = SegNum(aPosition);
540 PRUint32 segmentOffset = SegOffset(aPosition);
541 mReadCursor = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum) +
542 segmentOffset;
543 PRUint32 available = length - aPosition;
544 mSegmentEnd = mReadCursor + PR_MIN(mSegmentSize - segmentOffset, available);
545 mLogicalCursor = aPosition;
546 return NS_OK;
549 NS_COM nsresult
550 NS_NewStorageStream(PRUint32 segmentSize, PRUint32 maxSize, nsIStorageStream **result)
552 NS_ENSURE_ARG(result);
554 nsStorageStream* storageStream = new nsStorageStream();
555 if (!storageStream) return NS_ERROR_OUT_OF_MEMORY;
557 NS_ADDREF(storageStream);
558 nsresult rv = storageStream->Init(segmentSize, maxSize, nsnull);
559 if (NS_FAILED(rv)) {
560 NS_RELEASE(storageStream);
561 return rv;
563 *result = storageStream;
564 return NS_OK;