|
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 /* 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/. */ |
|
6 |
|
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. |
|
13 */ |
|
14 |
|
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 "nsISeekableStream.h" |
|
22 #include "prlog.h" |
|
23 #include "mozilla/Attributes.h" |
|
24 #include "mozilla/Likely.h" |
|
25 #include "mozilla/MathAlgorithms.h" |
|
26 |
|
27 #if defined(PR_LOGGING) |
|
28 // |
|
29 // Log module for StorageStream logging... |
|
30 // |
|
31 // To enable logging (see prlog.h for full details): |
|
32 // |
|
33 // set NSPR_LOG_MODULES=StorageStreamLog:5 |
|
34 // set NSPR_LOG_FILE=nspr.log |
|
35 // |
|
36 // this enables PR_LOG_DEBUG level information and places all output in |
|
37 // the file nspr.log |
|
38 // |
|
39 static PRLogModuleInfo* |
|
40 GetStorageStreamLog() |
|
41 { |
|
42 static PRLogModuleInfo *sLog; |
|
43 if (!sLog) |
|
44 sLog = PR_NewLogModule("nsStorageStream"); |
|
45 return sLog; |
|
46 } |
|
47 #endif |
|
48 #ifdef LOG |
|
49 #undef LOG |
|
50 #endif |
|
51 #define LOG(args) PR_LOG(GetStorageStreamLog(), PR_LOG_DEBUG, args) |
|
52 |
|
53 nsStorageStream::nsStorageStream() |
|
54 : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false), |
|
55 mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0) |
|
56 { |
|
57 LOG(("Creating nsStorageStream [%p].\n", this)); |
|
58 } |
|
59 |
|
60 nsStorageStream::~nsStorageStream() |
|
61 { |
|
62 delete mSegmentedBuffer; |
|
63 } |
|
64 |
|
65 NS_IMPL_ISUPPORTS(nsStorageStream, |
|
66 nsIStorageStream, |
|
67 nsIOutputStream) |
|
68 |
|
69 NS_IMETHODIMP |
|
70 nsStorageStream::Init(uint32_t segmentSize, uint32_t maxSize) |
|
71 { |
|
72 mSegmentedBuffer = new nsSegmentedBuffer(); |
|
73 if (!mSegmentedBuffer) |
|
74 return NS_ERROR_OUT_OF_MEMORY; |
|
75 |
|
76 mSegmentSize = segmentSize; |
|
77 mSegmentSizeLog2 = mozilla::FloorLog2(segmentSize); |
|
78 |
|
79 // Segment size must be a power of two |
|
80 if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) |
|
81 return NS_ERROR_INVALID_ARG; |
|
82 |
|
83 return mSegmentedBuffer->Init(segmentSize, maxSize); |
|
84 } |
|
85 |
|
86 NS_IMETHODIMP |
|
87 nsStorageStream::GetOutputStream(int32_t aStartingOffset, |
|
88 nsIOutputStream * *aOutputStream) |
|
89 { |
|
90 if (NS_WARN_IF(!aOutputStream)) |
|
91 return NS_ERROR_INVALID_ARG; |
|
92 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
93 return NS_ERROR_NOT_INITIALIZED; |
|
94 |
|
95 if (mWriteInProgress) |
|
96 return NS_ERROR_NOT_AVAILABLE; |
|
97 |
|
98 nsresult rv = Seek(aStartingOffset); |
|
99 if (NS_FAILED(rv)) return rv; |
|
100 |
|
101 // Enlarge the last segment in the buffer so that it is the same size as |
|
102 // all the other segments in the buffer. (It may have been realloc'ed |
|
103 // smaller in the Close() method.) |
|
104 if (mLastSegmentNum >= 0) |
|
105 if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { |
|
106 // Need to re-Seek, since realloc changed segment base pointer |
|
107 rv = Seek(aStartingOffset); |
|
108 if (NS_FAILED(rv)) return rv; |
|
109 } |
|
110 |
|
111 NS_ADDREF(this); |
|
112 *aOutputStream = static_cast<nsIOutputStream*>(this); |
|
113 mWriteInProgress = true; |
|
114 return NS_OK; |
|
115 } |
|
116 |
|
117 NS_IMETHODIMP |
|
118 nsStorageStream::Close() |
|
119 { |
|
120 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
121 return NS_ERROR_NOT_INITIALIZED; |
|
122 |
|
123 mWriteInProgress = false; |
|
124 |
|
125 int32_t segmentOffset = SegOffset(mLogicalLength); |
|
126 |
|
127 // Shrink the final segment in the segmented buffer to the minimum size |
|
128 // needed to contain the data, so as to conserve memory. |
|
129 if (segmentOffset) |
|
130 mSegmentedBuffer->ReallocLastSegment(segmentOffset); |
|
131 |
|
132 mWriteCursor = 0; |
|
133 mSegmentEnd = 0; |
|
134 |
|
135 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n", |
|
136 this, mWriteCursor, mSegmentEnd)); |
|
137 |
|
138 return NS_OK; |
|
139 } |
|
140 |
|
141 NS_IMETHODIMP |
|
142 nsStorageStream::Flush() |
|
143 { |
|
144 return NS_OK; |
|
145 } |
|
146 |
|
147 NS_IMETHODIMP |
|
148 nsStorageStream::Write(const char *aBuffer, uint32_t aCount, uint32_t *aNumWritten) |
|
149 { |
|
150 if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) |
|
151 return NS_ERROR_INVALID_ARG; |
|
152 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
153 return NS_ERROR_NOT_INITIALIZED; |
|
154 |
|
155 const char* readCursor; |
|
156 uint32_t count, availableInSegment, remaining; |
|
157 nsresult rv = NS_OK; |
|
158 |
|
159 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n", |
|
160 this, mWriteCursor, mSegmentEnd, aCount)); |
|
161 |
|
162 remaining = aCount; |
|
163 readCursor = aBuffer; |
|
164 // If no segments have been created yet, create one even if we don't have |
|
165 // to write any data; this enables creating an input stream which reads from |
|
166 // the very end of the data for any amount of data in the stream (i.e. |
|
167 // this stream contains N bytes of data and newInputStream(N) is called), |
|
168 // even for N=0 (with the caveat that we require .write("", 0) be called to |
|
169 // initialize internal buffers). |
|
170 bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; |
|
171 while (remaining || MOZ_UNLIKELY(firstTime)) { |
|
172 firstTime = false; |
|
173 availableInSegment = mSegmentEnd - mWriteCursor; |
|
174 if (!availableInSegment) { |
|
175 mWriteCursor = mSegmentedBuffer->AppendNewSegment(); |
|
176 if (!mWriteCursor) { |
|
177 mSegmentEnd = 0; |
|
178 rv = NS_ERROR_OUT_OF_MEMORY; |
|
179 goto out; |
|
180 } |
|
181 mLastSegmentNum++; |
|
182 mSegmentEnd = mWriteCursor + mSegmentSize; |
|
183 availableInSegment = mSegmentEnd - mWriteCursor; |
|
184 LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n", |
|
185 this, mWriteCursor, mSegmentEnd)); |
|
186 } |
|
187 |
|
188 count = XPCOM_MIN(availableInSegment, remaining); |
|
189 memcpy(mWriteCursor, readCursor, count); |
|
190 remaining -= count; |
|
191 readCursor += count; |
|
192 mWriteCursor += count; |
|
193 LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n", |
|
194 this, mWriteCursor, mSegmentEnd, count)); |
|
195 }; |
|
196 |
|
197 out: |
|
198 *aNumWritten = aCount - remaining; |
|
199 mLogicalLength += *aNumWritten; |
|
200 |
|
201 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n", |
|
202 this, mWriteCursor, mSegmentEnd, *aNumWritten)); |
|
203 return rv; |
|
204 } |
|
205 |
|
206 NS_IMETHODIMP |
|
207 nsStorageStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) |
|
208 { |
|
209 return NS_ERROR_NOT_IMPLEMENTED; |
|
210 } |
|
211 |
|
212 NS_IMETHODIMP |
|
213 nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) |
|
214 { |
|
215 return NS_ERROR_NOT_IMPLEMENTED; |
|
216 } |
|
217 |
|
218 NS_IMETHODIMP |
|
219 nsStorageStream::IsNonBlocking(bool *aNonBlocking) |
|
220 { |
|
221 *aNonBlocking = false; |
|
222 return NS_OK; |
|
223 } |
|
224 |
|
225 NS_IMETHODIMP |
|
226 nsStorageStream::GetLength(uint32_t *aLength) |
|
227 { |
|
228 *aLength = mLogicalLength; |
|
229 return NS_OK; |
|
230 } |
|
231 |
|
232 // Truncate the buffer by deleting the end segments |
|
233 NS_IMETHODIMP |
|
234 nsStorageStream::SetLength(uint32_t aLength) |
|
235 { |
|
236 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
237 return NS_ERROR_NOT_INITIALIZED; |
|
238 |
|
239 if (mWriteInProgress) |
|
240 return NS_ERROR_NOT_AVAILABLE; |
|
241 |
|
242 if (aLength > mLogicalLength) |
|
243 return NS_ERROR_INVALID_ARG; |
|
244 |
|
245 int32_t newLastSegmentNum = SegNum(aLength); |
|
246 int32_t segmentOffset = SegOffset(aLength); |
|
247 if (segmentOffset == 0) |
|
248 newLastSegmentNum--; |
|
249 |
|
250 while (newLastSegmentNum < mLastSegmentNum) { |
|
251 mSegmentedBuffer->DeleteLastSegment(); |
|
252 mLastSegmentNum--; |
|
253 } |
|
254 |
|
255 mLogicalLength = aLength; |
|
256 return NS_OK; |
|
257 } |
|
258 |
|
259 NS_IMETHODIMP |
|
260 nsStorageStream::GetWriteInProgress(bool *aWriteInProgress) |
|
261 { |
|
262 *aWriteInProgress = mWriteInProgress; |
|
263 return NS_OK; |
|
264 } |
|
265 |
|
266 NS_METHOD |
|
267 nsStorageStream::Seek(int32_t aPosition) |
|
268 { |
|
269 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
270 return NS_ERROR_NOT_INITIALIZED; |
|
271 |
|
272 // An argument of -1 means "seek to end of stream" |
|
273 if (aPosition == -1) |
|
274 aPosition = mLogicalLength; |
|
275 |
|
276 // Seeking beyond the buffer end is illegal |
|
277 if ((uint32_t)aPosition > mLogicalLength) |
|
278 return NS_ERROR_INVALID_ARG; |
|
279 |
|
280 // Seeking backwards in the write stream results in truncation |
|
281 SetLength(aPosition); |
|
282 |
|
283 // Special handling for seek to start-of-buffer |
|
284 if (aPosition == 0) { |
|
285 mWriteCursor = 0; |
|
286 mSegmentEnd = 0; |
|
287 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", |
|
288 this, mWriteCursor, mSegmentEnd)); |
|
289 return NS_OK; |
|
290 } |
|
291 |
|
292 // Segment may have changed, so reset pointers |
|
293 mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); |
|
294 NS_ASSERTION(mWriteCursor, "null mWriteCursor"); |
|
295 mSegmentEnd = mWriteCursor + mSegmentSize; |
|
296 |
|
297 // Adjust write cursor for current segment offset. This test is necessary |
|
298 // because SegNum may reference the next-to-be-allocated segment, in which |
|
299 // case we need to be pointing at the end of the last segment. |
|
300 int32_t segmentOffset = SegOffset(aPosition); |
|
301 if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) |
|
302 mWriteCursor = mSegmentEnd; |
|
303 else |
|
304 mWriteCursor += segmentOffset; |
|
305 |
|
306 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", |
|
307 this, mWriteCursor, mSegmentEnd)); |
|
308 return NS_OK; |
|
309 } |
|
310 |
|
311 //////////////////////////////////////////////////////////////////////////////// |
|
312 |
|
313 // There can be many nsStorageInputStreams for a single nsStorageStream |
|
314 class nsStorageInputStream MOZ_FINAL : public nsIInputStream |
|
315 , public nsISeekableStream |
|
316 { |
|
317 public: |
|
318 nsStorageInputStream(nsStorageStream *aStorageStream, uint32_t aSegmentSize) |
|
319 : mStorageStream(aStorageStream), mReadCursor(0), |
|
320 mSegmentEnd(0), mSegmentNum(0), |
|
321 mSegmentSize(aSegmentSize), mLogicalCursor(0), |
|
322 mStatus(NS_OK) |
|
323 { |
|
324 NS_ADDREF(mStorageStream); |
|
325 } |
|
326 |
|
327 NS_DECL_THREADSAFE_ISUPPORTS |
|
328 NS_DECL_NSIINPUTSTREAM |
|
329 NS_DECL_NSISEEKABLESTREAM |
|
330 |
|
331 private: |
|
332 ~nsStorageInputStream() |
|
333 { |
|
334 NS_IF_RELEASE(mStorageStream); |
|
335 } |
|
336 |
|
337 protected: |
|
338 NS_METHOD Seek(uint32_t aPosition); |
|
339 |
|
340 friend class nsStorageStream; |
|
341 |
|
342 private: |
|
343 nsStorageStream* mStorageStream; |
|
344 uint32_t mReadCursor; // Next memory location to read byte, or 0 |
|
345 uint32_t mSegmentEnd; // One byte past end of current buffer segment |
|
346 uint32_t mSegmentNum; // Segment number containing read cursor |
|
347 uint32_t mSegmentSize; // All segments, except the last, are of this size |
|
348 uint32_t mLogicalCursor; // Logical offset into stream |
|
349 nsresult mStatus; |
|
350 |
|
351 uint32_t SegNum(uint32_t aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;} |
|
352 uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);} |
|
353 }; |
|
354 |
|
355 NS_IMPL_ISUPPORTS(nsStorageInputStream, |
|
356 nsIInputStream, |
|
357 nsISeekableStream) |
|
358 |
|
359 NS_IMETHODIMP |
|
360 nsStorageStream::NewInputStream(int32_t aStartingOffset, nsIInputStream* *aInputStream) |
|
361 { |
|
362 if (NS_WARN_IF(!mSegmentedBuffer)) |
|
363 return NS_ERROR_NOT_INITIALIZED; |
|
364 |
|
365 nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize); |
|
366 if (!inputStream) |
|
367 return NS_ERROR_OUT_OF_MEMORY; |
|
368 |
|
369 NS_ADDREF(inputStream); |
|
370 |
|
371 nsresult rv = inputStream->Seek(aStartingOffset); |
|
372 if (NS_FAILED(rv)) { |
|
373 NS_RELEASE(inputStream); |
|
374 return rv; |
|
375 } |
|
376 |
|
377 *aInputStream = inputStream; |
|
378 return NS_OK; |
|
379 } |
|
380 |
|
381 NS_IMETHODIMP |
|
382 nsStorageInputStream::Close() |
|
383 { |
|
384 mStatus = NS_BASE_STREAM_CLOSED; |
|
385 return NS_OK; |
|
386 } |
|
387 |
|
388 NS_IMETHODIMP |
|
389 nsStorageInputStream::Available(uint64_t *aAvailable) |
|
390 { |
|
391 if (NS_FAILED(mStatus)) |
|
392 return mStatus; |
|
393 |
|
394 *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; |
|
395 return NS_OK; |
|
396 } |
|
397 |
|
398 NS_IMETHODIMP |
|
399 nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead) |
|
400 { |
|
401 return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); |
|
402 } |
|
403 |
|
404 NS_IMETHODIMP |
|
405 nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t aCount, uint32_t *aNumRead) |
|
406 { |
|
407 *aNumRead = 0; |
|
408 if (mStatus == NS_BASE_STREAM_CLOSED) |
|
409 return NS_OK; |
|
410 if (NS_FAILED(mStatus)) |
|
411 return mStatus; |
|
412 |
|
413 uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; |
|
414 nsresult rv; |
|
415 |
|
416 remainingCapacity = aCount; |
|
417 while (remainingCapacity) { |
|
418 availableInSegment = mSegmentEnd - mReadCursor; |
|
419 if (!availableInSegment) { |
|
420 uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; |
|
421 if (!available) |
|
422 goto out; |
|
423 |
|
424 mSegmentNum++; |
|
425 mReadCursor = 0; |
|
426 mSegmentEnd = XPCOM_MIN(mSegmentSize, available); |
|
427 availableInSegment = mSegmentEnd; |
|
428 } |
|
429 const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); |
|
430 |
|
431 count = XPCOM_MIN(availableInSegment, remainingCapacity); |
|
432 rv = writer(this, closure, cur + mReadCursor, aCount - remainingCapacity, |
|
433 count, &bytesConsumed); |
|
434 if (NS_FAILED(rv) || (bytesConsumed == 0)) |
|
435 break; |
|
436 remainingCapacity -= bytesConsumed; |
|
437 mReadCursor += bytesConsumed; |
|
438 mLogicalCursor += bytesConsumed; |
|
439 }; |
|
440 |
|
441 out: |
|
442 *aNumRead = aCount - remainingCapacity; |
|
443 |
|
444 bool isWriteInProgress = false; |
|
445 if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) |
|
446 isWriteInProgress = false; |
|
447 |
|
448 if (*aNumRead == 0 && isWriteInProgress) |
|
449 return NS_BASE_STREAM_WOULD_BLOCK; |
|
450 |
|
451 return NS_OK; |
|
452 } |
|
453 |
|
454 NS_IMETHODIMP |
|
455 nsStorageInputStream::IsNonBlocking(bool *aNonBlocking) |
|
456 { |
|
457 // TODO: This class should implement nsIAsyncInputStream so that callers |
|
458 // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. |
|
459 |
|
460 *aNonBlocking = true; |
|
461 return NS_OK; |
|
462 } |
|
463 |
|
464 NS_IMETHODIMP |
|
465 nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) |
|
466 { |
|
467 if (NS_FAILED(mStatus)) |
|
468 return mStatus; |
|
469 |
|
470 int64_t pos = aOffset; |
|
471 |
|
472 switch (aWhence) { |
|
473 case NS_SEEK_SET: |
|
474 break; |
|
475 case NS_SEEK_CUR: |
|
476 pos += mLogicalCursor; |
|
477 break; |
|
478 case NS_SEEK_END: |
|
479 pos += mStorageStream->mLogicalLength; |
|
480 break; |
|
481 default: |
|
482 NS_NOTREACHED("unexpected whence value"); |
|
483 return NS_ERROR_UNEXPECTED; |
|
484 } |
|
485 if (pos == int64_t(mLogicalCursor)) |
|
486 return NS_OK; |
|
487 |
|
488 return Seek(pos); |
|
489 } |
|
490 |
|
491 NS_IMETHODIMP |
|
492 nsStorageInputStream::Tell(int64_t *aResult) |
|
493 { |
|
494 if (NS_FAILED(mStatus)) |
|
495 return mStatus; |
|
496 |
|
497 *aResult = mLogicalCursor; |
|
498 return NS_OK; |
|
499 } |
|
500 |
|
501 NS_IMETHODIMP |
|
502 nsStorageInputStream::SetEOF() |
|
503 { |
|
504 NS_NOTREACHED("nsStorageInputStream::SetEOF"); |
|
505 return NS_ERROR_NOT_IMPLEMENTED; |
|
506 } |
|
507 |
|
508 NS_METHOD |
|
509 nsStorageInputStream::Seek(uint32_t aPosition) |
|
510 { |
|
511 uint32_t length = mStorageStream->mLogicalLength; |
|
512 if (aPosition > length) |
|
513 return NS_ERROR_INVALID_ARG; |
|
514 |
|
515 if (length == 0) |
|
516 return NS_OK; |
|
517 |
|
518 mSegmentNum = SegNum(aPosition); |
|
519 mReadCursor = SegOffset(aPosition); |
|
520 uint32_t available = length - aPosition; |
|
521 mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); |
|
522 mLogicalCursor = aPosition; |
|
523 return NS_OK; |
|
524 } |
|
525 |
|
526 nsresult |
|
527 NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result) |
|
528 { |
|
529 nsStorageStream* storageStream = new nsStorageStream(); |
|
530 if (!storageStream) return NS_ERROR_OUT_OF_MEMORY; |
|
531 |
|
532 NS_ADDREF(storageStream); |
|
533 nsresult rv = storageStream->Init(segmentSize, maxSize); |
|
534 if (NS_FAILED(rv)) { |
|
535 NS_RELEASE(storageStream); |
|
536 return rv; |
|
537 } |
|
538 *result = storageStream; |
|
539 return NS_OK; |
|
540 } |
|
541 |
|
542 // Undefine LOG, so that other .cpp files (or their includes) won't complain |
|
543 // about it already being defined, when we build in unified mode. |
|
544 #undef LOG |