xpcom/io/nsStorageStream.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

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

mercurial