Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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/. */
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 */
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"
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)
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 }
60 nsStorageStream::~nsStorageStream()
61 {
62 delete mSegmentedBuffer;
63 }
65 NS_IMPL_ISUPPORTS(nsStorageStream,
66 nsIStorageStream,
67 nsIOutputStream)
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;
76 mSegmentSize = segmentSize;
77 mSegmentSizeLog2 = mozilla::FloorLog2(segmentSize);
79 // Segment size must be a power of two
80 if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2))
81 return NS_ERROR_INVALID_ARG;
83 return mSegmentedBuffer->Init(segmentSize, maxSize);
84 }
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;
95 if (mWriteInProgress)
96 return NS_ERROR_NOT_AVAILABLE;
98 nsresult rv = Seek(aStartingOffset);
99 if (NS_FAILED(rv)) return rv;
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 }
111 NS_ADDREF(this);
112 *aOutputStream = static_cast<nsIOutputStream*>(this);
113 mWriteInProgress = true;
114 return NS_OK;
115 }
117 NS_IMETHODIMP
118 nsStorageStream::Close()
119 {
120 if (NS_WARN_IF(!mSegmentedBuffer))
121 return NS_ERROR_NOT_INITIALIZED;
123 mWriteInProgress = false;
125 int32_t segmentOffset = SegOffset(mLogicalLength);
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);
132 mWriteCursor = 0;
133 mSegmentEnd = 0;
135 LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
136 this, mWriteCursor, mSegmentEnd));
138 return NS_OK;
139 }
141 NS_IMETHODIMP
142 nsStorageStream::Flush()
143 {
144 return NS_OK;
145 }
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;
155 const char* readCursor;
156 uint32_t count, availableInSegment, remaining;
157 nsresult rv = NS_OK;
159 LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
160 this, mWriteCursor, mSegmentEnd, aCount));
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 }
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 };
197 out:
198 *aNumWritten = aCount - remaining;
199 mLogicalLength += *aNumWritten;
201 LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
202 this, mWriteCursor, mSegmentEnd, *aNumWritten));
203 return rv;
204 }
206 NS_IMETHODIMP
207 nsStorageStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
208 {
209 return NS_ERROR_NOT_IMPLEMENTED;
210 }
212 NS_IMETHODIMP
213 nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
214 {
215 return NS_ERROR_NOT_IMPLEMENTED;
216 }
218 NS_IMETHODIMP
219 nsStorageStream::IsNonBlocking(bool *aNonBlocking)
220 {
221 *aNonBlocking = false;
222 return NS_OK;
223 }
225 NS_IMETHODIMP
226 nsStorageStream::GetLength(uint32_t *aLength)
227 {
228 *aLength = mLogicalLength;
229 return NS_OK;
230 }
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;
239 if (mWriteInProgress)
240 return NS_ERROR_NOT_AVAILABLE;
242 if (aLength > mLogicalLength)
243 return NS_ERROR_INVALID_ARG;
245 int32_t newLastSegmentNum = SegNum(aLength);
246 int32_t segmentOffset = SegOffset(aLength);
247 if (segmentOffset == 0)
248 newLastSegmentNum--;
250 while (newLastSegmentNum < mLastSegmentNum) {
251 mSegmentedBuffer->DeleteLastSegment();
252 mLastSegmentNum--;
253 }
255 mLogicalLength = aLength;
256 return NS_OK;
257 }
259 NS_IMETHODIMP
260 nsStorageStream::GetWriteInProgress(bool *aWriteInProgress)
261 {
262 *aWriteInProgress = mWriteInProgress;
263 return NS_OK;
264 }
266 NS_METHOD
267 nsStorageStream::Seek(int32_t aPosition)
268 {
269 if (NS_WARN_IF(!mSegmentedBuffer))
270 return NS_ERROR_NOT_INITIALIZED;
272 // An argument of -1 means "seek to end of stream"
273 if (aPosition == -1)
274 aPosition = mLogicalLength;
276 // Seeking beyond the buffer end is illegal
277 if ((uint32_t)aPosition > mLogicalLength)
278 return NS_ERROR_INVALID_ARG;
280 // Seeking backwards in the write stream results in truncation
281 SetLength(aPosition);
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 }
292 // Segment may have changed, so reset pointers
293 mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum);
294 NS_ASSERTION(mWriteCursor, "null mWriteCursor");
295 mSegmentEnd = mWriteCursor + mSegmentSize;
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;
306 LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
307 this, mWriteCursor, mSegmentEnd));
308 return NS_OK;
309 }
311 ////////////////////////////////////////////////////////////////////////////////
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 }
327 NS_DECL_THREADSAFE_ISUPPORTS
328 NS_DECL_NSIINPUTSTREAM
329 NS_DECL_NSISEEKABLESTREAM
331 private:
332 ~nsStorageInputStream()
333 {
334 NS_IF_RELEASE(mStorageStream);
335 }
337 protected:
338 NS_METHOD Seek(uint32_t aPosition);
340 friend class nsStorageStream;
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;
351 uint32_t SegNum(uint32_t aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;}
352 uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);}
353 };
355 NS_IMPL_ISUPPORTS(nsStorageInputStream,
356 nsIInputStream,
357 nsISeekableStream)
359 NS_IMETHODIMP
360 nsStorageStream::NewInputStream(int32_t aStartingOffset, nsIInputStream* *aInputStream)
361 {
362 if (NS_WARN_IF(!mSegmentedBuffer))
363 return NS_ERROR_NOT_INITIALIZED;
365 nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize);
366 if (!inputStream)
367 return NS_ERROR_OUT_OF_MEMORY;
369 NS_ADDREF(inputStream);
371 nsresult rv = inputStream->Seek(aStartingOffset);
372 if (NS_FAILED(rv)) {
373 NS_RELEASE(inputStream);
374 return rv;
375 }
377 *aInputStream = inputStream;
378 return NS_OK;
379 }
381 NS_IMETHODIMP
382 nsStorageInputStream::Close()
383 {
384 mStatus = NS_BASE_STREAM_CLOSED;
385 return NS_OK;
386 }
388 NS_IMETHODIMP
389 nsStorageInputStream::Available(uint64_t *aAvailable)
390 {
391 if (NS_FAILED(mStatus))
392 return mStatus;
394 *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor;
395 return NS_OK;
396 }
398 NS_IMETHODIMP
399 nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead)
400 {
401 return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead);
402 }
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;
413 uint32_t count, availableInSegment, remainingCapacity, bytesConsumed;
414 nsresult rv;
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;
424 mSegmentNum++;
425 mReadCursor = 0;
426 mSegmentEnd = XPCOM_MIN(mSegmentSize, available);
427 availableInSegment = mSegmentEnd;
428 }
429 const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum);
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 };
441 out:
442 *aNumRead = aCount - remainingCapacity;
444 bool isWriteInProgress = false;
445 if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress)))
446 isWriteInProgress = false;
448 if (*aNumRead == 0 && isWriteInProgress)
449 return NS_BASE_STREAM_WOULD_BLOCK;
451 return NS_OK;
452 }
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.
460 *aNonBlocking = true;
461 return NS_OK;
462 }
464 NS_IMETHODIMP
465 nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset)
466 {
467 if (NS_FAILED(mStatus))
468 return mStatus;
470 int64_t pos = aOffset;
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;
488 return Seek(pos);
489 }
491 NS_IMETHODIMP
492 nsStorageInputStream::Tell(int64_t *aResult)
493 {
494 if (NS_FAILED(mStatus))
495 return mStatus;
497 *aResult = mLogicalCursor;
498 return NS_OK;
499 }
501 NS_IMETHODIMP
502 nsStorageInputStream::SetEOF()
503 {
504 NS_NOTREACHED("nsStorageInputStream::SetEOF");
505 return NS_ERROR_NOT_IMPLEMENTED;
506 }
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;
515 if (length == 0)
516 return NS_OK;
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 }
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;
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 }
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