1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/io/nsStorageStream.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,544 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:set ts=4 sts=4 sw=4 cin et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * The storage stream provides an internal buffer that can be filled by a 1.12 + * client using a single output stream. One or more independent input streams 1.13 + * can be created to read the data out non-destructively. The implementation 1.14 + * uses a segmented buffer internally to avoid realloc'ing of large buffers, 1.15 + * with the attendant performance loss and heap fragmentation. 1.16 + */ 1.17 + 1.18 +#include "nsAlgorithm.h" 1.19 +#include "nsStorageStream.h" 1.20 +#include "nsSegmentedBuffer.h" 1.21 +#include "nsStreamUtils.h" 1.22 +#include "nsCOMPtr.h" 1.23 +#include "nsIInputStream.h" 1.24 +#include "nsISeekableStream.h" 1.25 +#include "prlog.h" 1.26 +#include "mozilla/Attributes.h" 1.27 +#include "mozilla/Likely.h" 1.28 +#include "mozilla/MathAlgorithms.h" 1.29 + 1.30 +#if defined(PR_LOGGING) 1.31 +// 1.32 +// Log module for StorageStream logging... 1.33 +// 1.34 +// To enable logging (see prlog.h for full details): 1.35 +// 1.36 +// set NSPR_LOG_MODULES=StorageStreamLog:5 1.37 +// set NSPR_LOG_FILE=nspr.log 1.38 +// 1.39 +// this enables PR_LOG_DEBUG level information and places all output in 1.40 +// the file nspr.log 1.41 +// 1.42 +static PRLogModuleInfo* 1.43 +GetStorageStreamLog() 1.44 +{ 1.45 + static PRLogModuleInfo *sLog; 1.46 + if (!sLog) 1.47 + sLog = PR_NewLogModule("nsStorageStream"); 1.48 + return sLog; 1.49 +} 1.50 +#endif 1.51 +#ifdef LOG 1.52 +#undef LOG 1.53 +#endif 1.54 +#define LOG(args) PR_LOG(GetStorageStreamLog(), PR_LOG_DEBUG, args) 1.55 + 1.56 +nsStorageStream::nsStorageStream() 1.57 + : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false), 1.58 + mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0) 1.59 +{ 1.60 + LOG(("Creating nsStorageStream [%p].\n", this)); 1.61 +} 1.62 + 1.63 +nsStorageStream::~nsStorageStream() 1.64 +{ 1.65 + delete mSegmentedBuffer; 1.66 +} 1.67 + 1.68 +NS_IMPL_ISUPPORTS(nsStorageStream, 1.69 + nsIStorageStream, 1.70 + nsIOutputStream) 1.71 + 1.72 +NS_IMETHODIMP 1.73 +nsStorageStream::Init(uint32_t segmentSize, uint32_t maxSize) 1.74 +{ 1.75 + mSegmentedBuffer = new nsSegmentedBuffer(); 1.76 + if (!mSegmentedBuffer) 1.77 + return NS_ERROR_OUT_OF_MEMORY; 1.78 + 1.79 + mSegmentSize = segmentSize; 1.80 + mSegmentSizeLog2 = mozilla::FloorLog2(segmentSize); 1.81 + 1.82 + // Segment size must be a power of two 1.83 + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) 1.84 + return NS_ERROR_INVALID_ARG; 1.85 + 1.86 + return mSegmentedBuffer->Init(segmentSize, maxSize); 1.87 +} 1.88 + 1.89 +NS_IMETHODIMP 1.90 +nsStorageStream::GetOutputStream(int32_t aStartingOffset, 1.91 + nsIOutputStream * *aOutputStream) 1.92 +{ 1.93 + if (NS_WARN_IF(!aOutputStream)) 1.94 + return NS_ERROR_INVALID_ARG; 1.95 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.96 + return NS_ERROR_NOT_INITIALIZED; 1.97 + 1.98 + if (mWriteInProgress) 1.99 + return NS_ERROR_NOT_AVAILABLE; 1.100 + 1.101 + nsresult rv = Seek(aStartingOffset); 1.102 + if (NS_FAILED(rv)) return rv; 1.103 + 1.104 + // Enlarge the last segment in the buffer so that it is the same size as 1.105 + // all the other segments in the buffer. (It may have been realloc'ed 1.106 + // smaller in the Close() method.) 1.107 + if (mLastSegmentNum >= 0) 1.108 + if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { 1.109 + // Need to re-Seek, since realloc changed segment base pointer 1.110 + rv = Seek(aStartingOffset); 1.111 + if (NS_FAILED(rv)) return rv; 1.112 + } 1.113 + 1.114 + NS_ADDREF(this); 1.115 + *aOutputStream = static_cast<nsIOutputStream*>(this); 1.116 + mWriteInProgress = true; 1.117 + return NS_OK; 1.118 +} 1.119 + 1.120 +NS_IMETHODIMP 1.121 +nsStorageStream::Close() 1.122 +{ 1.123 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.124 + return NS_ERROR_NOT_INITIALIZED; 1.125 + 1.126 + mWriteInProgress = false; 1.127 + 1.128 + int32_t segmentOffset = SegOffset(mLogicalLength); 1.129 + 1.130 + // Shrink the final segment in the segmented buffer to the minimum size 1.131 + // needed to contain the data, so as to conserve memory. 1.132 + if (segmentOffset) 1.133 + mSegmentedBuffer->ReallocLastSegment(segmentOffset); 1.134 + 1.135 + mWriteCursor = 0; 1.136 + mSegmentEnd = 0; 1.137 + 1.138 + LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n", 1.139 + this, mWriteCursor, mSegmentEnd)); 1.140 + 1.141 + return NS_OK; 1.142 +} 1.143 + 1.144 +NS_IMETHODIMP 1.145 +nsStorageStream::Flush() 1.146 +{ 1.147 + return NS_OK; 1.148 +} 1.149 + 1.150 +NS_IMETHODIMP 1.151 +nsStorageStream::Write(const char *aBuffer, uint32_t aCount, uint32_t *aNumWritten) 1.152 +{ 1.153 + if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) 1.154 + return NS_ERROR_INVALID_ARG; 1.155 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.156 + return NS_ERROR_NOT_INITIALIZED; 1.157 + 1.158 + const char* readCursor; 1.159 + uint32_t count, availableInSegment, remaining; 1.160 + nsresult rv = NS_OK; 1.161 + 1.162 + LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n", 1.163 + this, mWriteCursor, mSegmentEnd, aCount)); 1.164 + 1.165 + remaining = aCount; 1.166 + readCursor = aBuffer; 1.167 + // If no segments have been created yet, create one even if we don't have 1.168 + // to write any data; this enables creating an input stream which reads from 1.169 + // the very end of the data for any amount of data in the stream (i.e. 1.170 + // this stream contains N bytes of data and newInputStream(N) is called), 1.171 + // even for N=0 (with the caveat that we require .write("", 0) be called to 1.172 + // initialize internal buffers). 1.173 + bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; 1.174 + while (remaining || MOZ_UNLIKELY(firstTime)) { 1.175 + firstTime = false; 1.176 + availableInSegment = mSegmentEnd - mWriteCursor; 1.177 + if (!availableInSegment) { 1.178 + mWriteCursor = mSegmentedBuffer->AppendNewSegment(); 1.179 + if (!mWriteCursor) { 1.180 + mSegmentEnd = 0; 1.181 + rv = NS_ERROR_OUT_OF_MEMORY; 1.182 + goto out; 1.183 + } 1.184 + mLastSegmentNum++; 1.185 + mSegmentEnd = mWriteCursor + mSegmentSize; 1.186 + availableInSegment = mSegmentEnd - mWriteCursor; 1.187 + LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n", 1.188 + this, mWriteCursor, mSegmentEnd)); 1.189 + } 1.190 + 1.191 + count = XPCOM_MIN(availableInSegment, remaining); 1.192 + memcpy(mWriteCursor, readCursor, count); 1.193 + remaining -= count; 1.194 + readCursor += count; 1.195 + mWriteCursor += count; 1.196 + LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n", 1.197 + this, mWriteCursor, mSegmentEnd, count)); 1.198 + }; 1.199 + 1.200 + out: 1.201 + *aNumWritten = aCount - remaining; 1.202 + mLogicalLength += *aNumWritten; 1.203 + 1.204 + LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n", 1.205 + this, mWriteCursor, mSegmentEnd, *aNumWritten)); 1.206 + return rv; 1.207 +} 1.208 + 1.209 +NS_IMETHODIMP 1.210 +nsStorageStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) 1.211 +{ 1.212 + return NS_ERROR_NOT_IMPLEMENTED; 1.213 +} 1.214 + 1.215 +NS_IMETHODIMP 1.216 +nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) 1.217 +{ 1.218 + return NS_ERROR_NOT_IMPLEMENTED; 1.219 +} 1.220 + 1.221 +NS_IMETHODIMP 1.222 +nsStorageStream::IsNonBlocking(bool *aNonBlocking) 1.223 +{ 1.224 + *aNonBlocking = false; 1.225 + return NS_OK; 1.226 +} 1.227 + 1.228 +NS_IMETHODIMP 1.229 +nsStorageStream::GetLength(uint32_t *aLength) 1.230 +{ 1.231 + *aLength = mLogicalLength; 1.232 + return NS_OK; 1.233 +} 1.234 + 1.235 +// Truncate the buffer by deleting the end segments 1.236 +NS_IMETHODIMP 1.237 +nsStorageStream::SetLength(uint32_t aLength) 1.238 +{ 1.239 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.240 + return NS_ERROR_NOT_INITIALIZED; 1.241 + 1.242 + if (mWriteInProgress) 1.243 + return NS_ERROR_NOT_AVAILABLE; 1.244 + 1.245 + if (aLength > mLogicalLength) 1.246 + return NS_ERROR_INVALID_ARG; 1.247 + 1.248 + int32_t newLastSegmentNum = SegNum(aLength); 1.249 + int32_t segmentOffset = SegOffset(aLength); 1.250 + if (segmentOffset == 0) 1.251 + newLastSegmentNum--; 1.252 + 1.253 + while (newLastSegmentNum < mLastSegmentNum) { 1.254 + mSegmentedBuffer->DeleteLastSegment(); 1.255 + mLastSegmentNum--; 1.256 + } 1.257 + 1.258 + mLogicalLength = aLength; 1.259 + return NS_OK; 1.260 +} 1.261 + 1.262 +NS_IMETHODIMP 1.263 +nsStorageStream::GetWriteInProgress(bool *aWriteInProgress) 1.264 +{ 1.265 + *aWriteInProgress = mWriteInProgress; 1.266 + return NS_OK; 1.267 +} 1.268 + 1.269 +NS_METHOD 1.270 +nsStorageStream::Seek(int32_t aPosition) 1.271 +{ 1.272 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.273 + return NS_ERROR_NOT_INITIALIZED; 1.274 + 1.275 + // An argument of -1 means "seek to end of stream" 1.276 + if (aPosition == -1) 1.277 + aPosition = mLogicalLength; 1.278 + 1.279 + // Seeking beyond the buffer end is illegal 1.280 + if ((uint32_t)aPosition > mLogicalLength) 1.281 + return NS_ERROR_INVALID_ARG; 1.282 + 1.283 + // Seeking backwards in the write stream results in truncation 1.284 + SetLength(aPosition); 1.285 + 1.286 + // Special handling for seek to start-of-buffer 1.287 + if (aPosition == 0) { 1.288 + mWriteCursor = 0; 1.289 + mSegmentEnd = 0; 1.290 + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", 1.291 + this, mWriteCursor, mSegmentEnd)); 1.292 + return NS_OK; 1.293 + } 1.294 + 1.295 + // Segment may have changed, so reset pointers 1.296 + mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); 1.297 + NS_ASSERTION(mWriteCursor, "null mWriteCursor"); 1.298 + mSegmentEnd = mWriteCursor + mSegmentSize; 1.299 + 1.300 + // Adjust write cursor for current segment offset. This test is necessary 1.301 + // because SegNum may reference the next-to-be-allocated segment, in which 1.302 + // case we need to be pointing at the end of the last segment. 1.303 + int32_t segmentOffset = SegOffset(aPosition); 1.304 + if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) 1.305 + mWriteCursor = mSegmentEnd; 1.306 + else 1.307 + mWriteCursor += segmentOffset; 1.308 + 1.309 + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", 1.310 + this, mWriteCursor, mSegmentEnd)); 1.311 + return NS_OK; 1.312 +} 1.313 + 1.314 +//////////////////////////////////////////////////////////////////////////////// 1.315 + 1.316 +// There can be many nsStorageInputStreams for a single nsStorageStream 1.317 +class nsStorageInputStream MOZ_FINAL : public nsIInputStream 1.318 + , public nsISeekableStream 1.319 +{ 1.320 +public: 1.321 + nsStorageInputStream(nsStorageStream *aStorageStream, uint32_t aSegmentSize) 1.322 + : mStorageStream(aStorageStream), mReadCursor(0), 1.323 + mSegmentEnd(0), mSegmentNum(0), 1.324 + mSegmentSize(aSegmentSize), mLogicalCursor(0), 1.325 + mStatus(NS_OK) 1.326 + { 1.327 + NS_ADDREF(mStorageStream); 1.328 + } 1.329 + 1.330 + NS_DECL_THREADSAFE_ISUPPORTS 1.331 + NS_DECL_NSIINPUTSTREAM 1.332 + NS_DECL_NSISEEKABLESTREAM 1.333 + 1.334 +private: 1.335 + ~nsStorageInputStream() 1.336 + { 1.337 + NS_IF_RELEASE(mStorageStream); 1.338 + } 1.339 + 1.340 +protected: 1.341 + NS_METHOD Seek(uint32_t aPosition); 1.342 + 1.343 + friend class nsStorageStream; 1.344 + 1.345 +private: 1.346 + nsStorageStream* mStorageStream; 1.347 + uint32_t mReadCursor; // Next memory location to read byte, or 0 1.348 + uint32_t mSegmentEnd; // One byte past end of current buffer segment 1.349 + uint32_t mSegmentNum; // Segment number containing read cursor 1.350 + uint32_t mSegmentSize; // All segments, except the last, are of this size 1.351 + uint32_t mLogicalCursor; // Logical offset into stream 1.352 + nsresult mStatus; 1.353 + 1.354 + uint32_t SegNum(uint32_t aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;} 1.355 + uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);} 1.356 +}; 1.357 + 1.358 +NS_IMPL_ISUPPORTS(nsStorageInputStream, 1.359 + nsIInputStream, 1.360 + nsISeekableStream) 1.361 + 1.362 +NS_IMETHODIMP 1.363 +nsStorageStream::NewInputStream(int32_t aStartingOffset, nsIInputStream* *aInputStream) 1.364 +{ 1.365 + if (NS_WARN_IF(!mSegmentedBuffer)) 1.366 + return NS_ERROR_NOT_INITIALIZED; 1.367 + 1.368 + nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize); 1.369 + if (!inputStream) 1.370 + return NS_ERROR_OUT_OF_MEMORY; 1.371 + 1.372 + NS_ADDREF(inputStream); 1.373 + 1.374 + nsresult rv = inputStream->Seek(aStartingOffset); 1.375 + if (NS_FAILED(rv)) { 1.376 + NS_RELEASE(inputStream); 1.377 + return rv; 1.378 + } 1.379 + 1.380 + *aInputStream = inputStream; 1.381 + return NS_OK; 1.382 +} 1.383 + 1.384 +NS_IMETHODIMP 1.385 +nsStorageInputStream::Close() 1.386 +{ 1.387 + mStatus = NS_BASE_STREAM_CLOSED; 1.388 + return NS_OK; 1.389 +} 1.390 + 1.391 +NS_IMETHODIMP 1.392 +nsStorageInputStream::Available(uint64_t *aAvailable) 1.393 +{ 1.394 + if (NS_FAILED(mStatus)) 1.395 + return mStatus; 1.396 + 1.397 + *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; 1.398 + return NS_OK; 1.399 +} 1.400 + 1.401 +NS_IMETHODIMP 1.402 +nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead) 1.403 +{ 1.404 + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); 1.405 +} 1.406 + 1.407 +NS_IMETHODIMP 1.408 +nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t aCount, uint32_t *aNumRead) 1.409 +{ 1.410 + *aNumRead = 0; 1.411 + if (mStatus == NS_BASE_STREAM_CLOSED) 1.412 + return NS_OK; 1.413 + if (NS_FAILED(mStatus)) 1.414 + return mStatus; 1.415 + 1.416 + uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; 1.417 + nsresult rv; 1.418 + 1.419 + remainingCapacity = aCount; 1.420 + while (remainingCapacity) { 1.421 + availableInSegment = mSegmentEnd - mReadCursor; 1.422 + if (!availableInSegment) { 1.423 + uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; 1.424 + if (!available) 1.425 + goto out; 1.426 + 1.427 + mSegmentNum++; 1.428 + mReadCursor = 0; 1.429 + mSegmentEnd = XPCOM_MIN(mSegmentSize, available); 1.430 + availableInSegment = mSegmentEnd; 1.431 + } 1.432 + const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); 1.433 + 1.434 + count = XPCOM_MIN(availableInSegment, remainingCapacity); 1.435 + rv = writer(this, closure, cur + mReadCursor, aCount - remainingCapacity, 1.436 + count, &bytesConsumed); 1.437 + if (NS_FAILED(rv) || (bytesConsumed == 0)) 1.438 + break; 1.439 + remainingCapacity -= bytesConsumed; 1.440 + mReadCursor += bytesConsumed; 1.441 + mLogicalCursor += bytesConsumed; 1.442 + }; 1.443 + 1.444 + out: 1.445 + *aNumRead = aCount - remainingCapacity; 1.446 + 1.447 + bool isWriteInProgress = false; 1.448 + if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) 1.449 + isWriteInProgress = false; 1.450 + 1.451 + if (*aNumRead == 0 && isWriteInProgress) 1.452 + return NS_BASE_STREAM_WOULD_BLOCK; 1.453 + 1.454 + return NS_OK; 1.455 +} 1.456 + 1.457 +NS_IMETHODIMP 1.458 +nsStorageInputStream::IsNonBlocking(bool *aNonBlocking) 1.459 +{ 1.460 + // TODO: This class should implement nsIAsyncInputStream so that callers 1.461 + // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. 1.462 + 1.463 + *aNonBlocking = true; 1.464 + return NS_OK; 1.465 +} 1.466 + 1.467 +NS_IMETHODIMP 1.468 +nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) 1.469 +{ 1.470 + if (NS_FAILED(mStatus)) 1.471 + return mStatus; 1.472 + 1.473 + int64_t pos = aOffset; 1.474 + 1.475 + switch (aWhence) { 1.476 + case NS_SEEK_SET: 1.477 + break; 1.478 + case NS_SEEK_CUR: 1.479 + pos += mLogicalCursor; 1.480 + break; 1.481 + case NS_SEEK_END: 1.482 + pos += mStorageStream->mLogicalLength; 1.483 + break; 1.484 + default: 1.485 + NS_NOTREACHED("unexpected whence value"); 1.486 + return NS_ERROR_UNEXPECTED; 1.487 + } 1.488 + if (pos == int64_t(mLogicalCursor)) 1.489 + return NS_OK; 1.490 + 1.491 + return Seek(pos); 1.492 +} 1.493 + 1.494 +NS_IMETHODIMP 1.495 +nsStorageInputStream::Tell(int64_t *aResult) 1.496 +{ 1.497 + if (NS_FAILED(mStatus)) 1.498 + return mStatus; 1.499 + 1.500 + *aResult = mLogicalCursor; 1.501 + return NS_OK; 1.502 +} 1.503 + 1.504 +NS_IMETHODIMP 1.505 +nsStorageInputStream::SetEOF() 1.506 +{ 1.507 + NS_NOTREACHED("nsStorageInputStream::SetEOF"); 1.508 + return NS_ERROR_NOT_IMPLEMENTED; 1.509 +} 1.510 + 1.511 +NS_METHOD 1.512 +nsStorageInputStream::Seek(uint32_t aPosition) 1.513 +{ 1.514 + uint32_t length = mStorageStream->mLogicalLength; 1.515 + if (aPosition > length) 1.516 + return NS_ERROR_INVALID_ARG; 1.517 + 1.518 + if (length == 0) 1.519 + return NS_OK; 1.520 + 1.521 + mSegmentNum = SegNum(aPosition); 1.522 + mReadCursor = SegOffset(aPosition); 1.523 + uint32_t available = length - aPosition; 1.524 + mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); 1.525 + mLogicalCursor = aPosition; 1.526 + return NS_OK; 1.527 +} 1.528 + 1.529 +nsresult 1.530 +NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result) 1.531 +{ 1.532 + nsStorageStream* storageStream = new nsStorageStream(); 1.533 + if (!storageStream) return NS_ERROR_OUT_OF_MEMORY; 1.534 + 1.535 + NS_ADDREF(storageStream); 1.536 + nsresult rv = storageStream->Init(segmentSize, maxSize); 1.537 + if (NS_FAILED(rv)) { 1.538 + NS_RELEASE(storageStream); 1.539 + return rv; 1.540 + } 1.541 + *result = storageStream; 1.542 + return NS_OK; 1.543 +} 1.544 + 1.545 +// Undefine LOG, so that other .cpp files (or their includes) won't complain 1.546 +// about it already being defined, when we build in unified mode. 1.547 +#undef LOG