tor-browser

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

FileSystemDatabaseManagerVersion002.cpp (32618B)


      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 "FileSystemDatabaseManagerVersion002.h"
      8 
      9 #include "ErrorList.h"
     10 #include "FileSystemContentTypeGuess.h"
     11 #include "FileSystemDataManager.h"
     12 #include "FileSystemFileManager.h"
     13 #include "FileSystemHashSource.h"
     14 #include "FileSystemHashStorageFunction.h"
     15 #include "FileSystemParentTypes.h"
     16 #include "ResultStatement.h"
     17 #include "StartedTransaction.h"
     18 #include "mozStorageHelper.h"
     19 #include "mozilla/dom/FileSystemDataManager.h"
     20 #include "mozilla/dom/FileSystemHandle.h"
     21 #include "mozilla/dom/FileSystemLog.h"
     22 #include "mozilla/dom/FileSystemTypes.h"
     23 #include "mozilla/dom/PFileSystemManager.h"
     24 #include "mozilla/dom/QMResult.h"
     25 #include "mozilla/dom/quota/Client.h"
     26 #include "mozilla/dom/quota/QuotaCommon.h"
     27 #include "mozilla/dom/quota/QuotaManager.h"
     28 #include "mozilla/dom/quota/QuotaObject.h"
     29 #include "mozilla/dom/quota/ResultExtensions.h"
     30 
     31 namespace mozilla::dom::fs::data {
     32 
     33 namespace {
     34 
     35 Result<FileId, QMResult> GetFileId002(const FileSystemConnection& aConnection,
     36                                      const EntryId& aEntryId) {
     37  const nsLiteralCString fileIdQuery =
     38      "SELECT fileId FROM MainFiles WHERE handle = :entryId ;"_ns;
     39 
     40  QM_TRY_UNWRAP(ResultStatement stmt,
     41                ResultStatement::Create(aConnection, fileIdQuery));
     42  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
     43  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
     44 
     45  if (!moreResults) {
     46    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
     47  }
     48 
     49  QM_TRY_INSPECT(const FileId& fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
     50 
     51  return fileId;
     52 }
     53 
     54 Result<bool, QMResult> DoesFileIdExist(const FileSystemConnection& aConnection,
     55                                       const FileId& aFileId) {
     56  MOZ_ASSERT(!aFileId.IsEmpty());
     57 
     58  const nsLiteralCString existsQuery =
     59      "SELECT EXISTS "
     60      "(SELECT 1 FROM FileIds WHERE fileId = :handle ) "
     61      ";"_ns;
     62 
     63  QM_TRY_RETURN(
     64      ApplyEntryExistsQuery(aConnection, existsQuery, aFileId.Value()));
     65 }
     66 
     67 nsresult RehashFile(const FileSystemConnection& aConnection,
     68                    const EntryId& aEntryId,
     69                    const FileSystemChildMetadata& aNewDesignation,
     70                    const ContentType& aNewType) {
     71  QM_TRY_INSPECT(const EntryId& newId,
     72                 FileSystemHashSource::GenerateHash(
     73                     aNewDesignation.parentId(), aNewDesignation.childName()));
     74 
     75  // The destination should be empty at this point: either we exited because
     76  // overwrite was not desired, or the existing content was removed.
     77  const nsLiteralCString insertNewEntryQuery =
     78      "INSERT INTO Entries ( handle, parent ) "
     79      "VALUES ( :newId, :newParent ) "
     80      ";"_ns;
     81 
     82  const nsLiteralCString insertNewFileAndTypeQuery =
     83      "INSERT INTO Files ( handle, type, name ) "
     84      "VALUES ( :newId, :type, :newName ) "
     85      ";"_ns;
     86 
     87  const nsLiteralCString insertNewFileKeepTypeQuery =
     88      "INSERT INTO Files ( handle, type, name ) "
     89      "SELECT :newId, type, :newName FROM Files "
     90      "WHERE handle = :oldId ;"_ns;
     91 
     92  const auto& insertNewFileQuery = aNewType.IsVoid()
     93                                       ? insertNewFileKeepTypeQuery
     94                                       : insertNewFileAndTypeQuery;
     95 
     96  const nsLiteralCString updateFileMappingsQuery =
     97      "UPDATE FileIds SET handle = :newId WHERE handle = :handle ;"_ns;
     98 
     99  const nsLiteralCString updateMainFilesQuery =
    100      "UPDATE MainFiles SET handle = :newId WHERE handle = :handle ;"_ns;
    101 
    102  const nsLiteralCString cleanupOldEntryQuery =
    103      "DELETE FROM Entries WHERE handle = :handle ;"_ns;
    104 
    105  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
    106 
    107  {
    108    QM_TRY_UNWRAP(ResultStatement stmt,
    109                  ResultStatement::Create(aConnection, insertNewEntryQuery));
    110    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
    111    QM_TRY(QM_TO_RESULT(
    112        stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId())));
    113    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    114  }
    115 
    116  {
    117    QM_TRY_UNWRAP(ResultStatement stmt,
    118                  ResultStatement::Create(aConnection, insertNewFileQuery));
    119    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
    120    if (aNewType.IsVoid()) {
    121      QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("oldId"_ns, aEntryId)));
    122    } else {
    123      QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType)));
    124    }
    125    QM_TRY(QM_TO_RESULT(
    126        stmt.BindNameByName("newName"_ns, aNewDesignation.childName())));
    127    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    128  }
    129 
    130  {
    131    QM_TRY_UNWRAP(
    132        ResultStatement stmt,
    133        ResultStatement::Create(aConnection, updateFileMappingsQuery));
    134    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
    135    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    136    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    137  }
    138 
    139  {
    140    QM_TRY_UNWRAP(ResultStatement stmt,
    141                  ResultStatement::Create(aConnection, updateMainFilesQuery));
    142    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
    143    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    144    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    145  }
    146 
    147  {
    148    QM_TRY_UNWRAP(ResultStatement stmt,
    149                  ResultStatement::Create(aConnection, cleanupOldEntryQuery));
    150    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    151    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    152  }
    153 
    154  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    155 
    156  return NS_OK;
    157 }
    158 
    159 nsresult RehashDirectory(const FileSystemConnection& aConnection,
    160                         const EntryId& aEntryId,
    161                         const FileSystemChildMetadata& aNewDesignation) {
    162  // This name won't match up with the entryId for the old path but
    163  // it will be removed at the end
    164  const nsLiteralCString updateNameQuery =
    165      "UPDATE Directories SET name = :newName WHERE handle = :handle "
    166      "; "_ns;
    167 
    168  const nsLiteralCString calculateHashesQuery =
    169      "CREATE TEMPORARY TABLE ParentChildHash AS "
    170      "WITH RECURSIVE "
    171      "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( "
    172      "SELECT 0, isFile, handle, parent, name, hashEntry( :newParent, name ) "
    173      "FROM EntryNames WHERE handle = :handle UNION SELECT "
    174      "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, "
    175      "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) "
    176      "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) "
    177      "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap "
    178      ";"_ns;
    179 
    180  const nsLiteralCString createIndexByDepthQuery =
    181      "CREATE INDEX indexOnDepth ON ParentChildHash ( depth ); "_ns;
    182 
    183  // To avoid constraint violation, we insert new entries under the old parent.
    184  // The destination should be empty at this point: either we exited because
    185  // overwrite was not desired, or the existing content was removed.
    186  const nsLiteralCString insertNewEntriesQuery =
    187      "INSERT INTO Entries ( handle, parent ) "
    188      "SELECT hash, :parent FROM ParentChildHash "
    189      ";"_ns;
    190 
    191  const nsLiteralCString insertNewDirectoriesQuery =
    192      "INSERT INTO Directories ( handle, name ) "
    193      "SELECT hash, name FROM ParentChildHash WHERE isFile = 0 "
    194      "ORDER BY depth "
    195      ";"_ns;
    196 
    197  const nsLiteralCString insertNewFilesQuery =
    198      "INSERT INTO Files ( handle, type, name ) "
    199      "SELECT ParentChildHash.hash, Files.type, ParentChildHash.name "
    200      "FROM ParentChildHash INNER JOIN Files USING (handle) "
    201      "WHERE ParentChildHash.isFile = 1 "
    202      ";"_ns;
    203 
    204  const nsLiteralCString updateFileMappingsQuery =
    205      "UPDATE FileIds "
    206      "SET handle = CASE WHEN replacement.isMain IS NULL THEN NULL "
    207      "ELSE replacement.hash END "
    208      "FROM ( SELECT ParentChildHash.handle AS handle, "
    209      "ParentChildHash.hash AS hash, "
    210      "MainFiles.handle AS isMain "
    211      "FROM ParentChildHash LEFT JOIN MainFiles "
    212      "ON ParentChildHash.handle = MainFiles.handle ) AS replacement "
    213      "WHERE FileIds.handle = replacement.handle "
    214      ";"_ns;
    215 
    216  const nsLiteralCString updateMainFilesQuery =
    217      "UPDATE MainFiles SET handle = hash "
    218      "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement "
    219      "WHERE MainFiles.handle = replacement.handle "
    220      ";"_ns;
    221 
    222  // Now fix the parents
    223  const nsLiteralCString updateEntryMappingsQuery =
    224      "UPDATE Entries SET parent = hash "
    225      "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth "
    226      "FROM ParentChildHash AS Lhs "
    227      "INNER JOIN ParentChildHash AS Rhs "
    228      "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement "
    229      "WHERE Entries.handle = replacement.handle "
    230      ";"_ns;
    231 
    232  const nsLiteralCString cleanupOldEntriesQuery =
    233      "DELETE FROM Entries WHERE handle = :handle "
    234      ";"_ns;
    235 
    236  // Index is automatically deleted
    237  const nsLiteralCString cleanupTemporaries =
    238      "DROP TABLE ParentChildHash "
    239      ";"_ns;
    240 
    241  nsCOMPtr<mozIStorageFunction> rehashFunction =
    242      new data::FileSystemHashStorageFunction();
    243  QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns,
    244                                                   /* number of arguments */ 2,
    245                                                   rehashFunction)));
    246  auto finallyRemoveFunction = MakeScopeExit([&aConnection]() {
    247    QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns)));
    248  });
    249 
    250  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
    251 
    252  {
    253    QM_TRY_UNWRAP(ResultStatement stmt,
    254                  ResultStatement::Create(aConnection, updateNameQuery));
    255    QM_TRY(QM_TO_RESULT(
    256        stmt.BindNameByName("newName"_ns, aNewDesignation.childName())));
    257    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    258    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    259  }
    260 
    261  {
    262    QM_TRY_UNWRAP(ResultStatement stmt,
    263                  ResultStatement::Create(aConnection, calculateHashesQuery));
    264    QM_TRY(QM_TO_RESULT(
    265        stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId())));
    266    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    267    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    268  }
    269 
    270  QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery)));
    271 
    272  {
    273    QM_TRY_UNWRAP(ResultStatement stmt,
    274                  ResultStatement::Create(aConnection, insertNewEntriesQuery));
    275    QM_TRY(QM_TO_RESULT(
    276        stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId())));
    277    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    278  }
    279 
    280  {
    281    QM_TRY_UNWRAP(
    282        ResultStatement stmt,
    283        ResultStatement::Create(aConnection, insertNewDirectoriesQuery));
    284    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    285  }
    286 
    287  {
    288    QM_TRY_UNWRAP(ResultStatement stmt,
    289                  ResultStatement::Create(aConnection, insertNewFilesQuery));
    290    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    291  }
    292 
    293  {
    294    QM_TRY_UNWRAP(
    295        ResultStatement stmt,
    296        ResultStatement::Create(aConnection, updateFileMappingsQuery));
    297    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    298  }
    299 
    300  {
    301    QM_TRY_UNWRAP(ResultStatement stmt,
    302                  ResultStatement::Create(aConnection, updateMainFilesQuery));
    303    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    304  }
    305 
    306  {
    307    QM_TRY_UNWRAP(
    308        ResultStatement stmt,
    309        ResultStatement::Create(aConnection, updateEntryMappingsQuery));
    310    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    311  }
    312 
    313  {
    314    QM_TRY_UNWRAP(ResultStatement stmt,
    315                  ResultStatement::Create(aConnection, cleanupOldEntriesQuery));
    316    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    317    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    318  }
    319 
    320  {
    321    QM_TRY_UNWRAP(ResultStatement stmt,
    322                  ResultStatement::Create(aConnection, cleanupTemporaries));
    323    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    324  }
    325 
    326  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    327 
    328  return NS_OK;
    329 }
    330 
    331 /**
    332 * @brief Each entryId is interpreted as a large integer, which is increased
    333 * until an unused value is found. This process is in principle infallible.
    334 * The files associated with a given path will form a cluster next to the
    335 * entryId which could be used for recovery because our hash function is
    336 * expected to distribute all clusters far from each other.
    337 */
    338 Result<FileId, QMResult> GetNextFreeFileId(
    339    const FileSystemConnection& aConnection,
    340    const FileSystemFileManager& aFileManager, const EntryId& aEntryId) {
    341  MOZ_ASSERT(32u == aEntryId.Length());
    342 
    343  auto DoesExist = [&aConnection, &aFileManager](
    344                       const FileId& aId) -> Result<bool, QMResult> {
    345    QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& diskFile,
    346                   aFileManager.GetFile(aId));
    347 
    348    bool result = true;
    349    QM_TRY(QM_TO_RESULT(diskFile->Exists(&result)));
    350    if (result) {
    351      return true;
    352    }
    353 
    354    QM_TRY_RETURN(DoesFileIdExist(aConnection, aId));
    355  };
    356 
    357  auto Next = [](FileId& aId) {
    358    // Using a larger integer would make fileIds depend on platform endianness.
    359    using IntegerType = uint8_t;
    360    constexpr int32_t bufferSize = 32 / sizeof(IntegerType);
    361    using IdBuffer = std::array<IntegerType, bufferSize>;
    362 
    363    auto Increase = [](IdBuffer& aIn) {
    364      for (int i = 0; i < bufferSize; ++i) {
    365        if (1u + aIn[i] != 0u) {
    366          ++aIn[i];
    367          return;
    368        }
    369        aIn[i] = 0u;
    370      }
    371    };
    372 
    373    DebugOnly<nsCString> original = aId.Value();
    374    Increase(*reinterpret_cast<IdBuffer*>(aId.mValue.BeginWriting()));
    375    MOZ_ASSERT(!aId.Value().Equals(original));
    376  };
    377 
    378  FileId id = FileId(aEntryId);
    379 
    380  while (true) {
    381    QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeExists, DoesExist(id));
    382    if (maybeExists.isSome() && !maybeExists.value()) {
    383      return id;
    384    }
    385 
    386    Next(id);
    387  }
    388 }
    389 
    390 Result<FileId, QMResult> AddNewFileId(const FileSystemConnection& aConnection,
    391                                      const FileSystemFileManager& aFileManager,
    392                                      const EntryId& aEntryId) {
    393  QM_TRY_INSPECT(const FileId& nextFreeId,
    394                 GetNextFreeFileId(aConnection, aFileManager, aEntryId));
    395 
    396  const nsLiteralCString insertNewFileIdQuery =
    397      "INSERT INTO FileIds ( fileId, handle ) "
    398      "VALUES ( :fileId, :entryId ) "
    399      "; "_ns;
    400 
    401  QM_TRY_UNWRAP(ResultStatement stmt,
    402                ResultStatement::Create(aConnection, insertNewFileIdQuery));
    403  QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, nextFreeId)));
    404  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
    405 
    406  QM_TRY(QM_TO_RESULT(stmt.Execute()));
    407 
    408  return nextFreeId;
    409 }
    410 
    411 /**
    412 * @brief Get recorded usage or zero if nothing was ever written to the file.
    413 * Removing files is only allowed when there is no lock on the file, and their
    414 * usage is either correctly recorded in the database during unlock, or nothing,
    415 * or they remain in tracked state and the quota manager assumes their usage to
    416 * be equal to the latest recorded value. In all cases, the latest recorded
    417 * value (or nothing) is the correct amount of quota to be released.
    418 */
    419 Result<Usage, QMResult> GetKnownUsage(const FileSystemConnection& aConnection,
    420                                      const FileId& aFileId) {
    421  const nsLiteralCString trackedUsageQuery =
    422      "SELECT usage FROM Usages WHERE handle = :handle ;"_ns;
    423 
    424  QM_TRY_UNWRAP(ResultStatement stmt,
    425                ResultStatement::Create(aConnection, trackedUsageQuery));
    426  QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
    427 
    428  QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
    429  if (!moreResults) {
    430    return 0;
    431  }
    432 
    433  QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
    434 }
    435 
    436 }  // namespace
    437 
    438 /* static */
    439 nsresult FileSystemDatabaseManagerVersion002::RescanTrackedUsages(
    440    const FileSystemConnection& aConnection,
    441    const quota::OriginMetadata& aOriginMetadata) {
    442  return FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
    443      aConnection, aOriginMetadata);
    444 }
    445 
    446 /* static */
    447 Result<Usage, QMResult> FileSystemDatabaseManagerVersion002::GetFileUsage(
    448    const FileSystemConnection& aConnection) {
    449  return FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection);
    450 }
    451 
    452 nsresult FileSystemDatabaseManagerVersion002::GetFile(
    453    const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode,
    454    ContentType& aType, TimeStamp& lastModifiedMilliSeconds,
    455    nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const {
    456  MOZ_ASSERT(!aFileId.IsEmpty());
    457 
    458  const FileSystemEntryPair endPoints(mRootEntry, aEntryId);
    459  QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
    460  if (aPath.IsEmpty()) {
    461    return NS_ERROR_DOM_NOT_FOUND_ERR;
    462  }
    463 
    464  QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType)));
    465 
    466  if (aMode == FileMode::SHARED_FROM_COPY) {
    467    QM_WARNONLY_TRY_UNWRAP(Maybe<FileId> mainFileId, GetFileId(aEntryId));
    468    if (mainFileId) {
    469      QM_TRY_UNWRAP(aFile,
    470                    mFileManager->CreateFileFrom(aFileId, mainFileId.value()));
    471      int64_t fileSize = 0;
    472      QM_TRY(QM_TO_RESULT(aFile->GetFileSize(&fileSize)));
    473      UpdateCachedQuotaUsage(aFileId, 0, fileSize);
    474    } else {
    475      // LockShared/EnsureTemporaryFileId has provided a brand new fileId.
    476      QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
    477    }
    478  } else {
    479    MOZ_ASSERT(aMode == FileMode::EXCLUSIVE ||
    480               aMode == FileMode::SHARED_FROM_EMPTY);
    481 
    482    QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
    483  }
    484 
    485  PRTime lastModTime = 0;
    486  QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime)));
    487  lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime);
    488 
    489  aPath.Reverse();
    490 
    491  return NS_OK;
    492 }
    493 
    494 Result<Ok, QMResult>
    495 FileSystemDatabaseManagerVersion002::DeprecateSharedLocksForDirectory(
    496    const FileSystemChildMetadata& aNewDesignation) {
    497  // We will deprecate all shared locks whose entries (locators)
    498  // are about to get overwritten at the destination.
    499  //
    500  // If a non-empty directory gets overwritten with a different
    501  // directory layout, closing a WritableFileStream for an invalid path
    502  // will quietly turn into abort.
    503  //
    504  // If a deprecated handle is explicitly closed and the associated
    505  // path is valid, it will overwrite the file at the destination,
    506  // unless the destination is exclusively locked.
    507  //
    508  // This behavior allows one to use WritableFileStreams for
    509  // switching between contexts with last-writer-wins semantics.
    510  //
    511  // To match stricter application requirements, WebLocks can be used
    512  // to disallow such use as necessary.
    513  QM_TRY_INSPECT(const auto& destinationId, GetEntryId(aNewDesignation));
    514  QM_TRY_INSPECT(const auto& descendants,
    515                 FindFileEntriesUnderDirectory(destinationId));
    516  for (const auto& entryFile : descendants) {
    517    mDataManager->DeprecateSharedLocks(entryFile.first, entryFile.second);
    518  }
    519 
    520  return Ok{};
    521 }
    522 
    523 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::RenameEntry(
    524    const FileSystemEntryMetadata& aHandle, const Name& aNewName) {
    525  MOZ_ASSERT(!aNewName.IsEmpty());
    526 
    527  const auto& entryId = aHandle.entryId();
    528  MOZ_ASSERT(!entryId.IsEmpty());
    529 
    530  // Can't rename root
    531  if (mRootEntry == entryId) {
    532    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    533  }
    534 
    535  // Verify the source exists
    536  QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
    537                Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
    538 
    539  // Are we actually renaming?
    540  if (aHandle.entryName() == aNewName) {
    541    return entryId;
    542  }
    543 
    544  QM_TRY_UNWRAP(EntryId parentId, FindParent(mConnection, entryId));
    545  FileSystemChildMetadata newDesignation(parentId, aNewName);
    546 
    547  if (isFile) {
    548    // This will fail if there there is a locked file at the destination.
    549    QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle,
    550                                           aNewName, isFile)));
    551 
    552    const ContentType type = DetermineContentType(aNewName);
    553    QM_TRY(
    554        QM_TO_RESULT(RehashFile(mConnection, entryId, newDesignation, type)));
    555  } else {
    556    QM_TRY(DeprecateSharedLocksForDirectory(newDesignation));
    557 
    558    // We remove all not deprecated items from the destination.
    559    // This will fail if the destination contains locked files.
    560    QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle,
    561                                           aNewName, isFile)));
    562 
    563    QM_TRY(QM_TO_RESULT(RehashDirectory(mConnection, entryId, newDesignation)));
    564  }
    565 
    566  QM_TRY_UNWRAP(DebugOnly<EntryId> dbId,
    567                FindEntryId(mConnection, newDesignation, isFile));
    568  QM_TRY_UNWRAP(EntryId generated,
    569                FileSystemHashSource::GenerateHash(parentId, aNewName));
    570  MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated));
    571 
    572  return generated;
    573 }
    574 
    575 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::MoveEntry(
    576    const FileSystemEntryMetadata& aHandle,
    577    const FileSystemChildMetadata& aNewDesignation) {
    578  MOZ_ASSERT(!aHandle.entryId().IsEmpty());
    579 
    580  const auto& entryId = aHandle.entryId();
    581 
    582  if (mRootEntry == entryId) {
    583    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    584  }
    585 
    586  // Verify the source exists
    587  QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
    588                Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
    589 
    590  // If the rename doesn't change the name or directory, just return success.
    591  // XXX Needs to be added to the spec
    592  QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame,
    593                         IsSame(mConnection, aHandle, aNewDesignation, isFile));
    594  if (maybeSame && maybeSame.value()) {
    595    return entryId;
    596  }
    597 
    598  if (isFile) {
    599    QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle,
    600                                         aNewDesignation, isFile)));
    601 
    602    const ContentType type = DetermineContentType(aNewDesignation.childName());
    603    QM_TRY(
    604        QM_TO_RESULT(RehashFile(mConnection, entryId, aNewDesignation, type)));
    605  } else {
    606    QM_TRY(DeprecateSharedLocksForDirectory(aNewDesignation));
    607 
    608    QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle,
    609                                         aNewDesignation, isFile)));
    610 
    611    QM_TRY(
    612        QM_TO_RESULT(RehashDirectory(mConnection, entryId, aNewDesignation)));
    613  }
    614 
    615  QM_TRY_UNWRAP(DebugOnly<EntryId> dbId,
    616                FindEntryId(mConnection, aNewDesignation, isFile));
    617  QM_TRY_UNWRAP(EntryId generated,
    618                FileSystemHashSource::GenerateHash(
    619                    aNewDesignation.parentId(), aNewDesignation.childName()));
    620  MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated));
    621 
    622  return generated;
    623 }
    624 
    625 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId(
    626    const FileSystemChildMetadata& aHandle) const {
    627  return fs::data::GetEntryHandle(aHandle);
    628 }
    629 
    630 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId(
    631    const FileId& aFileId) const {
    632  const nsLiteralCString getEntryIdQuery =
    633      "SELECT handle FROM FileIds WHERE fileId = :fileId ;"_ns;
    634 
    635  QM_TRY_UNWRAP(ResultStatement stmt,
    636                ResultStatement::Create(mConnection, getEntryIdQuery));
    637  QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId)));
    638  QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep());
    639 
    640  if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) {
    641    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    642  }
    643 
    644  QM_TRY_RETURN(stmt.GetEntryIdByColumn(/* Column */ 0u));
    645 }
    646 
    647 Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::EnsureFileId(
    648    const EntryId& aEntryId) {
    649  QM_TRY_UNWRAP(const bool exists, DoesFileExist(aEntryId));
    650  if (!exists) {
    651    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    652  }
    653 
    654  QM_TRY_UNWRAP(Maybe<FileId> maybeMainFileId,
    655                QM_OR_ELSE_LOG_VERBOSE_IF(
    656                    // Expression.
    657                    GetFileId(aEntryId).map([](auto mainFileId) {
    658                      return Some(std::move(mainFileId));
    659                    }),
    660                    // Predicate.
    661                    IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
    662                    // Fallback.
    663                    ([](const auto&) -> Result<Maybe<FileId>, QMResult> {
    664                      return Maybe<FileId>{};
    665                    })));
    666 
    667  if (maybeMainFileId) {
    668    return *maybeMainFileId;
    669  }
    670 
    671  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
    672 
    673  QM_TRY_INSPECT(const FileId& fileId,
    674                 AddNewFileId(mConnection, *mFileManager, aEntryId));
    675 
    676  QM_TRY(QM_TO_RESULT(MergeFileId(aEntryId, fileId, /* aAbort */ false)));
    677 
    678  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    679 
    680  return fileId;
    681 }
    682 
    683 Result<FileId, QMResult>
    684 FileSystemDatabaseManagerVersion002::EnsureTemporaryFileId(
    685    const EntryId& aEntryId) {
    686  QM_TRY_UNWRAP(const bool exists, DoesFileExist(aEntryId));
    687  if (!exists) {
    688    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    689  }
    690 
    691  QM_TRY_RETURN(AddNewFileId(mConnection, *mFileManager, aEntryId));
    692 }
    693 
    694 Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::GetFileId(
    695    const EntryId& aEntryId) const {
    696  MOZ_ASSERT(mConnection);
    697  return data::GetFileId002(mConnection, aEntryId);
    698 }
    699 
    700 nsresult FileSystemDatabaseManagerVersion002::MergeFileId(
    701    const EntryId& aEntryId, const FileId& aFileId, bool aAbort) {
    702  MOZ_ASSERT(mConnection);
    703 
    704  auto doCleanUp = [this](const FileId& aCleanable) -> nsresult {
    705    // We need to clean up the old main file.
    706    QM_TRY_UNWRAP(Usage usage,
    707                  GetKnownUsage(mConnection, aCleanable).mapErr(toNSResult));
    708 
    709    QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> removedUsage,
    710                           mFileManager->RemoveFile(aCleanable));
    711 
    712    if (removedUsage) {
    713      // Removal of file data was ok, update the related fileId and usage
    714      QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(aCleanable)));
    715 
    716      if (usage > 0) {  // Performance!
    717        DecreaseCachedQuotaUsage(usage);
    718      }
    719 
    720      // We only check the most common case. This can fail spuriously if an
    721      // external application writes to the file, or OS reports zero size due to
    722      // corruption.
    723      MOZ_ASSERT_IF(0 == mFilesOfUnknownUsage, usage == removedUsage.value());
    724 
    725      return NS_OK;
    726    }
    727 
    728    // Removal failed
    729    const nsLiteralCString forgetCleanable =
    730        "UPDATE FileIds SET handle = NULL WHERE fileId = :fileId ; "_ns;
    731 
    732    QM_TRY_UNWRAP(ResultStatement stmt,
    733                  ResultStatement::Create(mConnection, forgetCleanable)
    734                      .mapErr(toNSResult));
    735    QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aCleanable)));
    736    QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
    737 
    738    TryRemoveDuringIdleMaintenance({aCleanable});
    739 
    740    return NS_OK;
    741  };
    742 
    743  if (aAbort) {
    744    QM_TRY(MOZ_TO_RESULT(doCleanUp(aFileId)));
    745 
    746    return NS_OK;
    747  }
    748 
    749  QM_TRY_UNWRAP(
    750      Maybe<FileId> maybeOldFileId,
    751      QM_OR_ELSE_LOG_VERBOSE_IF(
    752          // Expression.
    753          GetFileId(aEntryId)
    754              .map([](auto oldFileId) { return Some(std::move(oldFileId)); })
    755              .mapErr(toNSResult),
    756          // Predicate.
    757          IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
    758          // Fallback.
    759          ErrToDefaultOk<Maybe<FileId>>));
    760 
    761  if (maybeOldFileId && *maybeOldFileId == aFileId) {
    762    return NS_OK;  // Nothing to do
    763  }
    764 
    765  // Main file changed
    766  const nsLiteralCString flagAsMainFileQuery =
    767      "INSERT INTO MainFiles ( handle, fileId ) "
    768      "VALUES ( :entryId, :fileId ) "
    769      "ON CONFLICT (handle) "
    770      "DO UPDATE SET fileId = excluded.fileId "
    771      "; "_ns;
    772 
    773  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
    774 
    775  QM_TRY_UNWRAP(ResultStatement stmt,
    776                ResultStatement::Create(mConnection, flagAsMainFileQuery)
    777                    .mapErr(toNSResult));
    778  QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
    779  QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId)));
    780 
    781  QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
    782 
    783  if (!maybeOldFileId) {
    784    // We successfully added a new main file and there is nothing to clean up.
    785    QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
    786 
    787    return NS_OK;
    788  }
    789 
    790  MOZ_ASSERT(maybeOldFileId);
    791  MOZ_ASSERT(*maybeOldFileId != aFileId);
    792 
    793  QM_TRY(MOZ_TO_RESULT(doCleanUp(*maybeOldFileId)));
    794 
    795  // If the old fileId and usage were not deleted, main file update fails.
    796  QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
    797 
    798  return NS_OK;
    799 }
    800 
    801 Result<bool, QMResult> FileSystemDatabaseManagerVersion002::DoesFileIdExist(
    802    const FileId& aFileId) const {
    803  QM_TRY_RETURN(data::DoesFileIdExist(mConnection, aFileId));
    804 }
    805 
    806 nsresult FileSystemDatabaseManagerVersion002::RemoveFileId(
    807    const FileId& aFileId) {
    808  const nsLiteralCString removeFileIdQuery =
    809      "DELETE FROM FileIds "
    810      "WHERE fileId = :fileId "
    811      ";"_ns;
    812 
    813  QM_TRY_UNWRAP(ResultStatement stmt,
    814                ResultStatement::Create(mConnection, removeFileIdQuery)
    815                    .mapErr(toNSResult));
    816 
    817  QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("fileId"_ns, aFileId.Value())));
    818 
    819  return stmt.Execute();
    820 }
    821 
    822 /**
    823 * Technically, this function may return exclusively locked files.
    824 * It is meant to assist move and remove operations after it has been
    825 * already checked that the directory does not contain exclusively locked files.
    826 * Perhaps it these checks could be unified one day?
    827 * Until then, since this is not expected in those use cases, there is a
    828 * debug assert to alert about the presence of exclusive locks.
    829 */
    830 Result<std::pair<nsTArray<FileId>, Usage>, QMResult>
    831 FileSystemDatabaseManagerVersion002::FindFilesWithoutDeprecatedLocksUnderEntry(
    832    const EntryId& aEntryId) const {
    833  const nsLiteralCString descendantsQuery =
    834      "WITH RECURSIVE traceChildren(handle, parent) AS ( "
    835      "SELECT handle, parent FROM Entries WHERE handle = :handle "
    836      "UNION "
    837      "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
    838      "WHERE traceChildren.handle = Entries.parent ) "
    839      "SELECT FileIds.handle, FileIds.fileId, Usages.usage "
    840      "FROM traceChildren INNER JOIN FileIds USING (handle) "
    841      "INNER JOIN Usages ON Usages.handle = FileIds.fileId "
    842      ";"_ns;
    843 
    844  nsTArray<FileId> descendants;
    845  Usage usage{0};
    846  {
    847    QM_TRY_UNWRAP(ResultStatement stmt,
    848                  ResultStatement::Create(mConnection, descendantsQuery));
    849    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    850 
    851    while (true) {
    852      QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep());
    853      if (!moreResults) {
    854        break;
    855      }
    856 
    857      QM_TRY_INSPECT(const EntryId& entryId,
    858                     stmt.GetEntryIdByColumn(/* Column */ 0u));
    859      QM_TRY_INSPECT(const FileId& fileId,
    860                     stmt.GetFileIdByColumn(/* Column */ 1u));
    861      if (!mDataManager->IsLockedWithDeprecatedSharedLock(entryId, fileId)) {
    862        QM_TRY_INSPECT(const FileId& fileId,
    863                       stmt.GetFileIdByColumn(/* Column */ 1u));
    864        descendants.AppendElement(fileId);
    865 
    866        QM_TRY_INSPECT(const Usage& fileUsage,
    867                       stmt.GetUsageByColumn(/* Column */ 2u));
    868        usage += fileUsage;
    869      }
    870    }
    871  }
    872 
    873  return std::make_pair(std::move(descendants), usage);
    874 }
    875 
    876 Result<nsTArray<std::pair<EntryId, FileId>>, QMResult>
    877 FileSystemDatabaseManagerVersion002::FindFileEntriesUnderDirectory(
    878    const EntryId& aEntryId) const {
    879  const nsLiteralCString descendantsQuery =
    880      "WITH RECURSIVE traceChildren(handle, parent) AS ( "
    881      "SELECT handle, parent FROM Entries WHERE handle = :handle "
    882      "UNION "
    883      "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
    884      "WHERE traceChildren.handle = Entries.parent ) "
    885      "SELECT FileIds.handle, FileIds.fileId "
    886      "FROM traceChildren INNER JOIN FileIds USING(handle) "
    887      ";"_ns;
    888 
    889  nsTArray<std::pair<EntryId, FileId>> descendants;
    890  {
    891    QM_TRY_UNWRAP(ResultStatement stmt,
    892                  ResultStatement::Create(mConnection, descendantsQuery));
    893    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    894 
    895    while (true) {
    896      QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep());
    897      if (!moreResults) {
    898        break;
    899      }
    900 
    901      QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
    902      QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 1u));
    903      descendants.AppendElement(
    904          std::make_pair(std::move(entryId), std::move(fileId)));
    905    }
    906  }
    907 
    908  return descendants;
    909 }
    910 
    911 }  // namespace mozilla::dom::fs::data