FileUtils.cpp (29259B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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/. */ 6 7 #include "CacheCipherKeyManager.h" 8 #include "DBSchema.h" 9 #include "FileUtilsImpl.h" 10 #include "mozilla/ScopeExit.h" 11 #include "mozilla/SnappyCompressOutputStream.h" 12 #include "mozilla/dom/InternalResponse.h" 13 #include "mozilla/dom/quota/DecryptingInputStream.h" 14 #include "mozilla/dom/quota/DecryptingInputStream_impl.h" 15 #include "mozilla/dom/quota/EncryptingOutputStream.h" 16 #include "mozilla/dom/quota/EncryptingOutputStream_impl.h" 17 #include "mozilla/dom/quota/FileStreams.h" 18 #include "mozilla/dom/quota/PersistenceType.h" 19 #include "mozilla/dom/quota/QuotaManager.h" 20 #include "mozilla/dom/quota/QuotaObject.h" 21 #include "mozilla/dom/quota/ResultExtensions.h" 22 #include "nsIFile.h" 23 #include "nsIObjectInputStream.h" 24 #include "nsIObjectOutputStream.h" 25 #include "nsIUUIDGenerator.h" 26 #include "nsNetCID.h" 27 #include "nsNetUtil.h" 28 #include "nsServiceManagerUtils.h" 29 #include "nsString.h" 30 #include "nsThreadUtils.h" 31 #include "snappy/snappy.h" 32 33 namespace mozilla::dom::cache { 34 35 static_assert(SNAPPY_VERSION == 0x010202); 36 37 using mozilla::dom::quota::Client; 38 using mozilla::dom::quota::CloneFileAndAppend; 39 using mozilla::dom::quota::GetDirEntryKind; 40 using mozilla::dom::quota::nsIFileKind; 41 using mozilla::dom::quota::QuotaManager; 42 using mozilla::dom::quota::QuotaObject; 43 44 namespace { 45 46 // Const variable for generate padding size. 47 // XXX This will be tweaked to something more meaningful in Bug 1383656. 48 const int64_t kRoundUpNumber = 20480; 49 50 // At the moment, the encrypted stream block size is assumed to be unchangeable 51 // between encrypting and decrypting blobs. This assumptions holds as long as we 52 // only encrypt in private browsing mode, but when we support encryption for 53 // persistent storage, this needs to be changed. 54 constexpr uint32_t kEncryptedStreamBlockSize = 4096; 55 56 enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP }; 57 58 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile( 59 nsIFile& aBaseDir, const nsID& aId, BodyFileType aType, 60 bool aCreateDirIfNotExists = false); 61 62 int64_t RoundUp(int64_t aX, int64_t aY); 63 64 // The alogrithm for generating padding refers to the mitigation approach in 65 // https://github.com/whatwg/storage/issues/31. 66 // First, generate a random number between 0 and 100kB. 67 // Next, round up the sum of random number and response size to the nearest 68 // 20kB. 69 // Finally, the virtual padding size will be the result minus the response size. 70 int64_t BodyGeneratePadding(int64_t aBodyFileSize, uint32_t aPaddingInfo); 71 72 nsresult DirectoryPaddingWrite(nsIFile& aBaseDir, 73 DirPaddingFile aPaddingFileType, 74 int64_t aPaddingSize); 75 76 const auto kMorgueDirectory = u"morgue"_ns; 77 78 bool IsFileNotFoundError(const nsresult aRv) { 79 return aRv == NS_ERROR_FILE_NOT_FOUND; 80 } 81 82 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyGetCacheDir( 83 nsIFile& aBaseDir, const nsID& aId, bool aCreateDirIfNotExists = true) { 84 QM_TRY_UNWRAP(auto cacheDir, CloneFileAndAppend(aBaseDir, kMorgueDirectory)); 85 86 // Some file systems have poor performance when there are too many files 87 // in a single directory. Mitigate this issue by spreading the body 88 // files out into sub-directories. We use the last byte of the ID for 89 // the name of the sub-directory. 90 QM_TRY(MOZ_TO_RESULT(cacheDir->Append(IntToString(aId.m3[7])))); 91 92 if (aCreateDirIfNotExists) { 93 bool exists; 94 QM_TRY(MOZ_TO_RESULT(cacheDir->Exists(&exists))); 95 if (!exists) { 96 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 97 // Expression. 98 MOZ_TO_RESULT(cacheDir->Create(nsIFile::DIRECTORY_TYPE, 0755)), 99 // Predicate. 100 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 101 // Fallback. 102 ErrToDefaultOk<>)); 103 } 104 } 105 106 return WrapNotNullUnchecked(std::move(cacheDir)); 107 } 108 109 } // namespace 110 111 nsresult BodyCreateDir(nsIFile& aBaseDir) { 112 QM_TRY_INSPECT(const auto& bodyDir, 113 CloneFileAndAppend(aBaseDir, kMorgueDirectory)); 114 115 // Callers call this function without checking if the directory already 116 // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we 117 // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the 118 // reports. 119 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 120 // Expression. 121 MOZ_TO_RESULT(bodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755)), 122 // Predicate. 123 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 124 // Fallback. 125 ErrToDefaultOk<>)); 126 127 return NS_OK; 128 } 129 130 nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata, 131 nsIFile& aBaseDir) { 132 QM_TRY_INSPECT(const auto& bodyDir, 133 CloneFileAndAppend(aBaseDir, kMorgueDirectory)); 134 135 QM_TRY(MOZ_TO_RESULT(RemoveNsIFileRecursively(aDirectoryMetadata, *bodyDir))); 136 137 return NS_OK; 138 } 139 140 Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream( 141 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, 142 const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey, 143 nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) { 144 MOZ_DIAGNOSTIC_ASSERT(aClosure); 145 MOZ_DIAGNOSTIC_ASSERT(aCallback); 146 147 // Check if the final file already exists, in which case we error out. 148 { 149 QM_TRY_INSPECT(const auto& finalFile, 150 // There's no need to create the cache directory in this 151 // case since we're just checking. 152 BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_FINAL, 153 /* aCreateDirIfNotExists */ false)); 154 155 QM_TRY_INSPECT(const bool& exists, 156 MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, Exists)); 157 158 QM_TRY(OkIf(!exists), Err(NS_ERROR_FILE_ALREADY_EXISTS)); 159 } 160 161 QM_TRY_INSPECT(const auto& tmpFile, 162 // We do want to create and write to the file here, so we do 163 // need to ensure the directory exists. 164 BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_TMP, 165 /* aCreateDirIfNotExists */ true)); 166 167 QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream, 168 CreateFileOutputStream(aDirectoryMetadata.mPersistenceType, 169 aDirectoryMetadata, Client::DOMCACHE, 170 tmpFile.get())); 171 172 const auto privateBody = aDirectoryMetadata.mIsPrivate; 173 if (privateBody) { 174 MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey); 175 176 fileStream = MakeRefPtr<quota::EncryptingOutputStream<CipherStrategy>>( 177 std::move(fileStream), kEncryptedStreamBlockSize, *aMaybeCipherKey); 178 } 179 180 const auto compressed = MakeRefPtr<SnappyCompressOutputStream>(fileStream); 181 182 const nsCOMPtr<nsIEventTarget> target = 183 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 184 185 nsCOMPtr<nsISupports> copyContext; 186 QM_TRY(MOZ_TO_RESULT( 187 NS_AsyncCopy(&aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS, 188 compressed->BlockSize(), aCallback, aClosure, true, 189 true, // close streams 190 getter_AddRefs(copyContext)))); 191 192 return std::move(copyContext); 193 } 194 195 void BodyCancelWrite(nsISupports& aCopyContext) { 196 QM_WARNONLY_TRY( 197 QM_TO_RESULT(NS_CancelAsyncCopy(&aCopyContext, NS_ERROR_ABORT))); 198 199 // TODO The partially written file must be cleaned up after the async copy 200 // makes its callback. 201 } 202 203 Result<int64_t, nsresult> BodyFinalizeWrite(nsIFile& aBaseDir, 204 const nsID& aId) { 205 QM_TRY_INSPECT(const auto& tmpFile, 206 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP)); 207 208 QM_TRY_INSPECT(const auto& finalFile, 209 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); 210 211 nsAutoString finalFileName; 212 QM_TRY(MOZ_TO_RESULT(finalFile->GetLeafName(finalFileName))); 213 214 // It's fine to not notify the QuotaManager that the path has been changed, 215 // because its path will be updated and its size will be recalculated when 216 // opening file next time. 217 QM_TRY(MOZ_TO_RESULT(tmpFile->RenameTo(nullptr, finalFileName))); 218 219 QM_TRY_INSPECT(const int64_t& fileSize, 220 MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, GetFileSize)); 221 222 return fileSize; 223 } 224 225 Result<int64_t, nsresult> GetBodyDiskSize(nsIFile& aBaseDir, const nsID& aId) { 226 QM_TRY_INSPECT(const auto& finalFile, 227 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); 228 229 QM_TRY_INSPECT(const int64_t& fileSize, 230 MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, GetFileSize)); 231 232 return fileSize; 233 } 234 235 Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen( 236 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, 237 const nsID& aId, Maybe<CipherKey> aMaybeCipherKey) { 238 QM_TRY_INSPECT(const auto& finalFile, 239 BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); 240 241 QM_TRY_UNWRAP(nsCOMPtr<nsIInputStream> fileInputStream, 242 CreateFileInputStream(aDirectoryMetadata.mPersistenceType, 243 aDirectoryMetadata, Client::DOMCACHE, 244 finalFile.get())); 245 246 auto privateBody = aDirectoryMetadata.mIsPrivate; 247 if (privateBody) { 248 MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey); 249 250 fileInputStream = new quota::DecryptingInputStream<CipherStrategy>( 251 WrapNotNull(std::move(fileInputStream)), kEncryptedStreamBlockSize, 252 *aMaybeCipherKey); 253 } 254 return WrapMovingNotNull(std::move(fileInputStream)); 255 } 256 257 nsresult BodyMaybeUpdatePaddingSize( 258 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, 259 const nsID& aId, const uint32_t aPaddingInfo, int64_t* aPaddingSizeInOut) { 260 MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeInOut); 261 262 QM_TRY_INSPECT(const auto& bodyFile, 263 BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP)); 264 265 QuotaManager* quotaManager = QuotaManager::Get(); 266 MOZ_DIAGNOSTIC_ASSERT(quotaManager); 267 268 int64_t fileSize = 0; 269 RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject( 270 aDirectoryMetadata.mPersistenceType, aDirectoryMetadata, Client::DOMCACHE, 271 bodyFile.get(), -1, &fileSize); 272 MOZ_DIAGNOSTIC_ASSERT(quotaObject); 273 MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0); 274 // XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815 275 if (!quotaObject) { 276 return NS_ERROR_UNEXPECTED; 277 } 278 279 if (*aPaddingSizeInOut == InternalResponse::UNKNOWN_PADDING_SIZE) { 280 *aPaddingSizeInOut = BodyGeneratePadding(fileSize, aPaddingInfo); 281 } 282 283 MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeInOut >= 0); 284 285 if (!quotaObject->IncreaseSize(*aPaddingSizeInOut)) { 286 return NS_ERROR_FILE_NO_DEVICE_SPACE; 287 } 288 289 return NS_OK; 290 } 291 292 nsresult BodyDeleteFiles(const CacheDirectoryMetadata& aDirectoryMetadata, 293 nsIFile& aBaseDir, const nsTArray<nsID>& aIdList) { 294 for (const auto id : aIdList) { 295 bool exists; 296 297 QM_TRY_INSPECT(const auto& finalFile, 298 BodyIdToFile(aBaseDir, id, BODY_FILE_FINAL)); 299 QM_TRY(MOZ_TO_RESULT(finalFile->Exists(&exists))); 300 if (exists) { 301 QM_TRY(MOZ_TO_RESULT(RemoveNsIFile(aDirectoryMetadata, *finalFile, 302 /* aTrackQuota */ true))); 303 } 304 305 QM_TRY_INSPECT(const auto& tempFile, 306 BodyIdToFile(aBaseDir, id, BODY_FILE_TMP)); 307 QM_TRY(MOZ_TO_RESULT(tempFile->Exists(&exists))); 308 if (exists) { 309 QM_TRY(MOZ_TO_RESULT(RemoveNsIFile(aDirectoryMetadata, *tempFile, 310 /* aTrackQuota */ true))); 311 } 312 } 313 314 return NS_OK; 315 } 316 317 namespace { 318 319 Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile( 320 nsIFile& aBaseDir, const nsID& aId, const BodyFileType aType, 321 bool aCreateDirIfNotExists) { 322 QM_TRY_UNWRAP(auto bodyFile, 323 BodyGetCacheDir(aBaseDir, aId, aCreateDirIfNotExists)); 324 325 char idString[NSID_LENGTH]; 326 aId.ToProvidedString(idString); 327 328 NS_ConvertASCIItoUTF16 fileName(idString); 329 330 if (aType == BODY_FILE_FINAL) { 331 fileName.AppendLiteral(".final"); 332 } else { 333 fileName.AppendLiteral(".tmp"); 334 } 335 336 QM_TRY(MOZ_TO_RESULT(bodyFile->Append(fileName))); 337 338 return bodyFile; 339 } 340 341 int64_t RoundUp(const int64_t aX, const int64_t aY) { 342 MOZ_DIAGNOSTIC_ASSERT(aX >= 0); 343 MOZ_DIAGNOSTIC_ASSERT(aY > 0); 344 345 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY); 346 return aY + ((aX - 1) / aY) * aY; 347 } 348 349 int64_t BodyGeneratePadding(const int64_t aBodyFileSize, 350 const uint32_t aPaddingInfo) { 351 // Generate padding 352 int64_t randomSize = static_cast<int64_t>(aPaddingInfo); 353 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize); 354 randomSize += aBodyFileSize; 355 356 return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize; 357 } 358 359 nsresult DirectoryPaddingWrite(nsIFile& aBaseDir, 360 DirPaddingFile aPaddingFileType, 361 int64_t aPaddingSize) { 362 MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0); 363 364 QM_TRY_INSPECT( 365 const auto& file, 366 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE 367 ? nsLiteralString(PADDING_TMP_FILE_NAME) 368 : nsLiteralString(PADDING_FILE_NAME))); 369 370 QM_TRY_INSPECT(const auto& outputStream, NS_NewLocalFileOutputStream(file)); 371 372 nsCOMPtr<nsIObjectOutputStream> objectStream = 373 NS_NewObjectOutputStream(outputStream); 374 375 QM_TRY(MOZ_TO_RESULT(objectStream->Write64(aPaddingSize))); 376 377 return NS_OK; 378 } 379 380 } // namespace 381 382 nsresult BodyDeleteOrphanedFiles( 383 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, 384 nsTHashSet<nsID>& aKnownBodyIds) { 385 // body files are stored in a directory structure like: 386 // 387 // /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final 388 // /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp 389 390 Maybe<CacheDirectoryMetadata> dirMetaData = Some(aDirectoryMetadata); 391 392 QM_TRY_INSPECT(const auto& dir, 393 CloneFileAndAppend(aBaseDir, kMorgueDirectory)); 394 395 // Iterate over all the intermediate morgue subdirs 396 QM_TRY(quota::CollectEachFile( 397 *dir, 398 [&dirMetaData, &aKnownBodyIds]( 399 const nsCOMPtr<nsIFile>& subdir) -> Result<Ok, nsresult> { 400 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*subdir)); 401 402 switch (dirEntryKind) { 403 case nsIFileKind::ExistsAsDirectory: { 404 const auto removeOrphanedFiles = 405 [&dirMetaData, &aKnownBodyIds]( 406 nsIFile& bodyFile, 407 const nsACString& leafName) -> Result<bool, nsresult> { 408 // Finally, parse the uuid out of the name. If it fails to parse, 409 // then ignore the file. 410 auto cleanup = MakeScopeExit([&dirMetaData, &bodyFile] { 411 DebugOnly<nsresult> result = 412 RemoveNsIFile(dirMetaData, bodyFile); 413 MOZ_ASSERT(NS_SUCCEEDED(result)); 414 }); 415 416 nsID id; 417 QM_TRY(OkIf(id.Parse(leafName.BeginReading())), true); 418 419 if (!aKnownBodyIds.Contains(id)) { 420 return true; 421 } 422 423 cleanup.release(); 424 425 return false; 426 }; 427 428 // QM_OR_ELSE_WARN_IF is not used here since we just want to log 429 // NS_ERROR_FILE_FS_CORRUPTED result and not spam the reports (even 430 // a warning in the reports is not desired). 431 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 432 // Expression. 433 MOZ_TO_RESULT(BodyTraverseFilesForCleanup(dirMetaData, *subdir, 434 removeOrphanedFiles)), 435 // Predicate. 436 IsSpecificError<NS_ERROR_FILE_FS_CORRUPTED>, 437 // Fallback. We treat NS_ERROR_FILE_FS_CORRUPTED as if the 438 // directory did not exist at all. 439 ErrToDefaultOk<>)); 440 break; 441 } 442 443 case nsIFileKind::ExistsAsFile: { 444 // If a file got in here somehow, try to remove it and move on 445 DebugOnly<nsresult> result = RemoveNsIFile(dirMetaData, *subdir, 446 /* aTrackQuota */ false); 447 MOZ_ASSERT(NS_SUCCEEDED(result)); 448 break; 449 } 450 451 case nsIFileKind::DoesNotExist: 452 // Ignore files that got removed externally while iterating. 453 break; 454 } 455 456 return Ok{}; 457 })); 458 459 return NS_OK; 460 } 461 462 namespace { 463 464 Result<nsCOMPtr<nsIFile>, nsresult> GetMarkerFileHandle( 465 const CacheDirectoryMetadata& aDirectoryMetadata) { 466 QM_TRY_UNWRAP(auto marker, 467 CloneFileAndAppend(*aDirectoryMetadata.mDir, u"cache"_ns)); 468 469 QM_TRY(MOZ_TO_RESULT(marker->Append(u"context_open.marker"_ns))); 470 471 return marker; 472 } 473 474 } // namespace 475 476 nsresult CreateMarkerFile(const CacheDirectoryMetadata& aDirectoryMetadata) { 477 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata)); 478 479 // Callers call this function without checking if the file already exists 480 // (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want 481 // to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports. 482 // 483 // TODO: In theory if this file exists, then Context::~Context should have 484 // cleaned it up, but obviously we can crash and not clean it up, which is 485 // the whole point of the marker file. In that case, we'll realize the marker 486 // file exists in SetupAction::RunSyncWithDBOnTarget and do some cleanup, but 487 // we won't delete the marker file, so if we see this marker file, it is part 488 // of our standard operating procedure to redundantly try and create the 489 // marker here. We currently treat this as idempotent usage, but we could 490 // make sure to delete the marker file when handling the existing marker 491 // file in SetupAction::RunSyncWithDBOnTarget and change 492 // QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end. 493 QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF( 494 // Expression. 495 MOZ_TO_RESULT(marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644)), 496 // Predicate. 497 IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>, 498 // Fallback. 499 ErrToDefaultOk<>)); 500 501 // Note, we don't need to fsync here. We only care about actually 502 // writing the marker if later modifications to the Cache are 503 // actually flushed to the disk. If the OS crashes before the marker 504 // is written then we are ensured no other changes to the Cache were 505 // flushed either. 506 507 return NS_OK; 508 } 509 510 nsresult DeleteMarkerFile(const CacheDirectoryMetadata& aDirectoryMetadata) { 511 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata)); 512 513 DebugOnly<nsresult> result = 514 RemoveNsIFile(aDirectoryMetadata, *marker, /* aTrackQuota */ false); 515 MOZ_ASSERT(NS_SUCCEEDED(result)); 516 517 // Again, no fsync is necessary. If the OS crashes before the file 518 // removal is flushed, then the Cache will search for stale data on 519 // startup. This will cause the next Cache access to be a bit slow, but 520 // it seems appropriate after an OS crash. 521 522 return NS_OK; 523 } 524 525 bool MarkerFileExists(const CacheDirectoryMetadata& aDirectoryMetadata) { 526 QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata), 527 false); 528 529 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(marker, Exists), false); 530 } 531 532 nsresult RemoveNsIFileRecursively( 533 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, nsIFile& aFile, 534 const bool aTrackQuota) { 535 // XXX This assertion proves that we can remove aTrackQuota and just check 536 // aClientMetadata 537 MOZ_DIAGNOSTIC_ASSERT_IF(aTrackQuota, aDirectoryMetadata); 538 539 QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(aFile)); 540 541 switch (dirEntryKind) { 542 case nsIFileKind::ExistsAsDirectory: 543 // Unfortunately, we need to traverse all the entries and delete files one 544 // by 545 // one to update their usages to the QuotaManager. 546 QM_TRY(quota::CollectEachFile( 547 aFile, 548 [&aDirectoryMetadata, &aTrackQuota]( 549 const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> { 550 QM_TRY(MOZ_TO_RESULT(RemoveNsIFileRecursively(aDirectoryMetadata, 551 *file, aTrackQuota))); 552 553 return Ok{}; 554 })); 555 556 // In the end, remove the folder 557 QM_TRY(MOZ_TO_RESULT(aFile.Remove(/* recursive */ false))); 558 559 break; 560 561 case nsIFileKind::ExistsAsFile: 562 return RemoveNsIFile(aDirectoryMetadata, aFile, aTrackQuota); 563 564 case nsIFileKind::DoesNotExist: 565 // Ignore files that got removed externally while iterating. 566 break; 567 } 568 569 return NS_OK; 570 } 571 572 nsresult RemoveNsIFile(const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 573 nsIFile& aFile, const bool aTrackQuota) { 574 // XXX This assertion proves that we can remove aTrackQuota and just check 575 // aClientMetadata 576 MOZ_DIAGNOSTIC_ASSERT_IF(aTrackQuota, aDirectoryMetadata); 577 578 int64_t fileSize = 0; 579 if (aTrackQuota) { 580 QM_TRY_INSPECT( 581 const auto& maybeFileSize, 582 QM_OR_ELSE_WARN_IF( 583 // Expression. 584 MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize).map(Some<int64_t>), 585 // Predicate. 586 IsFileNotFoundError, 587 // Fallback. 588 ErrToDefaultOk<Maybe<int64_t>>)); 589 590 if (!maybeFileSize) { 591 return NS_OK; 592 } 593 594 fileSize = *maybeFileSize; 595 } 596 597 QM_TRY(QM_OR_ELSE_WARN_IF( 598 // Expression. 599 MOZ_TO_RESULT(aFile.Remove(/* recursive */ false)), 600 // Predicate. 601 IsFileNotFoundError, 602 // Fallback. 603 ErrToDefaultOk<>)); 604 605 if (fileSize > 0) { 606 MOZ_ASSERT(aTrackQuota); 607 DecreaseUsageForDirectoryMetadata(*aDirectoryMetadata, fileSize); 608 } 609 610 return NS_OK; 611 } 612 613 void DecreaseUsageForDirectoryMetadata( 614 const CacheDirectoryMetadata& aDirectoryMetadata, 615 const int64_t aUpdatingSize) { 616 MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0); 617 618 QuotaManager* quotaManager = QuotaManager::Get(); 619 MOZ_DIAGNOSTIC_ASSERT(quotaManager); 620 621 quotaManager->DecreaseUsageForClient( 622 quota::ClientMetadata{aDirectoryMetadata, Client::DOMCACHE}, 623 aUpdatingSize); 624 } 625 626 bool DirectoryPaddingFileExists(nsIFile& aBaseDir, 627 DirPaddingFile aPaddingFileType) { 628 QM_TRY_INSPECT( 629 const auto& file, 630 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE 631 ? nsLiteralString(PADDING_TMP_FILE_NAME) 632 : nsLiteralString(PADDING_FILE_NAME)), 633 false); 634 635 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists), false); 636 } 637 638 Result<int64_t, nsresult> DirectoryPaddingGet(nsIFile& aBaseDir) { 639 MOZ_DIAGNOSTIC_ASSERT( 640 !DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE)); 641 642 QM_TRY_INSPECT( 643 const auto& file, 644 CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_FILE_NAME))); 645 646 QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file)); 647 648 QM_TRY_INSPECT(const auto& bufferedStream, 649 NS_NewBufferedInputStream(stream.forget(), 512)); 650 651 const nsCOMPtr<nsIObjectInputStream> objectStream = 652 NS_NewObjectInputStream(bufferedStream); 653 654 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(objectStream, Read64) 655 .map([](const uint64_t val) { return int64_t(val); })); 656 } 657 658 nsresult DirectoryPaddingInit(nsIFile& aBaseDir) { 659 QM_TRY( 660 MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0))); 661 662 return NS_OK; 663 } 664 665 nsresult UpdateDirectoryPaddingFile(nsIFile& aBaseDir, 666 mozIStorageConnection& aConn, 667 const int64_t aIncreaseSize, 668 const int64_t aDecreaseSize, 669 const bool aTemporaryFileExist) { 670 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); 671 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); 672 673 const auto directoryPaddingGetResult = 674 aTemporaryFileExist ? Maybe<int64_t>{} : [&aBaseDir] { 675 QM_TRY_RETURN(QM_OR_ELSE_WARN_IF( 676 // Expression. 677 DirectoryPaddingGet(aBaseDir).map(Some<int64_t>), 678 // Predicate. 679 IsFileNotFoundError, 680 // Fallback. 681 ErrToDefaultOk<Maybe<int64_t>>), 682 Maybe<int64_t>{}); 683 }(); 684 685 QM_TRY_INSPECT( 686 const int64_t& currentPaddingSize, 687 ([directoryPaddingGetResult, &aBaseDir, &aConn, aIncreaseSize, 688 aDecreaseSize]() -> Result<int64_t, nsresult> { 689 if (!directoryPaddingGetResult) { 690 // Fail to read padding size from the dir padding file, so try to 691 // restore. 692 693 // Not delete the temporary padding file here, because we're going 694 // to overwrite it below anyway. 695 QM_TRY(MOZ_TO_RESULT( 696 DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE))); 697 698 // We don't need to add the aIncreaseSize or aDecreaseSize here, 699 // because it's already encompassed within the database. 700 QM_TRY_RETURN(db::FindOverallPaddingSize(aConn)); 701 } 702 703 int64_t currentPaddingSize = directoryPaddingGetResult.value(); 704 bool shouldRevise = false; 705 706 if (aIncreaseSize > 0) { 707 if (INT64_MAX - currentPaddingSize < aDecreaseSize) { 708 shouldRevise = true; 709 } else { 710 currentPaddingSize += aIncreaseSize; 711 } 712 } 713 714 if (aDecreaseSize > 0) { 715 if (currentPaddingSize < aDecreaseSize) { 716 shouldRevise = true; 717 } else if (!shouldRevise) { 718 currentPaddingSize -= aDecreaseSize; 719 } 720 } 721 722 if (shouldRevise) { 723 // If somehow runing into this condition, the tracking padding size is 724 // incorrect. 725 // Delete padding file to indicate the padding size is incorrect for 726 // avoiding error happening in the following lines. 727 QM_TRY(MOZ_TO_RESULT( 728 DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE))); 729 730 QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn)); 731 732 // XXXtt: we should have an easy way to update (increase or 733 // recalulate) padding size in the QM. For now, only correct the 734 // padding size in padding file and make QM be able to get the correct 735 // size in the next QM initialization. We still want to catch this in 736 // the debug build. 737 MOZ_ASSERT(false, "The padding size is unsync with QM"); 738 } 739 740 #ifdef DEBUG 741 const int64_t lastPaddingSize = currentPaddingSize; 742 QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn)); 743 744 MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize == lastPaddingSize); 745 #endif // DEBUG 746 747 return currentPaddingSize; 748 }())); 749 750 MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0); 751 752 QM_TRY(MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE, 753 currentPaddingSize))); 754 755 return NS_OK; 756 } 757 758 nsresult DirectoryPaddingFinalizeWrite(nsIFile& aBaseDir) { 759 MOZ_DIAGNOSTIC_ASSERT( 760 DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE)); 761 762 QM_TRY_INSPECT( 763 const auto& file, 764 CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_TMP_FILE_NAME))); 765 766 QM_TRY(MOZ_TO_RESULT( 767 file->RenameTo(nullptr, nsLiteralString(PADDING_FILE_NAME)))); 768 769 return NS_OK; 770 } 771 772 Result<int64_t, nsresult> DirectoryPaddingRestore(nsIFile& aBaseDir, 773 mozIStorageConnection& aConn, 774 const bool aMustRestore) { 775 // The content of padding file is untrusted, so remove it here. 776 QM_TRY(MOZ_TO_RESULT( 777 DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE))); 778 779 QM_TRY_INSPECT(const int64_t& paddingSize, db::FindOverallPaddingSize(aConn)); 780 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); 781 782 QM_TRY(MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 783 paddingSize)), 784 (aMustRestore ? Err(tryTempError) 785 : Result<int64_t, nsresult>{paddingSize})); 786 787 QM_TRY(MOZ_TO_RESULT( 788 DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE))); 789 790 return paddingSize; 791 } 792 793 nsresult DirectoryPaddingDeleteFile(nsIFile& aBaseDir, 794 DirPaddingFile aPaddingFileType) { 795 QM_TRY_INSPECT( 796 const auto& file, 797 CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE 798 ? nsLiteralString(PADDING_TMP_FILE_NAME) 799 : nsLiteralString(PADDING_FILE_NAME))); 800 801 QM_TRY(QM_OR_ELSE_WARN_IF( 802 // Expression. 803 MOZ_TO_RESULT(file->Remove(/* recursive */ false)), 804 // Predicate. 805 IsFileNotFoundError, 806 // Fallback. 807 ErrToDefaultOk<>)); 808 809 return NS_OK; 810 } 811 812 } // namespace mozilla::dom::cache