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.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 5 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "FileBlockCache.h" |
michael@0 | 8 | #include "VideoUtils.h" |
michael@0 | 9 | #include "prio.h" |
michael@0 | 10 | #include <algorithm> |
michael@0 | 11 | |
michael@0 | 12 | namespace mozilla { |
michael@0 | 13 | |
michael@0 | 14 | nsresult FileBlockCache::Open(PRFileDesc* aFD) |
michael@0 | 15 | { |
michael@0 | 16 | NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
michael@0 | 17 | NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE); |
michael@0 | 18 | { |
michael@0 | 19 | MonitorAutoLock mon(mFileMonitor); |
michael@0 | 20 | mFD = aFD; |
michael@0 | 21 | } |
michael@0 | 22 | { |
michael@0 | 23 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 24 | nsresult res = NS_NewThread(getter_AddRefs(mThread), |
michael@0 | 25 | nullptr, |
michael@0 | 26 | MEDIA_THREAD_STACK_SIZE); |
michael@0 | 27 | mIsOpen = NS_SUCCEEDED(res); |
michael@0 | 28 | return res; |
michael@0 | 29 | } |
michael@0 | 30 | } |
michael@0 | 31 | |
michael@0 | 32 | FileBlockCache::FileBlockCache() |
michael@0 | 33 | : mFileMonitor("MediaCache.Writer.IO.Monitor"), |
michael@0 | 34 | mFD(nullptr), |
michael@0 | 35 | mFDCurrentPos(0), |
michael@0 | 36 | mDataMonitor("MediaCache.Writer.Data.Monitor"), |
michael@0 | 37 | mIsWriteScheduled(false), |
michael@0 | 38 | mIsOpen(false) |
michael@0 | 39 | { |
michael@0 | 40 | MOZ_COUNT_CTOR(FileBlockCache); |
michael@0 | 41 | } |
michael@0 | 42 | |
michael@0 | 43 | FileBlockCache::~FileBlockCache() |
michael@0 | 44 | { |
michael@0 | 45 | NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying"); |
michael@0 | 46 | { |
michael@0 | 47 | // Note, mThread will be shutdown by the time this runs, so we won't |
michael@0 | 48 | // block while taking mFileMonitor. |
michael@0 | 49 | MonitorAutoLock mon(mFileMonitor); |
michael@0 | 50 | if (mFD) { |
michael@0 | 51 | PRStatus prrc; |
michael@0 | 52 | prrc = PR_Close(mFD); |
michael@0 | 53 | if (prrc != PR_SUCCESS) { |
michael@0 | 54 | NS_WARNING("PR_Close() failed."); |
michael@0 | 55 | } |
michael@0 | 56 | mFD = nullptr; |
michael@0 | 57 | } |
michael@0 | 58 | } |
michael@0 | 59 | MOZ_COUNT_DTOR(FileBlockCache); |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | |
michael@0 | 63 | void FileBlockCache::Close() |
michael@0 | 64 | { |
michael@0 | 65 | NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
michael@0 | 66 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 67 | |
michael@0 | 68 | mIsOpen = false; |
michael@0 | 69 | |
michael@0 | 70 | if (mThread) { |
michael@0 | 71 | // We must shut down the thread in another runnable. This is called |
michael@0 | 72 | // while we're shutting down the media cache, and nsIThread::Shutdown() |
michael@0 | 73 | // can cause events to run before it completes, which could end up |
michael@0 | 74 | // opening more streams, while the media cache is shutting down and |
michael@0 | 75 | // releasing memory etc! Also note we close mFD in the destructor so |
michael@0 | 76 | // as to not disturb any IO that's currently running. |
michael@0 | 77 | nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread); |
michael@0 | 78 | mThread = nullptr; |
michael@0 | 79 | NS_DispatchToMainThread(event); |
michael@0 | 80 | } |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData) |
michael@0 | 84 | { |
michael@0 | 85 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 86 | |
michael@0 | 87 | if (!mIsOpen) |
michael@0 | 88 | return NS_ERROR_FAILURE; |
michael@0 | 89 | |
michael@0 | 90 | // Check if we've already got a pending write scheduled for this block. |
michael@0 | 91 | mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1); |
michael@0 | 92 | bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr; |
michael@0 | 93 | mBlockChanges[aBlockIndex] = new BlockChange(aData); |
michael@0 | 94 | |
michael@0 | 95 | if (!blockAlreadyHadPendingChange || !mChangeIndexList.Contains(aBlockIndex)) { |
michael@0 | 96 | // We either didn't already have a pending change for this block, or we |
michael@0 | 97 | // did but we didn't have an entry for it in mChangeIndexList (we're in the process |
michael@0 | 98 | // of writing it and have removed the block's index out of mChangeIndexList |
michael@0 | 99 | // in Run() but not finished writing the block to file yet). Add the blocks |
michael@0 | 100 | // index to the end of mChangeIndexList to ensure the block is written as |
michael@0 | 101 | // as soon as possible. |
michael@0 | 102 | mChangeIndexList.PushBack(aBlockIndex); |
michael@0 | 103 | } |
michael@0 | 104 | NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block"); |
michael@0 | 105 | |
michael@0 | 106 | EnsureWriteScheduled(); |
michael@0 | 107 | |
michael@0 | 108 | return NS_OK; |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | void FileBlockCache::EnsureWriteScheduled() |
michael@0 | 112 | { |
michael@0 | 113 | mDataMonitor.AssertCurrentThreadOwns(); |
michael@0 | 114 | |
michael@0 | 115 | if (!mIsWriteScheduled) { |
michael@0 | 116 | mThread->Dispatch(this, NS_DISPATCH_NORMAL); |
michael@0 | 117 | mIsWriteScheduled = true; |
michael@0 | 118 | } |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | nsresult FileBlockCache::Seek(int64_t aOffset) |
michael@0 | 122 | { |
michael@0 | 123 | mFileMonitor.AssertCurrentThreadOwns(); |
michael@0 | 124 | |
michael@0 | 125 | if (mFDCurrentPos != aOffset) { |
michael@0 | 126 | int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET); |
michael@0 | 127 | if (result != aOffset) { |
michael@0 | 128 | NS_WARNING("Failed to seek media cache file"); |
michael@0 | 129 | return NS_ERROR_FAILURE; |
michael@0 | 130 | } |
michael@0 | 131 | mFDCurrentPos = result; |
michael@0 | 132 | } |
michael@0 | 133 | return NS_OK; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | nsresult FileBlockCache::ReadFromFile(int64_t aOffset, |
michael@0 | 137 | uint8_t* aDest, |
michael@0 | 138 | int32_t aBytesToRead, |
michael@0 | 139 | int32_t& aBytesRead) |
michael@0 | 140 | { |
michael@0 | 141 | mFileMonitor.AssertCurrentThreadOwns(); |
michael@0 | 142 | |
michael@0 | 143 | nsresult res = Seek(aOffset); |
michael@0 | 144 | if (NS_FAILED(res)) return res; |
michael@0 | 145 | |
michael@0 | 146 | aBytesRead = PR_Read(mFD, aDest, aBytesToRead); |
michael@0 | 147 | if (aBytesRead <= 0) |
michael@0 | 148 | return NS_ERROR_FAILURE; |
michael@0 | 149 | mFDCurrentPos += aBytesRead; |
michael@0 | 150 | |
michael@0 | 151 | return NS_OK; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex, |
michael@0 | 155 | const uint8_t* aBlockData) |
michael@0 | 156 | { |
michael@0 | 157 | mFileMonitor.AssertCurrentThreadOwns(); |
michael@0 | 158 | |
michael@0 | 159 | nsresult rv = Seek(BlockIndexToOffset(aBlockIndex)); |
michael@0 | 160 | if (NS_FAILED(rv)) return rv; |
michael@0 | 161 | |
michael@0 | 162 | int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE); |
michael@0 | 163 | if (amount < BLOCK_SIZE) { |
michael@0 | 164 | NS_WARNING("Failed to write media cache block!"); |
michael@0 | 165 | return NS_ERROR_FAILURE; |
michael@0 | 166 | } |
michael@0 | 167 | mFDCurrentPos += BLOCK_SIZE; |
michael@0 | 168 | |
michael@0 | 169 | return NS_OK; |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex, |
michael@0 | 173 | int32_t aDestBlockIndex) |
michael@0 | 174 | { |
michael@0 | 175 | mFileMonitor.AssertCurrentThreadOwns(); |
michael@0 | 176 | |
michael@0 | 177 | uint8_t buf[BLOCK_SIZE]; |
michael@0 | 178 | int32_t bytesRead = 0; |
michael@0 | 179 | if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex), |
michael@0 | 180 | buf, |
michael@0 | 181 | BLOCK_SIZE, |
michael@0 | 182 | bytesRead))) { |
michael@0 | 183 | return NS_ERROR_FAILURE; |
michael@0 | 184 | } |
michael@0 | 185 | return WriteBlockToFile(aDestBlockIndex, buf); |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | nsresult FileBlockCache::Run() |
michael@0 | 189 | { |
michael@0 | 190 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 191 | NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); |
michael@0 | 192 | NS_ASSERTION(!mChangeIndexList.IsEmpty(), "Only dispatch when there's work to do"); |
michael@0 | 193 | NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled."); |
michael@0 | 194 | |
michael@0 | 195 | while (!mChangeIndexList.IsEmpty()) { |
michael@0 | 196 | if (!mIsOpen) { |
michael@0 | 197 | // We've been closed, abort, discarding unwritten changes. |
michael@0 | 198 | mIsWriteScheduled = false; |
michael@0 | 199 | return NS_ERROR_FAILURE; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | // Process each pending change. We pop the index out of the change |
michael@0 | 203 | // list, but leave the BlockChange in mBlockChanges until the change |
michael@0 | 204 | // is written to file. This is so that any read which happens while |
michael@0 | 205 | // we drop mDataMonitor to write will refer to the data's source in |
michael@0 | 206 | // memory, rather than the not-yet up to date data written to file. |
michael@0 | 207 | // This also ensures we will insert a new index into mChangeIndexList |
michael@0 | 208 | // when this happens. |
michael@0 | 209 | |
michael@0 | 210 | // Hold a reference to the change, in case another change |
michael@0 | 211 | // overwrites the mBlockChanges entry for this block while we drop |
michael@0 | 212 | // mDataMonitor to take mFileMonitor. |
michael@0 | 213 | int32_t blockIndex = mChangeIndexList.PopFront(); |
michael@0 | 214 | nsRefPtr<BlockChange> change = mBlockChanges[blockIndex]; |
michael@0 | 215 | NS_ABORT_IF_FALSE(change, |
michael@0 | 216 | "Change index list should only contain entries for blocks with changes"); |
michael@0 | 217 | { |
michael@0 | 218 | MonitorAutoUnlock unlock(mDataMonitor); |
michael@0 | 219 | MonitorAutoLock lock(mFileMonitor); |
michael@0 | 220 | if (change->IsWrite()) { |
michael@0 | 221 | WriteBlockToFile(blockIndex, change->mData.get()); |
michael@0 | 222 | } else if (change->IsMove()) { |
michael@0 | 223 | MoveBlockInFile(change->mSourceBlockIndex, blockIndex); |
michael@0 | 224 | } |
michael@0 | 225 | } |
michael@0 | 226 | // If a new change has not been made to the block while we dropped |
michael@0 | 227 | // mDataMonitor, clear reference to the old change. Otherwise, the old |
michael@0 | 228 | // reference has been cleared already. |
michael@0 | 229 | if (mBlockChanges[blockIndex] == change) { |
michael@0 | 230 | mBlockChanges[blockIndex] = nullptr; |
michael@0 | 231 | } |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | mIsWriteScheduled = false; |
michael@0 | 235 | |
michael@0 | 236 | return NS_OK; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | nsresult FileBlockCache::Read(int64_t aOffset, |
michael@0 | 240 | uint8_t* aData, |
michael@0 | 241 | int32_t aLength, |
michael@0 | 242 | int32_t* aBytes) |
michael@0 | 243 | { |
michael@0 | 244 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 245 | |
michael@0 | 246 | if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX) |
michael@0 | 247 | return NS_ERROR_FAILURE; |
michael@0 | 248 | |
michael@0 | 249 | int32_t bytesToRead = aLength; |
michael@0 | 250 | int64_t offset = aOffset; |
michael@0 | 251 | uint8_t* dst = aData; |
michael@0 | 252 | while (bytesToRead > 0) { |
michael@0 | 253 | int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE); |
michael@0 | 254 | int32_t start = offset % BLOCK_SIZE; |
michael@0 | 255 | int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead); |
michael@0 | 256 | |
michael@0 | 257 | // If the block is not yet written to file, we can just read from |
michael@0 | 258 | // the memory buffer, otherwise we need to read from file. |
michael@0 | 259 | int32_t bytesRead = 0; |
michael@0 | 260 | nsRefPtr<BlockChange> change = mBlockChanges[blockIndex]; |
michael@0 | 261 | if (change && change->IsWrite()) { |
michael@0 | 262 | // Block isn't yet written to file. Read from memory buffer. |
michael@0 | 263 | const uint8_t* blockData = change->mData.get(); |
michael@0 | 264 | memcpy(dst, blockData + start, amount); |
michael@0 | 265 | bytesRead = amount; |
michael@0 | 266 | } else { |
michael@0 | 267 | if (change && change->IsMove()) { |
michael@0 | 268 | // The target block is the destination of a not-yet-completed move |
michael@0 | 269 | // action, so read from the move's source block from file. Note we |
michael@0 | 270 | // *don't* follow a chain of moves here, as a move's source index |
michael@0 | 271 | // is resolved when MoveBlock() is called, and the move's source's |
michael@0 | 272 | // block could be have itself been subject to a move (or write) |
michael@0 | 273 | // which happened *after* this move was recorded. |
michael@0 | 274 | blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex; |
michael@0 | 275 | } |
michael@0 | 276 | // Block has been written to file, either as the source block of a move, |
michael@0 | 277 | // or as a stable (all changes made) block. Read the data directly |
michael@0 | 278 | // from file. |
michael@0 | 279 | nsresult res; |
michael@0 | 280 | { |
michael@0 | 281 | MonitorAutoUnlock unlock(mDataMonitor); |
michael@0 | 282 | MonitorAutoLock lock(mFileMonitor); |
michael@0 | 283 | res = ReadFromFile(BlockIndexToOffset(blockIndex) + start, |
michael@0 | 284 | dst, |
michael@0 | 285 | amount, |
michael@0 | 286 | bytesRead); |
michael@0 | 287 | } |
michael@0 | 288 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 289 | } |
michael@0 | 290 | dst += bytesRead; |
michael@0 | 291 | offset += bytesRead; |
michael@0 | 292 | bytesToRead -= bytesRead; |
michael@0 | 293 | } |
michael@0 | 294 | *aBytes = aLength - bytesToRead; |
michael@0 | 295 | return NS_OK; |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex) |
michael@0 | 299 | { |
michael@0 | 300 | NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
michael@0 | 301 | MonitorAutoLock mon(mDataMonitor); |
michael@0 | 302 | |
michael@0 | 303 | if (!mIsOpen) |
michael@0 | 304 | return NS_ERROR_FAILURE; |
michael@0 | 305 | |
michael@0 | 306 | mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1); |
michael@0 | 307 | |
michael@0 | 308 | // The source block's contents may be the destination of another pending |
michael@0 | 309 | // move, which in turn can be the destination of another pending move, |
michael@0 | 310 | // etc. Resolve the final source block, so that if one of the blocks in |
michael@0 | 311 | // the chain of moves is overwritten, we don't lose the reference to the |
michael@0 | 312 | // contents of the destination block. |
michael@0 | 313 | int32_t sourceIndex = aSourceBlockIndex; |
michael@0 | 314 | BlockChange* sourceBlock = nullptr; |
michael@0 | 315 | while ((sourceBlock = mBlockChanges[sourceIndex]) && |
michael@0 | 316 | sourceBlock->IsMove()) { |
michael@0 | 317 | sourceIndex = sourceBlock->mSourceBlockIndex; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | if (mBlockChanges[aDestBlockIndex] == nullptr || |
michael@0 | 321 | !mChangeIndexList.Contains(aDestBlockIndex)) { |
michael@0 | 322 | // Only add another entry to the change index list if we don't already |
michael@0 | 323 | // have one for this block. We won't have an entry when either there's |
michael@0 | 324 | // no pending change for this block, or if there is a pending change for |
michael@0 | 325 | // this block and we're in the process of writing it (we've popped the |
michael@0 | 326 | // block's index out of mChangeIndexList in Run() but not finished writing |
michael@0 | 327 | // the block to file yet. |
michael@0 | 328 | mChangeIndexList.PushBack(aDestBlockIndex); |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | // If the source block hasn't yet been written to file then the dest block |
michael@0 | 332 | // simply contains that same write. Resolve this as a write instead. |
michael@0 | 333 | if (sourceBlock && sourceBlock->IsWrite()) { |
michael@0 | 334 | mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get()); |
michael@0 | 335 | } else { |
michael@0 | 336 | mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex); |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | EnsureWriteScheduled(); |
michael@0 | 340 | |
michael@0 | 341 | NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex), |
michael@0 | 342 | "Should have scheduled block for change"); |
michael@0 | 343 | |
michael@0 | 344 | return NS_OK; |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | } // End namespace mozilla. |