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
393 lines
12 KiB
C++
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
|