CacheFileOutputStream.cpp (13696B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "CacheLog.h" 6 #include "CacheFileOutputStream.h" 7 8 #include "CacheFile.h" 9 #include "CacheEntry.h" 10 #include "nsStreamUtils.h" 11 #include "nsThreadUtils.h" 12 #include "mozilla/IntegerPrintfMacros.h" 13 #include <algorithm> 14 15 namespace mozilla::net { 16 17 NS_IMPL_ADDREF(CacheFileOutputStream) 18 NS_IMETHODIMP_(MozExternalRefCountType) 19 CacheFileOutputStream::Release() { 20 MOZ_ASSERT(0 != mRefCnt, "dup release"); 21 nsrefcnt count = --mRefCnt; 22 NS_LOG_RELEASE(this, count, "CacheFileOutputStream"); 23 24 if (0 == count) { 25 mRefCnt = 1; 26 { 27 CacheFileAutoLock lock(mFile); 28 mFile->RemoveOutput(this, mStatus); 29 } 30 delete (this); 31 return 0; 32 } 33 34 return count; 35 } 36 37 NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream) 38 NS_INTERFACE_MAP_ENTRY(nsIOutputStream) 39 NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream) 40 NS_INTERFACE_MAP_ENTRY(nsISeekableStream) 41 NS_INTERFACE_MAP_ENTRY(nsITellableStream) 42 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) 43 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream) 44 NS_INTERFACE_MAP_END 45 46 CacheFileOutputStream::CacheFileOutputStream( 47 CacheFile* aFile, CacheOutputCloseListener* aCloseListener, 48 bool aAlternativeData) 49 : mFile(aFile), 50 mCloseListener(aCloseListener), 51 mPos(0), 52 mClosed(false), 53 mAlternativeData(aAlternativeData), 54 mStatus(NS_OK), 55 mCallbackFlags(0) { 56 LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this)); 57 58 if (mAlternativeData) { 59 mPos = mFile->mAltDataOffset; 60 } 61 } 62 63 CacheFileOutputStream::~CacheFileOutputStream() { 64 LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this)); 65 } 66 67 // nsIOutputStream 68 NS_IMETHODIMP 69 CacheFileOutputStream::Close() { 70 LOG(("CacheFileOutputStream::Close() [this=%p]", this)); 71 return CloseWithStatus(NS_OK); 72 } 73 74 NS_IMETHODIMP 75 CacheFileOutputStream::Flush() { 76 // TODO do we need to implement flush ??? 77 LOG(("CacheFileOutputStream::Flush() [this=%p]", this)); 78 return NS_OK; 79 } 80 81 NS_IMETHODIMP 82 CacheFileOutputStream::StreamStatus() { 83 CacheFileAutoLock lock(mFile); 84 mFile->AssertOwnsLock(); // For thread-safety analysis 85 86 LOG(("CacheFileOutputStream::Close() [this=%p]", this)); 87 if (mClosed) { 88 return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED; 89 } 90 return NS_OK; 91 } 92 93 NS_IMETHODIMP 94 CacheFileOutputStream::Write(const char* aBuf, uint32_t aCount, 95 uint32_t* _retval) { 96 CacheFileAutoLock lock(mFile); 97 mFile->AssertOwnsLock(); // For thread-safety analysis 98 99 LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount)); 100 101 if (mClosed) { 102 LOG( 103 ("CacheFileOutputStream::Write() - Stream is closed. [this=%p, " 104 "status=0x%08" PRIx32 "]", 105 this, static_cast<uint32_t>(mStatus))); 106 107 return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED; 108 } 109 110 if (!mFile->mSkipSizeCheck && 111 CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) { 112 LOG(("CacheFileOutputStream::Write() - Entry is too big. [this=%p]", this)); 113 114 CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG); 115 return NS_ERROR_FILE_TOO_BIG; 116 } 117 118 // We use 64-bit offset when accessing the file, unfortunately we use 32-bit 119 // metadata offset, so we cannot handle data bigger than 4GB. 120 if (mPos + aCount > PR_UINT32_MAX) { 121 LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB. [this=%p]", 122 this)); 123 124 CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG); 125 return NS_ERROR_FILE_TOO_BIG; 126 } 127 128 *_retval = aCount; 129 130 if (mDict) { 131 // We need to calculate the hash for the entry as we save it 132 // We don't necessarily need to save the data in memory, however 133 mDict->AccumulateHash(aBuf, aCount); 134 } 135 136 while (aCount) { 137 EnsureCorrectChunk(false); 138 if (NS_FAILED(mStatus)) { 139 return mStatus; 140 } 141 142 FillHole(); 143 if (NS_FAILED(mStatus)) { 144 return mStatus; 145 } 146 147 uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize; 148 uint32_t canWrite = kChunkSize - chunkOffset; 149 uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount); 150 151 CacheFileChunkWriteHandle hnd = 152 mChunk->GetWriteHandle(chunkOffset + thisWrite); 153 if (!hnd.Buf()) { 154 CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY); 155 return NS_ERROR_OUT_OF_MEMORY; 156 } 157 158 memcpy(hnd.Buf() + chunkOffset, aBuf, thisWrite); 159 hnd.UpdateDataSize(chunkOffset, thisWrite); 160 161 mPos += thisWrite; 162 aBuf += thisWrite; 163 aCount -= thisWrite; 164 } 165 166 EnsureCorrectChunk(true); 167 168 LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]", *_retval, 169 this)); 170 171 return NS_OK; 172 } 173 174 NS_IMETHODIMP 175 CacheFileOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, 176 uint32_t* _retval) { 177 LOG( 178 ("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p" 179 ", count=%d]", 180 this, aFromStream, aCount)); 181 182 return NS_ERROR_NOT_IMPLEMENTED; 183 } 184 185 NS_IMETHODIMP 186 CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, 187 uint32_t aCount, uint32_t* _retval) { 188 LOG( 189 ("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, " 190 "count=%d]", 191 this, aCount)); 192 193 return NS_ERROR_NOT_IMPLEMENTED; 194 } 195 196 NS_IMETHODIMP 197 CacheFileOutputStream::IsNonBlocking(bool* _retval) { 198 *_retval = false; 199 return NS_OK; 200 } 201 202 // nsIAsyncOutputStream 203 NS_IMETHODIMP 204 CacheFileOutputStream::CloseWithStatus(nsresult aStatus) { 205 CacheFileAutoLock lock(mFile); 206 207 LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32 208 "]", 209 this, static_cast<uint32_t>(aStatus))); 210 211 return CloseWithStatusLocked(aStatus); 212 } 213 214 nsresult CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus) { 215 LOG( 216 ("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, " 217 "aStatus=0x%08" PRIx32 "]", 218 this, static_cast<uint32_t>(aStatus))); 219 220 if (mClosed) { 221 MOZ_ASSERT(!mCallback); 222 return NS_OK; 223 } 224 225 mClosed = true; 226 mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED; 227 228 if (mChunk) { 229 ReleaseChunk(); 230 } 231 232 if (mCallback) { 233 NotifyListener(); 234 } 235 236 mFile->RemoveOutput(this, mStatus); 237 238 return NS_OK; 239 } 240 241 NS_IMETHODIMP 242 CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, 243 uint32_t aFlags, uint32_t aRequestedCount, 244 nsIEventTarget* aEventTarget) { 245 CacheFileAutoLock lock(mFile); 246 247 LOG( 248 ("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, " 249 "requestedCount=%d, eventTarget=%p]", 250 this, aCallback, aFlags, aRequestedCount, aEventTarget)); 251 252 mCallback = aCallback; 253 mCallbackFlags = aFlags; 254 mCallbackTarget = aEventTarget; 255 256 if (!mCallback) return NS_OK; 257 258 // The stream is blocking so it is writable at any time 259 if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY)) NotifyListener(); 260 261 return NS_OK; 262 } 263 264 // nsISeekableStream 265 NS_IMETHODIMP 266 CacheFileOutputStream::Seek(int32_t whence, int64_t offset) { 267 CacheFileAutoLock lock(mFile); 268 mFile->AssertOwnsLock(); // For thread-safety analysis 269 270 LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%" PRId64 "]", 271 this, whence, offset)); 272 273 if (mClosed) { 274 LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this)); 275 return NS_BASE_STREAM_CLOSED; 276 } 277 278 int64_t newPos = offset; 279 switch (whence) { 280 case NS_SEEK_SET: 281 if (mAlternativeData) { 282 newPos += mFile->mAltDataOffset; 283 } 284 break; 285 case NS_SEEK_CUR: 286 newPos += mPos; 287 break; 288 case NS_SEEK_END: 289 if (mAlternativeData) { 290 newPos += mFile->mDataSize; 291 } else { 292 newPos += mFile->mAltDataOffset; 293 } 294 break; 295 default: 296 NS_ERROR("invalid whence"); 297 return NS_ERROR_INVALID_ARG; 298 } 299 mPos = newPos; 300 EnsureCorrectChunk(true); 301 302 LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos)); 303 return NS_OK; 304 } 305 306 NS_IMETHODIMP 307 CacheFileOutputStream::SetEOF() { 308 MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented"); 309 // Right now we don't use SetEOF(). If we ever need this method, we need 310 // to think about what to do with input streams that already points beyond 311 // new EOF. 312 return NS_ERROR_NOT_IMPLEMENTED; 313 } 314 315 // nsITellableStream 316 NS_IMETHODIMP 317 CacheFileOutputStream::Tell(int64_t* _retval) { 318 CacheFileAutoLock lock(mFile); 319 mFile->AssertOwnsLock(); // For thread-safety analysis 320 321 if (mClosed) { 322 LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this)); 323 return NS_BASE_STREAM_CLOSED; 324 } 325 326 *_retval = mPos; 327 328 if (mAlternativeData) { 329 *_retval -= mFile->mAltDataOffset; 330 } 331 332 LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%" PRId64 "]", this, 333 *_retval)); 334 return NS_OK; 335 } 336 337 // CacheFileChunkListener 338 nsresult CacheFileOutputStream::OnChunkRead(nsresult aResult, 339 CacheFileChunk* aChunk) { 340 MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!"); 341 return NS_ERROR_UNEXPECTED; 342 } 343 344 nsresult CacheFileOutputStream::OnChunkWritten(nsresult aResult, 345 CacheFileChunk* aChunk) { 346 MOZ_CRASH("CacheFileOutputStream::OnChunkWritten should not be called!"); 347 return NS_ERROR_UNEXPECTED; 348 } 349 350 nsresult CacheFileOutputStream::OnChunkAvailable(nsresult aResult, 351 uint32_t aChunkIdx, 352 CacheFileChunk* aChunk) { 353 MOZ_CRASH("CacheFileOutputStream::OnChunkAvailable should not be called!"); 354 return NS_ERROR_UNEXPECTED; 355 } 356 357 nsresult CacheFileOutputStream::OnChunkUpdated(CacheFileChunk* aChunk) { 358 MOZ_CRASH("CacheFileOutputStream::OnChunkUpdated should not be called!"); 359 return NS_ERROR_UNEXPECTED; 360 } 361 362 void CacheFileOutputStream::NotifyCloseListener() { 363 RefPtr<CacheOutputCloseListener> listener; 364 listener.swap(mCloseListener); 365 if (!listener) return; 366 367 listener->OnOutputClosed(); 368 } 369 370 void CacheFileOutputStream::ReleaseChunk() { 371 mFile->AssertOwnsLock(); 372 373 LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]", this, 374 mChunk->Index())); 375 376 // If the chunk didn't write any data we need to remove hash for this chunk 377 // that was added when the chunk was created in CacheFile::GetChunkLocked. 378 if (mChunk->DataSize() == 0) { 379 // It must be due to a failure, we don't create a new chunk when we don't 380 // have data to write. 381 MOZ_ASSERT(NS_FAILED(mChunk->GetStatus())); 382 mFile->mMetadata->RemoveHash(mChunk->Index()); 383 } 384 385 mFile->ReleaseOutsideLock(std::move(mChunk)); 386 } 387 388 void CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly) { 389 mFile->AssertOwnsLock(); 390 391 LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]", 392 this, aReleaseOnly)); 393 394 uint32_t chunkIdx = mPos / kChunkSize; 395 396 if (mChunk) { 397 if (mChunk->Index() == chunkIdx) { 398 // we have a correct chunk 399 LOG( 400 ("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk " 401 "[this=%p, idx=%d]", 402 this, chunkIdx)); 403 404 return; 405 } 406 ReleaseChunk(); 407 } 408 409 if (aReleaseOnly) return; 410 411 nsresult rv; 412 rv = mFile->GetChunkLocked(chunkIdx, CacheFile::WRITER, nullptr, 413 getter_AddRefs(mChunk)); 414 if (NS_FAILED(rv)) { 415 LOG( 416 ("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. " 417 "[this=%p, idx=%d, rv=0x%08" PRIx32 "]", 418 this, chunkIdx, static_cast<uint32_t>(rv))); 419 CloseWithStatusLocked(rv); 420 } 421 } 422 423 void CacheFileOutputStream::FillHole() { 424 mFile->AssertOwnsLock(); 425 426 MOZ_ASSERT(mChunk); 427 MOZ_ASSERT(mPos / kChunkSize == mChunk->Index()); 428 429 uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize; 430 if (mChunk->DataSize() >= pos) return; 431 432 LOG( 433 ("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range " 434 "%d-%d [this=%p]", 435 mChunk->Index(), mChunk->DataSize(), pos - 1, this)); 436 437 CacheFileChunkWriteHandle hnd = mChunk->GetWriteHandle(pos); 438 if (!hnd.Buf()) { 439 CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY); 440 return; 441 } 442 443 uint32_t offset = hnd.DataSize(); 444 memset(hnd.Buf() + offset, 0, pos - offset); 445 hnd.UpdateDataSize(offset, pos - offset); 446 } 447 448 void CacheFileOutputStream::NotifyListener() { 449 mFile->AssertOwnsLock(); 450 451 LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this)); 452 453 MOZ_ASSERT(mCallback); 454 455 if (!mCallbackTarget) { 456 mCallbackTarget = CacheFileIOManager::IOTarget(); 457 if (!mCallbackTarget) { 458 LOG( 459 ("CacheFileOutputStream::NotifyListener() - Cannot get Cache I/O " 460 "thread! Using main thread for callback.")); 461 mCallbackTarget = GetMainThreadSerialEventTarget(); 462 } 463 } 464 465 nsCOMPtr<nsIOutputStreamCallback> asyncCallback = 466 NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget); 467 468 mCallback = nullptr; 469 mCallbackTarget = nullptr; 470 471 asyncCallback->OnOutputStreamReady(this); 472 } 473 474 // Memory reporting 475 476 size_t CacheFileOutputStream::SizeOfIncludingThis( 477 mozilla::MallocSizeOf mallocSizeOf) const { 478 // Everything the stream keeps a reference to is already reported somewhere 479 // else. 480 // mFile reports itself. 481 // mChunk reported as part of CacheFile. 482 // mCloseListener is CacheEntry, already reported. 483 // mCallback is usually CacheFile or a class that is reported elsewhere. 484 return mallocSizeOf(this); 485 } 486 487 } // namespace mozilla::net