Files
tubestation/dom/fs/parent/datamodel/FileSystemFileManager.cpp
Jan Varga 7783acd7c8 Bug 1866402 - Make it possible to initialize temporary origins without ensuring origin directories; r=dom-storage-reviewers,jari
LSNG already uses some QuotaManager APIs to achieve that origin directories are
not created if they don't exist during datastore preparation, but the feature
is not easy to use and it's also not generalized enough for use in other quota
clients. Besides that, the way how it's currently done in LSNG complicates
removal of QuotaManager::EnsureTemporaryOriginIsInitializedInternal calls from
LSNG. This patch is about generalizing of the feature, making it available to
all quota clients.

Differential Revision: https://phabricator.services.mozilla.com/D195551
2024-10-12 20:58:36 +00:00

393 lines
12 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FileSystemFileManager.h"
#include "FileSystemDataManager.h"
#include "FileSystemHashSource.h"
#include "FileSystemParentTypes.h"
#include "mozilla/Assertions.h"
#include "mozilla/NotNull.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsIFile.h"
#include "nsIFileProtocolHandler.h"
#include "nsIFileURL.h"
#include "nsIURIMutator.h"
#include "nsTHashMap.h"
#include "nsXPCOM.h"
namespace mozilla::dom::fs::data {
namespace {
constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns;
Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination(
const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
MOZ_ASSERT(32u == aFileId.Value().Length());
nsCOMPtr<nsIFile> destination;
// nsIFile Clone is not a constant method
QM_TRY(QM_TO_RESULT(aTopDirectory->Clone(getter_AddRefs(destination))));
QM_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(aFileId));
MOZ_ALWAYS_TRUE(IsAscii(encoded));
nsString relativePath;
relativePath.Append(Substring(encoded, 0, 2));
QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(relativePath)));
QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(encoded)));
return destination;
}
Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFileImpl(
const nsAString& aFilePath) {
MOZ_ASSERT(!aFilePath.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result,
QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(aFilePath)));
bool exists = true;
QM_TRY(QM_TO_RESULT(result->Exists(&exists)));
if (!exists) {
QM_TRY(QM_TO_RESULT(result->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
return result;
}
bool isDirectory = true;
QM_TRY(QM_TO_RESULT(result->IsDirectory(&isDirectory)));
QM_TRY(OkIf(!isDirectory), Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY)));
return result;
}
Result<nsCOMPtr<nsIFile>, QMResult> GetFile(
const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
MOZ_ASSERT(!aFileId.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
GetFileDestination(aTopDirectory, aFileId));
nsString desiredPath;
QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
QM_TRY_RETURN(QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(desiredPath)));
}
Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(
const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
MOZ_ASSERT(!aFileId.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
GetFileDestination(aTopDirectory, aFileId));
nsString desiredPath;
QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath));
return result;
}
nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) {
// If we cannot tell whether the object is file or directory, or it is a
// directory, it is abandoned as an unknown object. If an attempt is made to
// create a new object with the same path on disk, we regenerate the FileId
// until the collision is resolved.
bool isFile = false;
QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile)));
QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY);
QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false)));
return NS_OK;
}
#ifdef DEBUG
// Unused in release builds
Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) {
bool exists = false;
QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists)));
if (!exists) {
return 0;
}
bool isFile = false;
QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile)));
// We never create directories with this path: this is an unknown object
// and the file does not exist
QM_TRY(OkIf(isFile), 0);
QM_TRY_UNWRAP(Usage fileSize,
QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize));
return fileSize;
}
#endif
} // namespace
Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory(
const quota::OriginMetadata& aOriginMetadata) {
MOZ_ASSERT(aOriginMetadata.mPersistenceType ==
quota::PERSISTENCE_TYPE_DEFAULT);
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
MOZ_ASSERT(quotaManager);
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory,
QM_TO_RESULT_TRANSFORM(
quotaManager->GetOriginDirectory(aOriginMetadata)));
QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath(
NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME))));
return fileSystemDirectory;
}
nsresult EnsureFileSystemDirectory(
const quota::OriginMetadata& aOriginMetadata) {
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
MOZ_ASSERT(quotaManager);
QM_TRY_INSPECT(const auto& fileSystemDirectory,
quotaManager
->EnsureTemporaryOriginIsInitializedInternal(
aOriginMetadata, /* aCreateIfNonExistent */ true)
.map([](const auto& aPair) { return aPair.first; }));
QM_TRY(quotaManager->EnsureTemporaryOriginDirectoryCreated(aOriginMetadata));
QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath(
NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME))));
bool exists = true;
QM_TRY(QM_TO_RESULT(fileSystemDirectory->Exists(&exists)));
if (!exists) {
QM_TRY(QM_TO_RESULT(
fileSystemDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)));
return NS_OK;
}
bool isDirectory = true;
QM_TRY(QM_TO_RESULT(fileSystemDirectory->IsDirectory(&isDirectory)));
QM_TRY(OkIf(isDirectory), NS_ERROR_FILE_NOT_DIRECTORY);
return NS_OK;
}
Result<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile(
const quota::OriginMetadata& aOriginMetadata) {
MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
GetFileSystemDirectory(aOriginMetadata));
QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName)));
return databaseFile;
}
/**
* TODO: This is almost identical to the corresponding function of IndexedDB
*/
Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL(
const quota::OriginMetadata& aOriginMetadata,
const int64_t aDirectoryLockId) {
MOZ_ASSERT(aDirectoryLockId >= -1);
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
GetDatabaseFile(aOriginMetadata));
QM_TRY_INSPECT(
const auto& protocolHandler,
QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED(
nsCOMPtr<nsIProtocolHandler>, MOZ_SELECT_OVERLOAD(do_GetService),
NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file")));
QM_TRY_INSPECT(const auto& fileHandler,
QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED(
nsCOMPtr<nsIFileProtocolHandler>,
MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler)));
QM_TRY_INSPECT(const auto& mutator,
QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<nsIURIMutator>, fileHandler, NewFileURIMutator,
databaseFile)));
// aDirectoryLockId should only be -1 when we are called from
// FileSystemQuotaClient::InitOrigin when the temporary storage hasn't been
// initialized yet. At that time, the in-memory objects (e.g. OriginInfo) are
// only being created so it doesn't make sense to tunnel quota information to
// QuotaVFS to get corresponding QuotaObject instances for SQLite files.
const nsCString directoryLockIdClause =
"&directoryLockId="_ns + IntToCString(aDirectoryLockId);
nsCOMPtr<nsIFileURL> result;
QM_TRY(QM_TO_RESULT(NS_MutateURI(mutator)
.SetQuery("cache=private"_ns + directoryLockIdClause)
.Finalize(result)));
return result;
}
/* static */
Result<FileSystemFileManager, QMResult>
FileSystemFileManager::CreateFileSystemFileManager(
nsCOMPtr<nsIFile>&& topDirectory) {
return FileSystemFileManager(std::move(topDirectory));
}
/* static */
Result<UniquePtr<FileSystemFileManager>, QMResult>
FileSystemFileManager::CreateFileSystemFileManager(
const quota::OriginMetadata& aOriginMetadata) {
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory,
GetFileSystemDirectory(aOriginMetadata));
return MakeUnique<FileSystemFileManager>(
FileSystemFileManager(std::move(topDirectory)));
}
FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory)
: mTopDirectory(std::move(aTopDirectory)) {}
Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile(
const FileId& aFileId) const {
return data::GetFile(mTopDirectory, aFileId);
}
Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile(
const FileId& aFileId) {
return data::GetOrCreateFile(mTopDirectory, aFileId);
}
Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::CreateFileFrom(
const FileId& aDestinationFileId, const FileId& aSourceFileId) {
MOZ_ASSERT(!aDestinationFileId.IsEmpty());
MOZ_ASSERT(!aSourceFileId.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> original, GetFile(aSourceFileId));
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> destination,
GetFileDestination(mTopDirectory, aDestinationFileId));
nsAutoString leafName;
QM_TRY(QM_TO_RESULT(destination->GetLeafName(leafName)));
nsCOMPtr<nsIFile> destParent;
QM_TRY(QM_TO_RESULT(destination->GetParent(getter_AddRefs(destParent))));
QM_TRY(QM_TO_RESULT(original->CopyTo(destParent, leafName)));
#ifdef DEBUG
bool exists = false;
QM_TRY(QM_TO_RESULT(destination->Exists(&exists)));
MOZ_ASSERT(exists);
int64_t destSize = 0;
QM_TRY(QM_TO_RESULT(destination->GetFileSize(&destSize)));
int64_t origSize = 0;
QM_TRY(QM_TO_RESULT(original->GetFileSize(&origSize)));
MOZ_ASSERT(destSize == origSize);
#endif
return destination;
}
Result<Usage, QMResult> FileSystemFileManager::RemoveFile(
const FileId& aFileId) {
MOZ_ASSERT(!aFileId.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
GetFileDestination(mTopDirectory, aFileId));
bool exists = false;
QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists)));
if (!exists) {
return 0;
}
bool isFile = false;
QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile)));
// We could handle this also as a nonexistent file.
if (!isFile) {
return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY));
}
Usage totalUsage = 0;
#ifdef DEBUG
QM_TRY_UNWRAP(totalUsage,
QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize));
#endif
QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false)));
return totalUsage;
}
Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles(
const nsTArray<FileId>& aFileIds, nsTArray<FileId>& aFailedRemovals) {
if (aFileIds.IsEmpty()) {
return DebugOnly<Usage>(0);
}
CheckedInt64 totalUsage = 0;
for (const auto& someId : aFileIds) {
QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile,
GetFileDestination(mTopDirectory, someId));
if (!maybeFile) {
aFailedRemovals.AppendElement(someId);
continue;
}
nsCOMPtr<nsIFile> fileObject = maybeFile.value();
// Size recorded at close is checked to be equal to the sum of sizes on disk
#ifdef DEBUG
QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject));
if (!fileSize) {
aFailedRemovals.AppendElement(someId);
continue;
}
totalUsage += fileSize.value();
#endif
QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok,
MOZ_TO_RESULT(RemoveFileObject(fileObject)));
if (!ok) {
aFailedRemovals.AppendElement(someId);
}
}
MOZ_ASSERT(totalUsage.isValid());
return DebugOnly<Usage>(totalUsage.value());
}
} // namespace mozilla::dom::fs::data