1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #include "nsBufferedStreams.h"
39 #include "nsStreamUtils.h"
49 # define MAX_BIG_SEEKS 20
52 PRUint32 mSeeksWithinBuffer
;
53 PRUint32 mSeeksOutsideBuffer
;
54 PRUint32 mBufferReadUponSeek
;
55 PRUint32 mBufferUnreadUponSeek
;
56 PRUint32 mBytesReadFromBuffer
;
57 PRUint32 mBigSeekIndex
;
61 } mBigSeek
[MAX_BIG_SEEKS
];
64 # define METER(x) /* nothing */
67 ////////////////////////////////////////////////////////////////////////////////
70 nsBufferedStream::nsBufferedStream()
72 mBufferStartOffset(0),
76 mBufferDisabled(PR_FALSE
),
81 nsBufferedStream::~nsBufferedStream()
86 NS_IMPL_THREADSAFE_ISUPPORTS1(nsBufferedStream
, nsISeekableStream
)
89 nsBufferedStream::Init(nsISupports
* stream
, PRUint32 bufferSize
)
91 NS_ASSERTION(stream
, "need to supply a stream");
92 NS_ASSERTION(mStream
== nsnull
, "already inited");
94 NS_IF_ADDREF(mStream
);
95 mBufferSize
= bufferSize
;
96 mBufferStartOffset
= 0;
98 mBuffer
= new char[bufferSize
];
99 if (mBuffer
== nsnull
)
100 return NS_ERROR_OUT_OF_MEMORY
;
105 nsBufferedStream::Close()
107 NS_IF_RELEASE(mStream
);
112 mBufferStartOffset
= 0;
120 tfp
= fopen("/tmp/bufstats", "w");
122 setvbuf(tfp
, NULL
, _IOLBF
, 0);
125 fprintf(tfp
, "seeks within buffer: %u\n",
126 bufstats
.mSeeksWithinBuffer
);
127 fprintf(tfp
, "seeks outside buffer: %u\n",
128 bufstats
.mSeeksOutsideBuffer
);
129 fprintf(tfp
, "buffer read on seek: %u\n",
130 bufstats
.mBufferReadUponSeek
);
131 fprintf(tfp
, "buffer unread on seek: %u\n",
132 bufstats
.mBufferUnreadUponSeek
);
133 fprintf(tfp
, "bytes read from buffer: %u\n",
134 bufstats
.mBytesReadFromBuffer
);
135 for (PRUint32 i
= 0; i
< bufstats
.mBigSeekIndex
; i
++) {
136 fprintf(tfp
, "bigseek[%u] = {old: %u, new: %u}\n",
138 bufstats
.mBigSeek
[i
].mOldOffset
,
139 bufstats
.mBigSeek
[i
].mNewOffset
);
148 nsBufferedStream::Seek(PRInt32 whence
, PRInt64 offset
)
150 if (mStream
== nsnull
)
151 return NS_BASE_STREAM_CLOSED
;
153 // If the underlying stream isn't a random access store, then fail early.
154 // We could possibly succeed for the case where the seek position denotes
155 // something that happens to be read into the buffer, but that would make
156 // the failure data-dependent.
158 nsCOMPtr
<nsISeekableStream
> ras
= do_QueryInterface(mStream
, &rv
);
159 if (NS_FAILED(rv
)) return rv
;
163 case nsISeekableStream::NS_SEEK_SET
:
166 case nsISeekableStream::NS_SEEK_CUR
:
167 absPos
= mBufferStartOffset
;
171 case nsISeekableStream::NS_SEEK_END
:
175 NS_NOTREACHED("bogus seek whence parameter");
176 return NS_ERROR_UNEXPECTED
;
179 // Let mCursor point into the existing buffer if the new position is
180 // between the current cursor and the mFillPoint "fencepost" -- the
181 // client may never get around to a Read or Write after this Seek.
182 // Read and Write worry about flushing and filling in that event.
183 PRUint32 offsetInBuffer
= PRUint32(absPos
- mBufferStartOffset
);
184 if (offsetInBuffer
<= mFillPoint
) {
185 METER(bufstats
.mSeeksWithinBuffer
++);
186 mCursor
= offsetInBuffer
;
190 METER(bufstats
.mSeeksOutsideBuffer
++);
191 METER(bufstats
.mBufferReadUponSeek
+= mCursor
);
192 METER(bufstats
.mBufferUnreadUponSeek
+= mFillPoint
- mCursor
);
194 if (NS_FAILED(rv
)) return rv
;
196 rv
= ras
->Seek(whence
, offset
);
197 if (NS_FAILED(rv
)) return rv
;
199 METER(if (bufstats
.mBigSeekIndex
< MAX_BIG_SEEKS
)
200 bufstats
.mBigSeek
[bufstats
.mBigSeekIndex
].mOldOffset
=
201 mBufferStartOffset
+ nsInt64(mCursor
));
202 const nsInt64 minus1
= -1;
203 if (absPos
== minus1
) {
204 // then we had the SEEK_END case, above
206 rv
= ras
->Tell(&tellPos
);
207 mBufferStartOffset
= tellPos
;
208 if (NS_FAILED(rv
)) return rv
;
211 mBufferStartOffset
= absPos
;
213 METER(if (bufstats
.mBigSeekIndex
< MAX_BIG_SEEKS
)
214 bufstats
.mBigSeek
[bufstats
.mBigSeekIndex
++].mNewOffset
=
217 mFillPoint
= mCursor
= 0;
222 nsBufferedStream::Tell(PRInt64
*result
)
224 if (mStream
== nsnull
)
225 return NS_BASE_STREAM_CLOSED
;
227 nsInt64 result64
= mBufferStartOffset
;
234 nsBufferedStream::SetEOF()
236 if (mStream
== nsnull
)
237 return NS_BASE_STREAM_CLOSED
;
240 nsCOMPtr
<nsISeekableStream
> ras
= do_QueryInterface(mStream
, &rv
);
241 if (NS_FAILED(rv
)) return rv
;
243 return ras
->SetEOF();
246 ////////////////////////////////////////////////////////////////////////////////
247 // nsBufferedInputStream
249 NS_IMPL_ISUPPORTS_INHERITED3(nsBufferedInputStream
,
252 nsIBufferedInputStream
,
253 nsIStreamBufferAccess
)
256 nsBufferedInputStream::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
258 NS_ENSURE_NO_AGGREGATION(aOuter
);
260 nsBufferedInputStream
* stream
= new nsBufferedInputStream();
261 if (stream
== nsnull
)
262 return NS_ERROR_OUT_OF_MEMORY
;
264 nsresult rv
= stream
->QueryInterface(aIID
, aResult
);
270 nsBufferedInputStream::Init(nsIInputStream
* stream
, PRUint32 bufferSize
)
272 return nsBufferedStream::Init(stream
, bufferSize
);
276 nsBufferedInputStream::Close()
278 nsresult rv1
= NS_OK
, rv2
;
280 rv1
= Source()->Close();
283 rv2
= nsBufferedStream::Close();
284 if (NS_FAILED(rv1
)) return rv1
;
289 nsBufferedInputStream::Available(PRUint32
*result
)
294 rv
= Source()->Available(result
);
296 *result
+= (mFillPoint
- mCursor
);
301 nsBufferedInputStream::Read(char * buf
, PRUint32 count
, PRUint32
*result
)
303 if (mBufferDisabled
) {
308 nsresult rv
= Source()->Read(buf
, count
, result
);
309 if (NS_SUCCEEDED(rv
))
310 mBufferStartOffset
+= *result
; // so nsBufferedStream::Tell works
314 return ReadSegments(NS_CopySegmentToBuffer
, buf
, count
, result
);
318 nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer
, void *closure
,
319 PRUint32 count
, PRUint32
*result
)
328 PRUint32 amt
= PR_MIN(count
, mFillPoint
- mCursor
);
331 rv
= writer(this, closure
, mBuffer
+ mCursor
, *result
, amt
, &read
);
333 // errors returned from the writer end here!
343 if (NS_FAILED(rv
) || mFillPoint
== mCursor
)
347 return (*result
> 0) ? NS_OK
: rv
;
351 nsBufferedInputStream::IsNonBlocking(PRBool
*aNonBlocking
)
354 return Source()->IsNonBlocking(aNonBlocking
);
355 return NS_ERROR_NOT_INITIALIZED
;
359 nsBufferedInputStream::Fill()
363 NS_ENSURE_TRUE(mStream
, NS_ERROR_NOT_INITIALIZED
);
366 PRInt32 rem
= PRInt32(mFillPoint
- mCursor
);
368 // slide the remainder down to the start of the buffer
369 // |<------------->|<--rem-->|<--->|
371 memcpy(mBuffer
, mBuffer
+ mCursor
, rem
);
373 mBufferStartOffset
+= mCursor
;
378 rv
= Source()->Read(mBuffer
+ mFillPoint
, mBufferSize
- mFillPoint
, &amt
);
379 if (NS_FAILED(rv
)) return rv
;
385 NS_IMETHODIMP_(char*)
386 nsBufferedInputStream::GetBuffer(PRUint32 aLength
, PRUint32 aAlignMask
)
388 NS_ASSERTION(mGetBufferCount
== 0, "nested GetBuffer!");
389 if (mGetBufferCount
!= 0)
395 char* buf
= mBuffer
+ mCursor
;
396 PRUint32 rem
= mFillPoint
- mCursor
;
398 if (NS_FAILED(Fill()))
400 buf
= mBuffer
+ mCursor
;
401 rem
= mFillPoint
- mCursor
;
404 PRUint32 mod
= (NS_PTR_TO_INT32(buf
) & aAlignMask
);
406 PRUint32 pad
= aAlignMask
+ 1 - mod
;
423 nsBufferedInputStream::PutBuffer(char* aBuffer
, PRUint32 aLength
)
425 NS_ASSERTION(mGetBufferCount
== 1, "stray PutBuffer!");
426 if (--mGetBufferCount
!= 0)
429 NS_ASSERTION(mCursor
+ aLength
<= mFillPoint
, "PutBuffer botch");
434 nsBufferedInputStream::DisableBuffering()
436 NS_ASSERTION(!mBufferDisabled
, "redundant call to DisableBuffering!");
437 NS_ASSERTION(mGetBufferCount
== 0,
438 "DisableBuffer call between GetBuffer and PutBuffer!");
439 if (mGetBufferCount
!= 0)
440 return NS_ERROR_UNEXPECTED
;
442 // Empty the buffer so nsBufferedStream::Tell works.
443 mBufferStartOffset
+= mCursor
;
444 mFillPoint
= mCursor
= 0;
445 mBufferDisabled
= PR_TRUE
;
450 nsBufferedInputStream::EnableBuffering()
452 NS_ASSERTION(mBufferDisabled
, "gratuitous call to EnableBuffering!");
453 mBufferDisabled
= PR_FALSE
;
458 nsBufferedInputStream::GetUnbufferedStream(nsISupports
* *aStream
)
460 // Empty the buffer so subsequent i/o trumps any buffered data.
461 mBufferStartOffset
+= mCursor
;
462 mFillPoint
= mCursor
= 0;
465 NS_IF_ADDREF(*aStream
);
469 ////////////////////////////////////////////////////////////////////////////////
470 // nsBufferedOutputStream
472 NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream
, nsBufferedStream
)
473 NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream
, nsBufferedStream
)
474 // This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
475 // non-nullness of mSafeStream.
476 NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream
)
477 NS_INTERFACE_MAP_ENTRY(nsIOutputStream
)
478 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream
, mSafeStream
)
479 NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream
)
480 NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess
)
481 NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream
)
484 nsBufferedOutputStream::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
486 NS_ENSURE_NO_AGGREGATION(aOuter
);
488 nsBufferedOutputStream
* stream
= new nsBufferedOutputStream();
489 if (stream
== nsnull
)
490 return NS_ERROR_OUT_OF_MEMORY
;
492 nsresult rv
= stream
->QueryInterface(aIID
, aResult
);
498 nsBufferedOutputStream::Init(nsIOutputStream
* stream
, PRUint32 bufferSize
)
500 // QI stream to an nsISafeOutputStream, to see if we should support it
501 mSafeStream
= do_QueryInterface(stream
);
503 return nsBufferedStream::Init(stream
, bufferSize
);
507 nsBufferedOutputStream::Close()
509 nsresult rv1
, rv2
= NS_OK
, rv3
;
511 // If we fail to Flush all the data, then we close anyway and drop the
512 // remaining data in the buffer. We do this because it's what Unix does
513 // for fclose and close. However, we report the error from Flush anyway.
515 rv2
= Sink()->Close();
518 rv3
= nsBufferedStream::Close();
519 if (NS_FAILED(rv1
)) return rv1
;
520 if (NS_FAILED(rv2
)) return rv2
;
525 nsBufferedOutputStream::Write(const char *buf
, PRUint32 count
, PRUint32
*result
)
528 PRUint32 written
= 0;
530 PRUint32 amt
= PR_MIN(count
, mBufferSize
- mCursor
);
532 memcpy(mBuffer
+ mCursor
, buf
+ written
, amt
);
536 if (mFillPoint
< mCursor
)
537 mFillPoint
= mCursor
;
540 NS_ASSERTION(mFillPoint
, "iloop in nsBufferedOutputStream::Write!");
542 if (NS_FAILED(rv
)) break;
546 return (written
> 0) ? NS_OK
: rv
;
550 nsBufferedOutputStream::Flush()
555 // Stream already cancelled/flushed; probably because of error.
558 rv
= Sink()->Write(mBuffer
, mFillPoint
, &amt
);
559 if (NS_FAILED(rv
)) return rv
;
560 mBufferStartOffset
+= amt
;
561 if (amt
== mFillPoint
) {
562 mFillPoint
= mCursor
= 0;
563 return NS_OK
; // flushed everything
566 // slide the remainder down to the start of the buffer
567 // |<-------------->|<---|----->|
569 PRUint32 rem
= mFillPoint
- amt
;
570 memcpy(mBuffer
, mBuffer
+ amt
, rem
);
571 mFillPoint
= mCursor
= rem
;
572 return NS_ERROR_FAILURE
; // didn't flush all
575 // nsISafeOutputStream
577 nsBufferedOutputStream::Finish()
579 // flush the stream, to write out any buffered data...
580 nsresult rv
= nsBufferedOutputStream::Flush();
582 NS_WARNING("failed to flush buffered data! possible dataloss");
584 // ... and finish the underlying stream...
585 if (NS_SUCCEEDED(rv
))
586 rv
= mSafeStream
->Finish();
590 // ... and close the buffered stream, so any further attempts to flush/close
591 // the buffered stream won't cause errors.
592 nsBufferedStream::Close();
598 nsReadFromInputStream(nsIOutputStream
* outStr
,
605 nsIInputStream
* fromStream
= (nsIInputStream
*)closure
;
606 return fromStream
->Read(toRawSegment
, count
, readCount
);
610 nsBufferedOutputStream::WriteFrom(nsIInputStream
*inStr
, PRUint32 count
, PRUint32
*_retval
)
612 return WriteSegments(nsReadFromInputStream
, inStr
, count
, _retval
);
616 nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader
, void * closure
, PRUint32 count
, PRUint32
*_retval
)
621 PRUint32 left
= PR_MIN(count
, mBufferSize
- mCursor
);
631 rv
= reader(this, closure
, mBuffer
+ mCursor
, *_retval
, left
, &read
);
633 if (NS_FAILED(rv
)) // If we have written some data, return ok
634 return (*_retval
> 0) ? NS_OK
: rv
;
638 mFillPoint
= PR_MAX(mFillPoint
, mCursor
);
644 nsBufferedOutputStream::IsNonBlocking(PRBool
*aNonBlocking
)
647 return Sink()->IsNonBlocking(aNonBlocking
);
648 return NS_ERROR_NOT_INITIALIZED
;
651 NS_IMETHODIMP_(char*)
652 nsBufferedOutputStream::GetBuffer(PRUint32 aLength
, PRUint32 aAlignMask
)
654 NS_ASSERTION(mGetBufferCount
== 0, "nested GetBuffer!");
655 if (mGetBufferCount
!= 0)
661 char* buf
= mBuffer
+ mCursor
;
662 PRUint32 rem
= mBufferSize
- mCursor
;
664 if (NS_FAILED(Flush()))
666 buf
= mBuffer
+ mCursor
;
667 rem
= mBufferSize
- mCursor
;
670 PRUint32 mod
= (NS_PTR_TO_INT32(buf
) & aAlignMask
);
672 PRUint32 pad
= aAlignMask
+ 1 - mod
;
689 nsBufferedOutputStream::PutBuffer(char* aBuffer
, PRUint32 aLength
)
691 NS_ASSERTION(mGetBufferCount
== 1, "stray PutBuffer!");
692 if (--mGetBufferCount
!= 0)
695 NS_ASSERTION(mCursor
+ aLength
<= mBufferSize
, "PutBuffer botch");
697 if (mFillPoint
< mCursor
)
698 mFillPoint
= mCursor
;
702 nsBufferedOutputStream::DisableBuffering()
704 NS_ASSERTION(!mBufferDisabled
, "redundant call to DisableBuffering!");
705 NS_ASSERTION(mGetBufferCount
== 0,
706 "DisableBuffer call between GetBuffer and PutBuffer!");
707 if (mGetBufferCount
!= 0)
708 return NS_ERROR_UNEXPECTED
;
710 // Empty the buffer so nsBufferedStream::Tell works.
711 nsresult rv
= Flush();
715 mBufferDisabled
= PR_TRUE
;
720 nsBufferedOutputStream::EnableBuffering()
722 NS_ASSERTION(mBufferDisabled
, "gratuitous call to EnableBuffering!");
723 mBufferDisabled
= PR_FALSE
;
728 nsBufferedOutputStream::GetUnbufferedStream(nsISupports
* *aStream
)
730 // Empty the buffer so subsequent i/o trumps any buffered data.
732 nsresult rv
= Flush();
738 NS_IF_ADDREF(*aStream
);
742 ////////////////////////////////////////////////////////////////////////////////