Files
tubestation/dom/quota/OriginOperations.cpp

2020 lines
61 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 "OriginOperations.h"
#include <algorithm>
#include <cstdint>
#include <utility>
#include "ErrorList.h"
#include "FileUtils.h"
#include "GroupInfo.h"
#include "MainThreadUtils.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/NotNull.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/dom/quota/CommonMetadata.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/Constants.h"
#include "mozilla/dom/quota/DirectoryLock.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/PQuota.h"
#include "mozilla/dom/quota/PQuotaRequest.h"
#include "mozilla/dom/quota/PQuotaUsageRequest.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaManagerImpl.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/StreamUtils.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/fallible.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "NormalOriginOperationBase.h"
#include "nsCOMPtr.h"
#include "nsTHashMap.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsHashKeys.h"
#include "nsIBinaryOutputStream.h"
#include "nsIFile.h"
#include "nsIObjectOutputStream.h"
#include "nsIOutputStream.h"
#include "nsLiteralString.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsTArray.h"
#include "OriginInfo.h"
#include "OriginOperationBase.h"
#include "QuotaRequestBase.h"
#include "QuotaUsageRequestBase.h"
#include "ResolvableNormalOriginOp.h"
#include "prthread.h"
#include "prtime.h"
namespace mozilla::dom::quota {
using namespace mozilla::ipc;
namespace {
class FinalizeOriginEvictionOp : public OriginOperationBase {
nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
public:
FinalizeOriginEvictionOp(nsISerialEventTarget* aBackgroundThread,
nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks)
: OriginOperationBase(aBackgroundThread,
"dom::quota::FinalizeOriginEvictionOp"),
mLocks(std::move(aLocks)) {
MOZ_ASSERT(!NS_IsMainThread());
}
private:
~FinalizeOriginEvictionOp() = default;
virtual void Open() override;
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
virtual void UnblockOpen() override;
};
class SaveOriginAccessTimeOp : public NormalOriginOperationBase {
const OriginMetadata mOriginMetadata;
int64_t mTimestamp;
public:
SaveOriginAccessTimeOp(const OriginMetadata& aOriginMetadata,
int64_t aTimestamp)
: NormalOriginOperationBase(
"dom::quota::SaveOriginAccessTimeOp",
Nullable<PersistenceType>(aOriginMetadata.mPersistenceType),
OriginScope::FromOrigin(aOriginMetadata.mOrigin),
Nullable<Client::Type>(),
/* aExclusive */ false),
mOriginMetadata(aOriginMetadata),
mTimestamp(aTimestamp) {
AssertIsOnOwningThread();
}
private:
~SaveOriginAccessTimeOp() = default;
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
virtual void SendResults() override;
};
class ClearPrivateRepositoryOp : public ResolvableNormalOriginOp<bool> {
public:
ClearPrivateRepositoryOp()
: ResolvableNormalOriginOp(
"dom::quota::ClearPrivateRepositoryOp",
Nullable<PersistenceType>(PERSISTENCE_TYPE_PRIVATE),
OriginScope::FromNull(), Nullable<Client::Type>(),
/* aExclusive */ true) {
AssertIsOnOwningThread();
}
private:
~ClearPrivateRepositoryOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
bool GetResolveValue() override { return true; }
};
class ShutdownStorageOp : public ResolvableNormalOriginOp<bool> {
public:
ShutdownStorageOp()
: ResolvableNormalOriginOp(
"dom::quota::ShutdownStorageOp", Nullable<PersistenceType>(),
OriginScope::FromNull(), Nullable<Client::Type>(),
/* aExclusive */ true) {
AssertIsOnOwningThread();
}
private:
~ShutdownStorageOp() = default;
#ifdef DEBUG
nsresult DirectoryOpen() override;
#endif
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
bool GetResolveValue() override { return true; }
};
// A mix-in class to simplify operations that need to process every origin in
// one or more repositories. Sub-classes should call TraverseRepository in their
// DoDirectoryWork and implement a ProcessOrigin method for their per-origin
// logic.
class TraverseRepositoryHelper {
public:
TraverseRepositoryHelper() = default;
protected:
virtual ~TraverseRepositoryHelper() = default;
// If ProcessOrigin returns an error, TraverseRepository will immediately
// terminate and return the received error code to its caller.
nsresult TraverseRepository(QuotaManager& aQuotaManager,
PersistenceType aPersistenceType);
private:
virtual const Atomic<bool>& GetIsCanceledFlag() = 0;
virtual nsresult ProcessOrigin(QuotaManager& aQuotaManager,
nsIFile& aOriginDir, const bool aPersistent,
const PersistenceType aPersistenceType) = 0;
};
class GetUsageOp final : public QuotaUsageRequestBase,
public TraverseRepositoryHelper {
nsTArray<OriginUsage> mOriginUsages;
nsTHashMap<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
bool mGetAll;
public:
explicit GetUsageOp(const UsageRequestParams& aParams);
private:
~GetUsageOp() = default;
void ProcessOriginInternal(QuotaManager* aQuotaManager,
const PersistenceType aPersistenceType,
const nsACString& aOrigin,
const int64_t aTimestamp, const bool aPersisted,
const uint64_t aUsage);
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
const Atomic<bool>& GetIsCanceledFlag() override;
nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
const bool aPersistent,
const PersistenceType aPersistenceType) override;
void GetResponse(UsageRequestResponse& aResponse) override;
};
class GetOriginUsageOp final : public QuotaUsageRequestBase {
const OriginUsageParams mParams;
nsCString mSuffix;
nsCString mGroup;
nsCString mStorageOrigin;
uint64_t mUsage;
uint64_t mFileUsage;
bool mIsPrivate;
bool mFromMemory;
public:
explicit GetOriginUsageOp(const UsageRequestParams& aParams);
private:
~GetOriginUsageOp() = default;
nsresult DoInit(QuotaManager& aQuotaManager) override;
RefPtr<DirectoryLock> CreateDirectoryLock() override;
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(UsageRequestResponse& aResponse) override;
};
class StorageNameOp final : public QuotaRequestBase {
nsString mName;
public:
StorageNameOp();
private:
~StorageNameOp() = default;
RefPtr<DirectoryLock> CreateDirectoryLock() override;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class InitializedRequestBase : public QuotaRequestBase {
protected:
bool mInitialized;
InitializedRequestBase(const char* aRunnableName);
private:
RefPtr<DirectoryLock> CreateDirectoryLock() override;
};
class StorageInitializedOp final : public InitializedRequestBase {
public:
StorageInitializedOp()
: InitializedRequestBase("dom::quota::StorageInitializedOp") {}
private:
~StorageInitializedOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class TemporaryStorageInitializedOp final : public InitializedRequestBase {
public:
TemporaryStorageInitializedOp()
: InitializedRequestBase("dom::quota::StorageInitializedOp") {}
private:
~TemporaryStorageInitializedOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class InitOp final : public QuotaRequestBase {
public:
InitOp();
private:
~InitOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class InitTemporaryStorageOp final : public QuotaRequestBase {
public:
InitTemporaryStorageOp();
private:
~InitTemporaryStorageOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class InitializeOriginRequestBase : public QuotaRequestBase {
protected:
const PrincipalInfo mPrincipalInfo;
nsCString mSuffix;
nsCString mGroup;
nsCString mStorageOrigin;
bool mIsPrivate;
bool mCreated;
InitializeOriginRequestBase(const char* aRunnableName,
PersistenceType aPersistenceType,
const PrincipalInfo& aPrincipalInfo);
nsresult DoInit(QuotaManager& aQuotaManager) override;
};
class InitializePersistentOriginOp final : public InitializeOriginRequestBase {
public:
explicit InitializePersistentOriginOp(const RequestParams& aParams);
private:
~InitializePersistentOriginOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class InitializeTemporaryOriginOp final : public InitializeOriginRequestBase {
public:
explicit InitializeTemporaryOriginOp(const RequestParams& aParams);
private:
~InitializeTemporaryOriginOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class GetFullOriginMetadataOp : public QuotaRequestBase {
const GetFullOriginMetadataParams mParams;
// XXX Consider wrapping with LazyInitializedOnce
OriginMetadata mOriginMetadata;
Maybe<FullOriginMetadata> mMaybeFullOriginMetadata;
public:
explicit GetFullOriginMetadataOp(const GetFullOriginMetadataParams& aParams);
private:
nsresult DoInit(QuotaManager& aQuotaManager) override;
RefPtr<DirectoryLock> CreateDirectoryLock() override;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class ResetOrClearOp final : public QuotaRequestBase {
const bool mClear;
public:
explicit ResetOrClearOp(bool aClear);
private:
~ResetOrClearOp() = default;
void DeleteFiles(QuotaManager& aQuotaManager);
void DeleteStorageFile(QuotaManager& aQuotaManager);
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
virtual void GetResponse(RequestResponse& aResponse) override;
};
class ClearRequestBase : public QuotaRequestBase {
protected:
explicit ClearRequestBase(const char* aRunnableName, bool aExclusive)
: QuotaRequestBase(aRunnableName, aExclusive) {
AssertIsOnOwningThread();
}
ClearRequestBase(const char* aRunnableName,
const Nullable<PersistenceType>& aPersistenceType,
const OriginScope& aOriginScope,
const Nullable<Client::Type>& aClientType, bool aExclusive)
: QuotaRequestBase(aRunnableName, aPersistenceType, aOriginScope,
aClientType, aExclusive) {}
void DeleteFiles(QuotaManager& aQuotaManager,
PersistenceType aPersistenceType);
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
};
class ClearOriginOp final : public ClearRequestBase {
const ClearResetOriginParams mParams;
const bool mMatchAll;
public:
explicit ClearOriginOp(const RequestParams& aParams);
private:
~ClearOriginOp() = default;
void GetResponse(RequestResponse& aResponse) override;
};
class ClearDataOp final : public ClearRequestBase {
const ClearDataParams mParams;
public:
explicit ClearDataOp(const RequestParams& aParams);
private:
~ClearDataOp() = default;
void GetResponse(RequestResponse& aResponse) override;
};
class ResetOriginOp final : public QuotaRequestBase {
public:
explicit ResetOriginOp(const RequestParams& aParams);
private:
~ResetOriginOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class PersistRequestBase : public QuotaRequestBase {
const PrincipalInfo mPrincipalInfo;
protected:
nsCString mSuffix;
nsCString mGroup;
nsCString mStorageOrigin;
bool mIsPrivate;
protected:
explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
nsresult DoInit(QuotaManager& aQuotaManager) override;
};
class PersistedOp final : public PersistRequestBase {
bool mPersisted;
public:
explicit PersistedOp(const RequestParams& aParams);
private:
~PersistedOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class PersistOp final : public PersistRequestBase {
public:
explicit PersistOp(const RequestParams& aParams);
private:
~PersistOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class EstimateOp final : public QuotaRequestBase {
const EstimateParams mParams;
OriginMetadata mOriginMetadata;
std::pair<uint64_t, uint64_t> mUsageAndLimit;
public:
explicit EstimateOp(const EstimateParams& aParams);
private:
~EstimateOp() = default;
nsresult DoInit(QuotaManager& aQuotaManager) override;
RefPtr<DirectoryLock> CreateDirectoryLock() override;
virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
void GetResponse(RequestResponse& aResponse) override;
};
class ListOriginsOp final : public QuotaRequestBase,
public TraverseRepositoryHelper {
// XXX Bug 1521541 will make each origin has it's own state.
nsTArray<nsCString> mOrigins;
public:
ListOriginsOp();
private:
~ListOriginsOp() = default;
nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
const Atomic<bool>& GetIsCanceledFlag() override;
nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
const bool aPersistent,
const PersistenceType aPersistenceType) override;
void GetResponse(RequestResponse& aResponse) override;
};
} // namespace
RefPtr<OriginOperationBase> CreateFinalizeOriginEvictionOp(
nsISerialEventTarget* aOwningThread,
nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
return MakeRefPtr<FinalizeOriginEvictionOp>(aOwningThread, std::move(aLocks));
}
RefPtr<NormalOriginOperationBase> CreateSaveOriginAccessTimeOp(
const OriginMetadata& aOriginMetadata, int64_t aTimestamp) {
return MakeRefPtr<SaveOriginAccessTimeOp>(aOriginMetadata, aTimestamp);
}
RefPtr<ResolvableNormalOriginOp<bool>> CreateClearPrivateRepositoryOp() {
return MakeRefPtr<ClearPrivateRepositoryOp>();
}
RefPtr<ResolvableNormalOriginOp<bool>> CreateShutdownStorageOp() {
return MakeRefPtr<ShutdownStorageOp>();
}
RefPtr<QuotaUsageRequestBase> CreateGetUsageOp(
const UsageRequestParams& aParams) {
return MakeRefPtr<GetUsageOp>(aParams);
}
RefPtr<QuotaUsageRequestBase> CreateGetOriginUsageOp(
const UsageRequestParams& aParams) {
return MakeRefPtr<GetOriginUsageOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateStorageNameOp() {
return MakeRefPtr<StorageNameOp>();
}
RefPtr<QuotaRequestBase> CreateStorageInitializedOp() {
return MakeRefPtr<StorageInitializedOp>();
}
RefPtr<QuotaRequestBase> CreateTemporaryStorageInitializedOp() {
return MakeRefPtr<TemporaryStorageInitializedOp>();
}
RefPtr<QuotaRequestBase> CreateInitOp() { return MakeRefPtr<InitOp>(); }
RefPtr<QuotaRequestBase> CreateInitTemporaryStorageOp() {
return MakeRefPtr<InitTemporaryStorageOp>();
}
RefPtr<QuotaRequestBase> CreateInitializePersistentOriginOp(
const RequestParams& aParams) {
return MakeRefPtr<InitializePersistentOriginOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateInitializeTemporaryOriginOp(
const RequestParams& aParams) {
return MakeRefPtr<InitializeTemporaryOriginOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateGetFullOriginMetadataOp(
const GetFullOriginMetadataParams& aParams) {
return MakeRefPtr<GetFullOriginMetadataOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateResetOrClearOp(bool aClear) {
return MakeRefPtr<ResetOrClearOp>(aClear);
}
RefPtr<QuotaRequestBase> CreateClearOriginOp(const RequestParams& aParams) {
return MakeRefPtr<ClearOriginOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateClearDataOp(const RequestParams& aParams) {
return MakeRefPtr<ClearDataOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateResetOriginOp(const RequestParams& aParams) {
return MakeRefPtr<ResetOriginOp>(aParams);
}
RefPtr<QuotaRequestBase> CreatePersistedOp(const RequestParams& aParams) {
return MakeRefPtr<PersistedOp>(aParams);
}
RefPtr<QuotaRequestBase> CreatePersistOp(const RequestParams& aParams) {
return MakeRefPtr<PersistOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateEstimateOp(const EstimateParams& aParams) {
return MakeRefPtr<EstimateOp>(aParams);
}
RefPtr<QuotaRequestBase> CreateListOriginsOp() {
return MakeRefPtr<ListOriginsOp>();
}
void FinalizeOriginEvictionOp::Open() {
AssertIsOnOwningThread();
MOZ_ASSERT(GetState() == State_Initial);
AdvanceState();
QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
[this](const nsresult rv) { Finish(rv); });
}
nsresult FinalizeOriginEvictionOp::DoDirectoryWork(
QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER);
for (const auto& lock : mLocks) {
aQuotaManager.OriginClearCompleted(
lock->GetPersistenceType(), lock->Origin(), Nullable<Client::Type>());
}
return NS_OK;
}
void FinalizeOriginEvictionOp::UnblockOpen() {
AssertIsOnOwningThread();
MOZ_ASSERT(GetState() == State_UnblockingOpen);
#ifdef DEBUG
NoteActorDestroyed();
#endif
mLocks.Clear();
AdvanceState();
}
nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
MOZ_ASSERT(mOriginScope.IsOrigin());
AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER);
QM_TRY_INSPECT(const auto& file,
aQuotaManager.GetOriginDirectory(mOriginMetadata));
// The origin directory might not exist
// anymore, because it was deleted by a clear operation.
QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
if (exists) {
QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
QM_TRY_INSPECT(const auto& stream,
GetBinaryOutputStream(*file, FileFlag::Update));
MOZ_ASSERT(stream);
QM_TRY(MOZ_TO_RESULT(stream->Write64(mTimestamp)));
}
return NS_OK;
}
void SaveOriginAccessTimeOp::SendResults() {
#ifdef DEBUG
NoteActorDestroyed();
#endif
}
nsresult ClearPrivateRepositoryOp::DoDirectoryWork(
QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_PRIVATE);
AUTO_PROFILER_LABEL("ClearPrivateRepositoryOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
QM_TRY_INSPECT(
const auto& directory,
QM_NewLocalFile(aQuotaManager.GetStoragePath(mPersistenceType.Value())));
nsresult rv = directory->Remove(true);
if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
// This should never fail if we've closed all storage connections
// correctly...
MOZ_ASSERT(false, "Failed to remove directory!");
}
aQuotaManager.RemoveQuotaForRepository(mPersistenceType.Value());
aQuotaManager.RepositoryClearCompleted(mPersistenceType.Value());
return NS_OK;
}
#ifdef DEBUG
nsresult ShutdownStorageOp::DirectoryOpen() {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mDirectoryLock);
mDirectoryLock->AssertIsAcquiredExclusively();
return NormalOriginOperationBase::DirectoryOpen();
}
#endif
nsresult ShutdownStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("ShutdownStorageOp::DoDirectoryWork", OTHER);
aQuotaManager.ShutdownStorageInternal();
return NS_OK;
}
nsresult TraverseRepositoryHelper::TraverseRepository(
QuotaManager& aQuotaManager, PersistenceType aPersistenceType) {
AssertIsOnIOThread();
QM_TRY_INSPECT(
const auto& directory,
QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)));
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
if (!exists) {
return NS_OK;
}
QM_TRY(CollectEachFileAtomicCancelable(
*directory, GetIsCanceledFlag(),
[this, aPersistenceType, &aQuotaManager,
persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT](
const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
switch (dirEntryKind) {
case nsIFileKind::ExistsAsDirectory:
QM_TRY(MOZ_TO_RESULT(ProcessOrigin(aQuotaManager, *originDir,
persistent, aPersistenceType)));
break;
case nsIFileKind::ExistsAsFile: {
QM_TRY_INSPECT(const auto& leafName,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsAutoString, originDir, GetLeafName));
// Unknown files during getting usages are allowed. Just warn if we
// find them.
if (!IsOSMetadata(leafName)) {
UNKNOWN_FILE_WARNING(leafName);
}
break;
}
case nsIFileKind::DoesNotExist:
// Ignore files that got removed externally while iterating.
break;
}
return Ok{};
}));
return NS_OK;
}
GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
: QuotaUsageRequestBase("dom::quota::GetUsageOp"),
mGetAll(aParams.get_AllUsageParams().getAll()) {
AssertIsOnOwningThread();
MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
}
void GetUsageOp::ProcessOriginInternal(QuotaManager* aQuotaManager,
const PersistenceType aPersistenceType,
const nsACString& aOrigin,
const int64_t aTimestamp,
const bool aPersisted,
const uint64_t aUsage) {
if (!mGetAll && aQuotaManager->IsOriginInternal(aOrigin)) {
return;
}
// We can't store pointers to OriginUsage objects in the hashtable
// since AppendElement() reallocates its internal array buffer as number
// of elements grows.
const auto& originUsage =
mOriginUsagesIndex.WithEntryHandle(aOrigin, [&](auto&& entry) {
if (entry) {
return WrapNotNullUnchecked(&mOriginUsages[entry.Data()]);
}
entry.Insert(mOriginUsages.Length());
return mOriginUsages.EmplaceBack(nsCString{aOrigin}, false, 0, 0);
});
if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
originUsage->persisted() = aPersisted;
}
originUsage->usage() = originUsage->usage() + aUsage;
originUsage->lastAccessed() =
std::max<int64_t>(originUsage->lastAccessed(), aTimestamp);
}
const Atomic<bool>& GetUsageOp::GetIsCanceledFlag() {
AssertIsOnIOThread();
return mCanceled;
}
// XXX Remove aPersistent
// XXX Remove aPersistenceType once GetUsageForOrigin uses the persistence
// type from OriginMetadata
nsresult GetUsageOp::ProcessOrigin(QuotaManager& aQuotaManager,
nsIFile& aOriginDir, const bool aPersistent,
const PersistenceType aPersistenceType) {
AssertIsOnIOThread();
QM_TRY_UNWRAP(auto maybeMetadata,
QM_OR_ELSE_WARN_IF(
// Expression
aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir)
.map([](auto metadata) -> Maybe<FullOriginMetadata> {
return Some(std::move(metadata));
}),
// Predicate.
IsSpecificError<NS_ERROR_MALFORMED_URI>,
// Fallback.
ErrToDefaultOk<Maybe<FullOriginMetadata>>));
if (!maybeMetadata) {
// Unknown directories during getting usage are allowed. Just warn if we
// find them.
QM_TRY_INSPECT(const auto& leafName,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aOriginDir,
GetLeafName));
UNKNOWN_FILE_WARNING(leafName);
return NS_OK;
}
auto metadata = maybeMetadata.extract();
QM_TRY_INSPECT(const auto& usageInfo,
GetUsageForOrigin(aQuotaManager, aPersistenceType, metadata));
ProcessOriginInternal(&aQuotaManager, aPersistenceType, metadata.mOrigin,
metadata.mLastAccessTime, metadata.mPersisted,
usageInfo.TotalUsage().valueOr(0));
return NS_OK;
}
nsresult GetUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
nsresult rv;
for (const PersistenceType type : kAllPersistenceTypes) {
rv = TraverseRepository(aQuotaManager, type);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
// TraverseRepository above only consulted the filesystem. We also need to
// consider origins which may have pending quota usage, such as buffered
// LocalStorage writes for an origin which didn't previously have any
// LocalStorage data.
aQuotaManager.CollectPendingOriginsForListing(
[this, &aQuotaManager](const auto& originInfo) {
ProcessOriginInternal(
&aQuotaManager, originInfo->GetGroupInfo()->GetPersistenceType(),
originInfo->Origin(), originInfo->LockedAccessTime(),
originInfo->LockedPersisted(), originInfo->LockedUsage());
});
return NS_OK;
}
void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = AllUsageResponse();
aResponse.get_AllUsageResponse().originUsages() = std::move(mOriginUsages);
}
GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
: QuotaUsageRequestBase("dom::quota::GetOriginUsageOp"),
mParams(aParams.get_OriginUsageParams()),
mUsage(0),
mFileUsage(0) {
AssertIsOnOwningThread();
MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
// Overwrite GetOriginUsageOp default values.
mFromMemory = mParams.fromMemory();
}
nsresult GetOriginUsageOp::DoInit(QuotaManager& aQuotaManager) {
AssertIsOnOwningThread();
QM_TRY_UNWRAP(
PrincipalMetadata principalMetadata,
aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
principalMetadata.AssertInvariants();
mSuffix = std::move(principalMetadata.mSuffix);
mGroup = std::move(principalMetadata.mGroup);
mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
mIsPrivate = principalMetadata.mIsPrivate;
return NS_OK;
}
RefPtr<DirectoryLock> GetOriginUsageOp::CreateDirectoryLock() {
if (mFromMemory) {
return nullptr;
}
return QuotaUsageRequestBase::CreateDirectoryLock();
}
nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(mUsage == 0);
MOZ_ASSERT(mFileUsage == 0);
AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
if (mFromMemory) {
const PrincipalMetadata principalMetadata = {
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}, mStorageOrigin,
mIsPrivate};
// Ensure temporary storage is initialized. If temporary storage hasn't been
// initialized yet, the method will initialize it by traversing the
// repositories for temporary and default storage (including our origin).
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
// Get cached usage (the method doesn't have to stat any files). File usage
// is not tracked in memory separately, so just add to the total usage.
mUsage = aQuotaManager.GetOriginUsage(principalMetadata);
return NS_OK;
}
UsageInfo usageInfo;
// Add all the persistent/temporary/default storage files we care about.
for (const PersistenceType type : kAllPersistenceTypes) {
const OriginMetadata originMetadata = {
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
mStorageOrigin, mIsPrivate, type};
auto usageInfoOrErr =
GetUsageForOrigin(aQuotaManager, type, originMetadata);
if (NS_WARN_IF(usageInfoOrErr.isErr())) {
return usageInfoOrErr.unwrapErr();
}
usageInfo += usageInfoOrErr.unwrap();
}
mUsage = usageInfo.TotalUsage().valueOr(0);
mFileUsage = usageInfo.FileUsage().valueOr(0);
return NS_OK;
}
void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) {
AssertIsOnOwningThread();
OriginUsageResponse usageResponse;
usageResponse.usage() = mUsage;
usageResponse.fileUsage() = mFileUsage;
aResponse = usageResponse;
}
StorageNameOp::StorageNameOp()
: QuotaRequestBase("dom::quota::StorageNameOp", /* aExclusive */ false) {
AssertIsOnOwningThread();
}
RefPtr<DirectoryLock> StorageNameOp::CreateDirectoryLock() { return nullptr; }
nsresult StorageNameOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("StorageNameOp::DoDirectoryWork", OTHER);
mName = aQuotaManager.GetStorageName();
return NS_OK;
}
void StorageNameOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
StorageNameResponse storageNameResponse;
storageNameResponse.name() = mName;
aResponse = storageNameResponse;
}
InitializedRequestBase::InitializedRequestBase(const char* aRunnableName)
: QuotaRequestBase(aRunnableName, /* aExclusive */ false),
mInitialized(false) {
AssertIsOnOwningThread();
}
RefPtr<DirectoryLock> InitializedRequestBase::CreateDirectoryLock() {
return nullptr;
}
nsresult StorageInitializedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("StorageInitializedOp::DoDirectoryWork", OTHER);
mInitialized = aQuotaManager.IsStorageInitialized();
return NS_OK;
}
void StorageInitializedOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
StorageInitializedResponse storageInitializedResponse;
storageInitializedResponse.initialized() = mInitialized;
aResponse = storageInitializedResponse;
}
nsresult TemporaryStorageInitializedOp::DoDirectoryWork(
QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("TemporaryStorageInitializedOp::DoDirectoryWork", OTHER);
mInitialized = aQuotaManager.IsTemporaryStorageInitialized();
return NS_OK;
}
void TemporaryStorageInitializedOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
TemporaryStorageInitializedResponse temporaryStorageInitializedResponse;
temporaryStorageInitializedResponse.initialized() = mInitialized;
aResponse = temporaryStorageInitializedResponse;
}
InitOp::InitOp()
: QuotaRequestBase("dom::quota::InitOp", /* aExclusive */ false) {
AssertIsOnOwningThread();
}
nsresult InitOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
return NS_OK;
}
void InitOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = InitResponse();
}
InitTemporaryStorageOp::InitTemporaryStorageOp()
: QuotaRequestBase("dom::quota::InitTemporaryStorageOp",
/* aExclusive */ false) {
AssertIsOnOwningThread();
}
nsresult InitTemporaryStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER);
QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_NOT_INITIALIZED);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
return NS_OK;
}
void InitTemporaryStorageOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = InitTemporaryStorageResponse();
}
InitializeOriginRequestBase::InitializeOriginRequestBase(
const char* aRunnableName, const PersistenceType aPersistenceType,
const PrincipalInfo& aPrincipalInfo)
: QuotaRequestBase(aRunnableName,
/* aExclusive */ false),
mPrincipalInfo(aPrincipalInfo),
mCreated(false) {
AssertIsOnOwningThread();
// Overwrite NormalOriginOperationBase default values.
mPersistenceType.SetValue(aPersistenceType);
}
nsresult InitializeOriginRequestBase::DoInit(QuotaManager& aQuotaManager) {
AssertIsOnOwningThread();
QM_TRY_UNWRAP(
auto principalMetadata,
aQuotaManager.GetInfoFromValidatedPrincipalInfo(mPrincipalInfo));
principalMetadata.AssertInvariants();
mSuffix = std::move(principalMetadata.mSuffix);
mGroup = std::move(principalMetadata.mGroup);
mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
mIsPrivate = principalMetadata.mIsPrivate;
return NS_OK;
}
InitializePersistentOriginOp::InitializePersistentOriginOp(
const RequestParams& aParams)
: InitializeOriginRequestBase(
"dom::quota::InitializePersistentOriginOp",
PERSISTENCE_TYPE_PERSISTENT,
aParams.get_InitializePersistentOriginParams().principalInfo()) {
AssertIsOnOwningThread();
MOZ_ASSERT(aParams.type() ==
RequestParams::TInitializePersistentOriginParams);
}
nsresult InitializePersistentOriginOp::DoDirectoryWork(
QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
AUTO_PROFILER_LABEL("InitializePersistentOriginOp::DoDirectoryWork", OTHER);
QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_NOT_INITIALIZED);
QM_TRY_UNWRAP(
mCreated,
(aQuotaManager
.EnsurePersistentOriginIsInitialized(OriginMetadata{
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
mStorageOrigin, mIsPrivate, PERSISTENCE_TYPE_PERSISTENT})
.map([](const auto& res) { return res.second; })));
return NS_OK;
}
void InitializePersistentOriginOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = InitializePersistentOriginResponse(mCreated);
}
InitializeTemporaryOriginOp::InitializeTemporaryOriginOp(
const RequestParams& aParams)
: InitializeOriginRequestBase(
"dom::quota::InitializeTemporaryOriginOp",
aParams.get_InitializeTemporaryOriginParams().persistenceType(),
aParams.get_InitializeTemporaryOriginParams().principalInfo()) {
AssertIsOnOwningThread();
MOZ_ASSERT(aParams.type() == RequestParams::TInitializeTemporaryOriginParams);
}
nsresult InitializeTemporaryOriginOp::DoDirectoryWork(
QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
AUTO_PROFILER_LABEL("InitializeTemporaryOriginOp::DoDirectoryWork", OTHER);
QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_NOT_INITIALIZED);
QM_TRY(OkIf(aQuotaManager.IsTemporaryStorageInitialized()),
NS_ERROR_NOT_INITIALIZED);
QM_TRY_UNWRAP(
mCreated,
(aQuotaManager
.EnsureTemporaryOriginIsInitialized(
mPersistenceType.Value(),
OriginMetadata{
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
mStorageOrigin, mIsPrivate, mPersistenceType.Value()})
.map([](const auto& res) { return res.second; })));
return NS_OK;
}
void InitializeTemporaryOriginOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = InitializeTemporaryOriginResponse(mCreated);
}
GetFullOriginMetadataOp::GetFullOriginMetadataOp(
const GetFullOriginMetadataParams& aParams)
: QuotaRequestBase("dom::quota::GetFullOriginMetadataOp",
/* aExclusive */ false),
mParams(aParams) {
AssertIsOnOwningThread();
}
nsresult GetFullOriginMetadataOp::DoInit(QuotaManager& aQuotaManager) {
AssertIsOnOwningThread();
QM_TRY_UNWRAP(
PrincipalMetadata principalMetadata,
aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
principalMetadata.AssertInvariants();
mOriginMetadata = {std::move(principalMetadata), mParams.persistenceType()};
return NS_OK;
}
RefPtr<DirectoryLock> GetFullOriginMetadataOp::CreateDirectoryLock() {
return nullptr;
}
nsresult GetFullOriginMetadataOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("GetFullOriginMetadataOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
// Ensure temporary storage is initialized. If temporary storage hasn't
// been initialized yet, the method will initialize it by traversing the
// repositories for temporary and default storage (including our origin).
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
// Get metadata cached in memory (the method doesn't have to stat any
// files).
mMaybeFullOriginMetadata =
aQuotaManager.GetFullOriginMetadata(mOriginMetadata);
return NS_OK;
}
void GetFullOriginMetadataOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = GetFullOriginMetadataResponse();
aResponse.get_GetFullOriginMetadataResponse().maybeFullOriginMetadata() =
std::move(mMaybeFullOriginMetadata);
}
ResetOrClearOp::ResetOrClearOp(bool aClear)
: QuotaRequestBase("dom::quota::ResetOrClearOp", /* aExclusive */ true),
mClear(aClear) {
AssertIsOnOwningThread();
}
void ResetOrClearOp::DeleteFiles(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
nsresult rv = aQuotaManager.AboutToClearOrigins(Nullable<PersistenceType>(),
OriginScope::FromNull(),
Nullable<Client::Type>());
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
auto directoryOrErr = QM_NewLocalFile(aQuotaManager.GetStoragePath());
if (NS_WARN_IF(directoryOrErr.isErr())) {
return;
}
nsCOMPtr<nsIFile> directory = directoryOrErr.unwrap();
rv = directory->Remove(true);
if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
// This should never fail if we've closed all storage connections
// correctly...
MOZ_ASSERT(false, "Failed to remove storage directory!");
}
}
void ResetOrClearOp::DeleteStorageFile(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
QM_TRY_INSPECT(const auto& storageFile,
QM_NewLocalFile(aQuotaManager.GetBasePath()), QM_VOID);
QM_TRY(MOZ_TO_RESULT(storageFile->Append(aQuotaManager.GetStorageName() +
kSQLiteSuffix)),
QM_VOID);
const nsresult rv = storageFile->Remove(true);
if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
// This should never fail if we've closed the storage connection
// correctly...
MOZ_ASSERT(false, "Failed to remove storage file!");
}
}
nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER);
if (mClear) {
DeleteFiles(aQuotaManager);
aQuotaManager.RemoveQuota();
}
aQuotaManager.ShutdownStorageInternal();
if (mClear) {
DeleteStorageFile(aQuotaManager);
}
return NS_OK;
}
void ResetOrClearOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
if (mClear) {
aResponse = ClearAllResponse();
} else {
aResponse = ResetAllResponse();
}
}
static Result<nsCOMPtr<nsIFile>, QMResult> OpenToBeRemovedDirectory(
const nsAString& aStoragePath) {
QM_TRY_INSPECT(const auto& dir,
QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aStoragePath)));
QM_TRY(QM_TO_RESULT(dir->Append(u"to-be-removed"_ns)));
nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
return dir;
}
return Err(QMResult(rv));
}
static Result<Ok, QMResult> RemoveOrMoveToDir(nsIFile& aFile,
nsIFile* aMoveTargetDir) {
if (!aMoveTargetDir) {
QM_TRY(QM_TO_RESULT(aFile.Remove(true)));
return Ok();
}
nsIDToCString uuid(nsID::GenerateUUID());
NS_ConvertUTF8toUTF16 subDirName(uuid.get(), NSID_LENGTH - 1);
QM_TRY(QM_TO_RESULT(aFile.MoveTo(aMoveTargetDir, subDirName)));
return Ok();
}
void ClearRequestBase::DeleteFiles(QuotaManager& aQuotaManager,
PersistenceType aPersistenceType) {
AssertIsOnIOThread();
QM_TRY(MOZ_TO_RESULT(aQuotaManager.AboutToClearOrigins(
Nullable<PersistenceType>(aPersistenceType), mOriginScope,
mClientType)),
QM_VOID);
QM_TRY_INSPECT(
const auto& directory,
QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)), QM_VOID);
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists), QM_VOID);
if (!exists) {
return;
}
nsTArray<nsCOMPtr<nsIFile>> directoriesForRemovalRetry;
aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
"ClearRequestBase: Starting deleting files"_ns);
nsCOMPtr<nsIFile> toBeRemovedDir;
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownTeardown)) {
QM_WARNONLY_TRY_UNWRAP(
auto result, OpenToBeRemovedDirectory(aQuotaManager.GetStoragePath()));
toBeRemovedDir = result.valueOr(nullptr);
}
QM_TRY(
CollectEachFile(
*directory,
[&originScope = mOriginScope, aPersistenceType, &aQuotaManager,
&directoriesForRemovalRetry, &toBeRemovedDir,
this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
QM_TRY_INSPECT(const auto& leafName,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
GetLeafName));
switch (dirEntryKind) {
case nsIFileKind::ExistsAsDirectory: {
QM_TRY_UNWRAP(
auto maybeMetadata,
QM_OR_ELSE_WARN_IF(
// Expression
aQuotaManager.GetOriginMetadata(file).map(
[](auto metadata) -> Maybe<OriginMetadata> {
return Some(std::move(metadata));
}),
// Predicate.
IsSpecificError<NS_ERROR_MALFORMED_URI>,
// Fallback.
ErrToDefaultOk<Maybe<OriginMetadata>>));
if (!maybeMetadata) {
// Unknown directories during clearing are allowed. Just warn
// if we find them.
UNKNOWN_FILE_WARNING(leafName);
break;
}
auto metadata = maybeMetadata.extract();
MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
// Skip the origin directory if it doesn't match the pattern.
if (!originScope.Matches(
OriginScope::FromOrigin(metadata.mOrigin))) {
break;
}
if (!mClientType.IsNull()) {
nsAutoString clientDirectoryName;
QM_TRY(
OkIf(Client::TypeToText(mClientType.Value(),
clientDirectoryName, fallible)),
Err(NS_ERROR_FAILURE));
QM_TRY(MOZ_TO_RESULT(file->Append(clientDirectoryName)));
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
if (!exists) {
break;
}
}
// We can't guarantee that this will always succeed on
// Windows...
QM_WARNONLY_TRY(
RemoveOrMoveToDir(*file, toBeRemovedDir), [&](const auto&) {
directoriesForRemovalRetry.AppendElement(std::move(file));
});
const bool initialized =
aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
: aQuotaManager.IsTemporaryStorageInitialized();
// If it hasn't been initialized, we don't need to update the
// quota and notify the removing client.
if (!initialized) {
break;
}
if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
if (mClientType.IsNull()) {
aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
metadata);
} else {
aQuotaManager.ResetUsageForClient(
ClientMetadata{metadata, mClientType.Value()});
}
}
aQuotaManager.OriginClearCompleted(
aPersistenceType, metadata.mOrigin, mClientType);
break;
}
case nsIFileKind::ExistsAsFile: {
// Unknown files during clearing are allowed. Just warn if we
// find them.
if (!IsOSMetadata(leafName)) {
UNKNOWN_FILE_WARNING(leafName);
}
break;
}
case nsIFileKind::DoesNotExist:
// Ignore files that got removed externally while iterating.
break;
}
return Ok{};
}),
QM_VOID);
// Retry removing any directories that failed to be removed earlier now.
//
// XXX This will still block this operation. We might instead dispatch a
// runnable to our own thread for each retry round with a timer. We must
// ensure that the directory lock is upheld until we complete or give up
// though.
for (uint32_t index = 0; index < 10; index++) {
aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
return nsPrintfCString(
"ClearRequestBase: Starting repeated directory removal #%d", index);
});
for (auto&& file : std::exchange(directoriesForRemovalRetry,
nsTArray<nsCOMPtr<nsIFile>>{})) {
QM_WARNONLY_TRY(
RemoveOrMoveToDir(*file, toBeRemovedDir),
([&directoriesForRemovalRetry, &file](const auto&) {
directoriesForRemovalRetry.AppendElement(std::move(file));
}));
}
aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
return nsPrintfCString(
"ClearRequestBase: Completed repeated directory removal #%d", index);
});
if (directoriesForRemovalRetry.IsEmpty()) {
break;
}
aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
return nsPrintfCString("ClearRequestBase: Before sleep #%d", index);
});
PR_Sleep(PR_MillisecondsToInterval(200));
aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
return nsPrintfCString("ClearRequestBase: After sleep #%d", index);
});
}
QM_WARNONLY_TRY(OkIf(directoriesForRemovalRetry.IsEmpty()));
aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
"ClearRequestBase: Completed deleting files"_ns);
}
nsresult ClearRequestBase::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
if (mPersistenceType.IsNull()) {
for (const PersistenceType type : kAllPersistenceTypes) {
DeleteFiles(aQuotaManager, type);
}
} else {
DeleteFiles(aQuotaManager, mPersistenceType.Value());
}
return NS_OK;
}
ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
: ClearRequestBase("dom::quota::ClearOriginOp", /* aExclusive */ true),
mParams(aParams.get_ClearOriginParams().commonParams()),
mMatchAll(aParams.get_ClearOriginParams().matchAll()) {
MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams);
if (mParams.persistenceTypeIsExplicit()) {
mPersistenceType.SetValue(mParams.persistenceType());
}
// Figure out which origin we're dealing with.
const auto origin = QuotaManager::GetOriginFromValidatedPrincipalInfo(
mParams.principalInfo());
if (mMatchAll) {
mOriginScope.SetFromPrefix(origin);
} else {
mOriginScope.SetFromOrigin(origin);
}
if (mParams.clientTypeIsExplicit()) {
mClientType.SetValue(mParams.clientType());
}
}
void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = ClearOriginResponse();
}
ClearDataOp::ClearDataOp(const RequestParams& aParams)
: ClearRequestBase("dom::quota::ClearDataOp", /* aExclusive */ true),
mParams(aParams) {
MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
mOriginScope.SetFromPattern(mParams.pattern());
}
void ClearDataOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = ClearDataResponse();
}
ResetOriginOp::ResetOriginOp(const RequestParams& aParams)
: QuotaRequestBase("dom::quota::ResetOriginOp", /* aExclusive */ true) {
AssertIsOnOwningThread();
MOZ_ASSERT(aParams.type() == RequestParams::TResetOriginParams);
const ClearResetOriginParams& params =
aParams.get_ResetOriginParams().commonParams();
const auto origin =
QuotaManager::GetOriginFromValidatedPrincipalInfo(params.principalInfo());
// Overwrite NormalOriginOperationBase default values.
if (params.persistenceTypeIsExplicit()) {
mPersistenceType.SetValue(params.persistenceType());
}
mOriginScope.SetFromOrigin(origin);
if (params.clientTypeIsExplicit()) {
mClientType.SetValue(params.clientType());
}
}
nsresult ResetOriginOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("ResetOriginOp::DoDirectoryWork", OTHER);
// All the work is handled by NormalOriginOperationBase parent class. In this
// particular case, we just needed to acquire an exclusive directory lock and
// that's it.
return NS_OK;
}
void ResetOriginOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = ResetOriginResponse();
}
PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
: QuotaRequestBase("dom::quota::PersistRequestBase",
/* aExclusive */ false),
mPrincipalInfo(aPrincipalInfo) {
AssertIsOnOwningThread();
mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
}
nsresult PersistRequestBase::DoInit(QuotaManager& aQuotaManager) {
AssertIsOnOwningThread();
// Figure out which origin we're dealing with.
QM_TRY_UNWRAP(
PrincipalMetadata principalMetadata,
aQuotaManager.GetInfoFromValidatedPrincipalInfo(mPrincipalInfo));
principalMetadata.AssertInvariants();
mSuffix = std::move(principalMetadata.mSuffix);
mGroup = std::move(principalMetadata.mGroup);
mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
mStorageOrigin = std::move(principalMetadata.mStorageOrigin);
mIsPrivate = principalMetadata.mIsPrivate;
return NS_OK;
}
PersistedOp::PersistedOp(const RequestParams& aParams)
: PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
mPersisted(false) {
MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
}
nsresult PersistedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
MOZ_ASSERT(mOriginScope.IsOrigin());
AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
const OriginMetadata originMetadata = {
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
mStorageOrigin, mIsPrivate, mPersistenceType.Value()};
Nullable<bool> persisted = aQuotaManager.OriginPersisted(originMetadata);
if (!persisted.IsNull()) {
mPersisted = persisted.Value();
return NS_OK;
}
// If we get here, it means the origin hasn't been initialized yet.
// Try to get the persisted flag from directory metadata on disk.
QM_TRY_INSPECT(const auto& directory,
aQuotaManager.GetOriginDirectory(originMetadata));
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
if (exists) {
// Get the metadata. We only use the persisted flag.
QM_TRY_INSPECT(const auto& metadata,
aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
mPersisted = metadata.mPersisted;
} else {
// The directory has not been created yet.
mPersisted = false;
}
return NS_OK;
}
void PersistedOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
PersistedResponse persistedResponse;
persistedResponse.persisted() = mPersisted;
aResponse = persistedResponse;
}
PersistOp::PersistOp(const RequestParams& aParams)
: PersistRequestBase(aParams.get_PersistParams().principalInfo()) {
MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
}
nsresult PersistOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
MOZ_ASSERT(!mPersistenceType.IsNull());
MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
MOZ_ASSERT(mOriginScope.IsOrigin());
const OriginMetadata originMetadata = {
mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
mStorageOrigin, mIsPrivate, mPersistenceType.Value()};
AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
// Update directory metadata on disk first. Then, create/update the originInfo
// if needed.
QM_TRY_INSPECT(const auto& directory,
aQuotaManager.GetOriginDirectory(originMetadata));
QM_TRY_INSPECT(const bool& created,
aQuotaManager.EnsureOriginDirectory(*directory));
if (created) {
int64_t timestamp;
// Origin directory has been successfully created.
// Create OriginInfo too if temporary storage was already initialized.
if (aQuotaManager.IsTemporaryStorageInitialized()) {
timestamp = aQuotaManager.NoteOriginDirectoryCreated(
originMetadata, /* aPersisted */ true);
} else {
timestamp = PR_Now();
}
QM_TRY(MOZ_TO_RESULT(QuotaManager::CreateDirectoryMetadata2(
*directory, timestamp,
/* aPersisted */ true, originMetadata)));
} else {
// Get the metadata (restore the metadata file if necessary). We only use
// the persisted flag.
QM_TRY_INSPECT(const auto& metadata,
aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
if (!metadata.mPersisted) {
QM_TRY_INSPECT(const auto& file,
CloneFileAndAppend(
*directory, nsLiteralString(METADATA_V2_FILE_NAME)));
QM_TRY_INSPECT(const auto& stream,
GetBinaryOutputStream(*file, FileFlag::Update));
MOZ_ASSERT(stream);
// Update origin access time while we are here.
QM_TRY(MOZ_TO_RESULT(stream->Write64(PR_Now())));
// Set the persisted flag to true.
QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(true)));
}
// Directory metadata has been successfully updated.
// Update OriginInfo too if temporary storage was already initialized.
if (aQuotaManager.IsTemporaryStorageInitialized()) {
aQuotaManager.PersistOrigin(originMetadata);
}
}
return NS_OK;
}
void PersistOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = PersistResponse();
}
EstimateOp::EstimateOp(const EstimateParams& aParams)
: QuotaRequestBase("dom::quota::EstimateOp", /* aExclusive */ false),
mParams(aParams) {
AssertIsOnOwningThread();
}
nsresult EstimateOp::DoInit(QuotaManager& aQuotaManager) {
AssertIsOnOwningThread();
QM_TRY_UNWRAP(
PrincipalMetadata principalMetadata,
aQuotaManager.GetInfoFromValidatedPrincipalInfo(mParams.principalInfo()));
principalMetadata.AssertInvariants();
mOriginMetadata = {std::move(principalMetadata), PERSISTENCE_TYPE_DEFAULT};
return NS_OK;
}
RefPtr<DirectoryLock> EstimateOp::CreateDirectoryLock() { return nullptr; }
nsresult EstimateOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("EstimateOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
// Ensure temporary storage is initialized. If temporary storage hasn't been
// initialized yet, the method will initialize it by traversing the
// repositories for temporary and default storage (including origins belonging
// to our group).
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
// Get cached usage (the method doesn't have to stat any files).
mUsageAndLimit = aQuotaManager.GetUsageAndLimitForEstimate(mOriginMetadata);
return NS_OK;
}
void EstimateOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
EstimateResponse estimateResponse;
estimateResponse.usage() = mUsageAndLimit.first;
estimateResponse.limit() = mUsageAndLimit.second;
aResponse = estimateResponse;
}
ListOriginsOp::ListOriginsOp()
: QuotaRequestBase("dom::quota::ListOriginsOp", /* aExclusive */ false),
TraverseRepositoryHelper() {
AssertIsOnOwningThread();
}
nsresult ListOriginsOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("ListOriginsOp::DoDirectoryWork", OTHER);
QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
for (const PersistenceType type : kAllPersistenceTypes) {
QM_TRY(MOZ_TO_RESULT(TraverseRepository(aQuotaManager, type)));
}
// TraverseRepository above only consulted the file-system to get a list of
// known origins, but we also need to include origins that have pending quota
// usage.
aQuotaManager.CollectPendingOriginsForListing([this](const auto& originInfo) {
mOrigins.AppendElement(originInfo->Origin());
});
return NS_OK;
}
const Atomic<bool>& ListOriginsOp::GetIsCanceledFlag() {
AssertIsOnIOThread();
return mCanceled;
}
nsresult ListOriginsOp::ProcessOrigin(QuotaManager& aQuotaManager,
nsIFile& aOriginDir,
const bool aPersistent,
const PersistenceType aPersistenceType) {
AssertIsOnIOThread();
QM_TRY_UNWRAP(auto maybeMetadata,
QM_OR_ELSE_WARN_IF(
// Expression
aQuotaManager.GetOriginMetadata(&aOriginDir)
.map([](auto metadata) -> Maybe<OriginMetadata> {
return Some(std::move(metadata));
}),
// Predicate.
IsSpecificError<NS_ERROR_MALFORMED_URI>,
// Fallback.
ErrToDefaultOk<Maybe<OriginMetadata>>));
if (!maybeMetadata) {
// Unknown directories during listing are allowed. Just warn if we find
// them.
QM_TRY_INSPECT(const auto& leafName,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aOriginDir,
GetLeafName));
UNKNOWN_FILE_WARNING(leafName);
return NS_OK;
}
auto metadata = maybeMetadata.extract();
if (aQuotaManager.IsOriginInternal(metadata.mOrigin)) {
return NS_OK;
}
mOrigins.AppendElement(std::move(metadata.mOrigin));
return NS_OK;
}
void ListOriginsOp::GetResponse(RequestResponse& aResponse) {
AssertIsOnOwningThread();
aResponse = ListOriginsResponse();
if (mOrigins.IsEmpty()) {
return;
}
nsTArray<nsCString>& origins = aResponse.get_ListOriginsResponse().origins();
mOrigins.SwapElements(origins);
}
} // namespace mozilla::dom::quota