tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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