tor-browser

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

FileSystemFileManager.cpp (11938B)


      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 "FileSystemFileManager.h"
      8 
      9 #include "FileSystemDataManager.h"
     10 #include "FileSystemHashSource.h"
     11 #include "FileSystemParentTypes.h"
     12 #include "mozilla/Assertions.h"
     13 #include "mozilla/Result.h"
     14 #include "mozilla/ResultVariant.h"
     15 #include "mozilla/dom/quota/QuotaManager.h"
     16 #include "mozilla/dom/quota/ResultExtensions.h"
     17 #include "nsCOMPtr.h"
     18 #include "nsHashKeys.h"
     19 #include "nsIFile.h"
     20 #include "nsIFileProtocolHandler.h"
     21 #include "nsIFileURL.h"
     22 #include "nsIURIMutator.h"
     23 #include "nsTHashMap.h"
     24 #include "nsXPCOM.h"
     25 
     26 namespace mozilla::dom::fs::data {
     27 
     28 namespace {
     29 
     30 constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns;
     31 
     32 Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination(
     33    const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
     34  MOZ_ASSERT(32u == aFileId.Value().Length());
     35 
     36  nsCOMPtr<nsIFile> destination;
     37 
     38  // nsIFile Clone is not a constant method
     39  QM_TRY(QM_TO_RESULT(aTopDirectory->Clone(getter_AddRefs(destination))));
     40 
     41  QM_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(aFileId));
     42 
     43  MOZ_ALWAYS_TRUE(IsAscii(encoded));
     44 
     45  nsString relativePath;
     46  relativePath.Append(Substring(encoded, 0, 2));
     47 
     48  QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(relativePath)));
     49 
     50  QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(encoded)));
     51 
     52  return destination;
     53 }
     54 
     55 Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFileImpl(
     56    const nsAString& aFilePath) {
     57  MOZ_ASSERT(!aFilePath.IsEmpty());
     58 
     59  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result,
     60                QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(aFilePath)));
     61 
     62  bool exists = true;
     63  QM_TRY(QM_TO_RESULT(result->Exists(&exists)));
     64 
     65  if (!exists) {
     66    QM_TRY(QM_TO_RESULT(result->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
     67 
     68    return result;
     69  }
     70 
     71  bool isDirectory = true;
     72  QM_TRY(QM_TO_RESULT(result->IsDirectory(&isDirectory)));
     73  QM_TRY(OkIf(!isDirectory), Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY)));
     74 
     75  return result;
     76 }
     77 
     78 Result<nsCOMPtr<nsIFile>, QMResult> GetFile(
     79    const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
     80  MOZ_ASSERT(!aFileId.IsEmpty());
     81 
     82  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
     83                GetFileDestination(aTopDirectory, aFileId));
     84 
     85  nsString desiredPath;
     86  QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
     87 
     88  QM_TRY_RETURN(QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(desiredPath)));
     89 }
     90 
     91 Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(
     92    const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
     93  MOZ_ASSERT(!aFileId.IsEmpty());
     94 
     95  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
     96                GetFileDestination(aTopDirectory, aFileId));
     97 
     98  nsString desiredPath;
     99  QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
    100 
    101  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath));
    102 
    103  return result;
    104 }
    105 
    106 nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) {
    107  // If we cannot tell whether the object is file or directory, or it is a
    108  // directory, it is abandoned as an unknown object. If an attempt is made to
    109  // create a new object with the same path on disk, we regenerate the FileId
    110  // until the collision is resolved.
    111 
    112  bool isFile = false;
    113  QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile)));
    114 
    115  QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY);
    116 
    117  QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false)));
    118 
    119  return NS_OK;
    120 }
    121 
    122 #ifdef DEBUG
    123 // Unused in release builds
    124 Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) {
    125  bool exists = false;
    126  QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists)));
    127 
    128  if (!exists) {
    129    return 0;
    130  }
    131 
    132  bool isFile = false;
    133  QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile)));
    134 
    135  // We never create directories with this path: this is an unknown object
    136  // and the file does not exist
    137  QM_TRY(OkIf(isFile), 0);
    138 
    139  QM_TRY_UNWRAP(Usage fileSize,
    140                QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize));
    141 
    142  return fileSize;
    143 }
    144 #endif
    145 
    146 }  // namespace
    147 
    148 Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory(
    149    const quota::OriginMetadata& aOriginMetadata) {
    150  MOZ_ASSERT(aOriginMetadata.mPersistenceType ==
    151             quota::PERSISTENCE_TYPE_DEFAULT);
    152 
    153  quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
    154  MOZ_ASSERT(quotaManager);
    155 
    156  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory,
    157                QM_TO_RESULT_TRANSFORM(
    158                    quotaManager->GetOriginDirectory(aOriginMetadata)));
    159 
    160  QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath(
    161      NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME))));
    162 
    163  return fileSystemDirectory;
    164 }
    165 
    166 nsresult EnsureFileSystemDirectory(
    167    const quota::OriginMetadata& aOriginMetadata) {
    168  quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
    169  MOZ_ASSERT(quotaManager);
    170 
    171  QM_TRY_INSPECT(
    172      const auto& fileSystemDirectory,
    173      quotaManager->GetOrCreateTemporaryOriginDirectory(aOriginMetadata));
    174 
    175  QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath(
    176      NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME))));
    177 
    178  bool exists = true;
    179  QM_TRY(QM_TO_RESULT(fileSystemDirectory->Exists(&exists)));
    180 
    181  if (!exists) {
    182    QM_TRY(QM_TO_RESULT(
    183        fileSystemDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
    184 
    185    return NS_OK;
    186  }
    187 
    188  bool isDirectory = true;
    189  QM_TRY(QM_TO_RESULT(fileSystemDirectory->IsDirectory(&isDirectory)));
    190  QM_TRY(OkIf(isDirectory), NS_ERROR_FILE_NOT_DIRECTORY);
    191 
    192  return NS_OK;
    193 }
    194 
    195 Result<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile(
    196    const quota::OriginMetadata& aOriginMetadata) {
    197  MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
    198 
    199  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
    200                GetFileSystemDirectory(aOriginMetadata));
    201 
    202  QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName)));
    203 
    204  return databaseFile;
    205 }
    206 
    207 /**
    208 * TODO: This is almost identical to the corresponding function of IndexedDB
    209 */
    210 Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL(
    211    const quota::OriginMetadata& aOriginMetadata,
    212    const int64_t aDirectoryLockId) {
    213  MOZ_ASSERT(aDirectoryLockId >= -1);
    214 
    215  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
    216                GetDatabaseFile(aOriginMetadata));
    217 
    218  QM_TRY_INSPECT(
    219      const auto& protocolHandler,
    220      QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED(
    221          nsCOMPtr<nsIProtocolHandler>, MOZ_SELECT_OVERLOAD(do_GetService),
    222          NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file")));
    223 
    224  QM_TRY_INSPECT(const auto& fileHandler,
    225                 QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED(
    226                     nsCOMPtr<nsIFileProtocolHandler>,
    227                     MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler)));
    228 
    229  QM_TRY_INSPECT(const auto& mutator,
    230                 QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    231                     nsCOMPtr<nsIURIMutator>, fileHandler, NewFileURIMutator,
    232                     databaseFile)));
    233 
    234  // aDirectoryLockId should only be -1 when we are called from
    235  // FileSystemQuotaClient::InitOrigin when the temporary storage hasn't been
    236  // initialized yet. At that time, the in-memory objects (e.g. OriginInfo) are
    237  // only being created so it doesn't make sense to tunnel quota information to
    238  // QuotaVFS to get corresponding QuotaObject instances for SQLite files.
    239  const nsCString directoryLockIdClause =
    240      "&directoryLockId="_ns + IntToCString(aDirectoryLockId);
    241 
    242  nsCOMPtr<nsIFileURL> result;
    243  QM_TRY(QM_TO_RESULT(NS_MutateURI(mutator)
    244                          .SetQuery("cache=private"_ns + directoryLockIdClause)
    245                          .Finalize(result)));
    246 
    247  return result;
    248 }
    249 
    250 /* static */
    251 Result<FileSystemFileManager, QMResult>
    252 FileSystemFileManager::CreateFileSystemFileManager(
    253    nsCOMPtr<nsIFile>&& topDirectory) {
    254  return FileSystemFileManager(std::move(topDirectory));
    255 }
    256 
    257 /* static */
    258 Result<UniquePtr<FileSystemFileManager>, QMResult>
    259 FileSystemFileManager::CreateFileSystemFileManager(
    260    const quota::OriginMetadata& aOriginMetadata) {
    261  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory,
    262                GetFileSystemDirectory(aOriginMetadata));
    263 
    264  return MakeUnique<FileSystemFileManager>(
    265      FileSystemFileManager(std::move(topDirectory)));
    266 }
    267 
    268 FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory)
    269    : mTopDirectory(std::move(aTopDirectory)) {}
    270 
    271 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile(
    272    const FileId& aFileId) const {
    273  return data::GetFile(mTopDirectory, aFileId);
    274 }
    275 
    276 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile(
    277    const FileId& aFileId) {
    278  return data::GetOrCreateFile(mTopDirectory, aFileId);
    279 }
    280 
    281 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::CreateFileFrom(
    282    const FileId& aDestinationFileId, const FileId& aSourceFileId) {
    283  MOZ_ASSERT(!aDestinationFileId.IsEmpty());
    284  MOZ_ASSERT(!aSourceFileId.IsEmpty());
    285 
    286  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> original, GetFile(aSourceFileId));
    287 
    288  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> destination,
    289                GetFileDestination(mTopDirectory, aDestinationFileId));
    290 
    291  nsAutoString leafName;
    292  QM_TRY(QM_TO_RESULT(destination->GetLeafName(leafName)));
    293 
    294  nsCOMPtr<nsIFile> destParent;
    295  QM_TRY(QM_TO_RESULT(destination->GetParent(getter_AddRefs(destParent))));
    296 
    297  QM_TRY(QM_TO_RESULT(original->CopyTo(destParent, leafName)));
    298 
    299 #ifdef DEBUG
    300  bool exists = false;
    301  QM_TRY(QM_TO_RESULT(destination->Exists(&exists)));
    302  MOZ_ASSERT(exists);
    303 
    304  int64_t destSize = 0;
    305  QM_TRY(QM_TO_RESULT(destination->GetFileSize(&destSize)));
    306 
    307  int64_t origSize = 0;
    308  QM_TRY(QM_TO_RESULT(original->GetFileSize(&origSize)));
    309 
    310  MOZ_ASSERT(destSize == origSize);
    311 #endif
    312 
    313  return destination;
    314 }
    315 
    316 Result<Usage, QMResult> FileSystemFileManager::RemoveFile(
    317    const FileId& aFileId) {
    318  MOZ_ASSERT(!aFileId.IsEmpty());
    319  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
    320                GetFileDestination(mTopDirectory, aFileId));
    321 
    322  bool exists = false;
    323  QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists)));
    324 
    325  if (!exists) {
    326    return 0;
    327  }
    328 
    329  bool isFile = false;
    330  QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile)));
    331 
    332  // We could handle this also as a nonexistent file.
    333  if (!isFile) {
    334    return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY));
    335  }
    336 
    337  Usage totalUsage = 0;
    338 #ifdef DEBUG
    339  QM_TRY_UNWRAP(totalUsage,
    340                QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize));
    341 #endif
    342 
    343  QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false)));
    344 
    345  return totalUsage;
    346 }
    347 
    348 Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles(
    349    const nsTArray<FileId>& aFileIds, nsTArray<FileId>& aFailedRemovals) {
    350  if (aFileIds.IsEmpty()) {
    351    return DebugOnly<Usage>(0);
    352  }
    353 
    354  CheckedInt64 totalUsage = 0;
    355  for (const auto& someId : aFileIds) {
    356    QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile,
    357                           GetFileDestination(mTopDirectory, someId));
    358    if (!maybeFile) {
    359      aFailedRemovals.AppendElement(someId);
    360      continue;
    361    }
    362    nsCOMPtr<nsIFile> fileObject = maybeFile.value();
    363 
    364 // Size recorded at close is checked to be equal to the sum of sizes on disk
    365 #ifdef DEBUG
    366    QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject));
    367    if (!fileSize) {
    368      aFailedRemovals.AppendElement(someId);
    369      continue;
    370    }
    371    totalUsage += fileSize.value();
    372 #endif
    373 
    374    QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok,
    375                           MOZ_TO_RESULT(RemoveFileObject(fileObject)));
    376    if (!ok) {
    377      aFailedRemovals.AppendElement(someId);
    378    }
    379  }
    380 
    381  MOZ_ASSERT(totalUsage.isValid());
    382 
    383  return DebugOnly<Usage>(totalUsage.value());
    384 }
    385 
    386 }  // namespace mozilla::dom::fs::data