diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/simpledb/ActorsParent.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/simpledb/ActorsParent.cpp')
-rw-r--r-- | dom/simpledb/ActorsParent.cpp | 1792 |
1 files changed, 1792 insertions, 0 deletions
diff --git a/dom/simpledb/ActorsParent.cpp b/dom/simpledb/ActorsParent.cpp new file mode 100644 index 0000000000..5b09a16908 --- /dev/null +++ b/dom/simpledb/ActorsParent.cpp @@ -0,0 +1,1792 @@ +/* -*- 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 "ActorsParent.h" + +// Local includes +#include "SimpleDBCommon.h" + +// Global includes +#include <cstdint> +#include <cstdlib> +#include <new> +#include <utility> +#include "ErrorList.h" +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FixedBufferOutputStream.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/Variant.h" +#include "mozilla/dom/PBackgroundSDBConnection.h" +#include "mozilla/dom/PBackgroundSDBConnectionParent.h" +#include "mozilla/dom/PBackgroundSDBRequestParent.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/quota/Client.h" +#include "mozilla/dom/quota/ClientImpl.h" +#include "mozilla/dom/quota/DirectoryLock.h" +#include "mozilla/dom/quota/FileStreams.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIEventTarget.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIRunnable.h" +#include "nsISeekableStream.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsStringStream.h" +#include "nsTArray.h" +#include "nsTLiteralString.h" +#include "nsTStringRepr.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "prio.h" + +namespace mozilla::dom { + +using namespace mozilla::dom::quota; +using namespace mozilla::ipc; + +namespace { + +/******************************************************************************* + * Constants + ******************************************************************************/ + +const uint32_t kCopyBufferSize = 32768; + +constexpr auto kSDBSuffix = u".sdb"_ns; + +/******************************************************************************* + * Actor class declarations + ******************************************************************************/ + +class StreamHelper final : public Runnable { + nsCOMPtr<nsIEventTarget> mOwningEventTarget; + nsCOMPtr<nsIFileRandomAccessStream> mFileRandomAccessStream; + nsCOMPtr<nsIRunnable> mCallback; + + public: + StreamHelper(nsIFileRandomAccessStream* aFileRandomAccessStream, + nsIRunnable* aCallback); + + void AsyncClose(); + + private: + ~StreamHelper() override; + + void RunOnBackgroundThread(); + + void RunOnIOThread(); + + NS_DECL_NSIRUNNABLE +}; + +class Connection final : public PBackgroundSDBConnectionParent { + RefPtr<DirectoryLock> mDirectoryLock; + nsCOMPtr<nsIFileRandomAccessStream> mFileRandomAccessStream; + const PrincipalInfo mPrincipalInfo; + nsCString mOrigin; + nsString mName; + + PersistenceType mPersistenceType; + bool mRunningRequest; + bool mOpen; + bool mAllowedToClose; + bool mActorDestroyed; + + public: + Connection(PersistenceType aPersistenceType, + const PrincipalInfo& aPrincipalInfo); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::Connection) + + Maybe<DirectoryLock&> MaybeDirectoryLockRef() const { + AssertIsOnBackgroundThread(); + + return ToMaybeRef(mDirectoryLock.get()); + } + + nsIFileRandomAccessStream* GetFileRandomAccessStream() const { + AssertIsOnIOThread(); + + return mFileRandomAccessStream; + } + + PersistenceType GetPersistenceType() const { return mPersistenceType; } + + const PrincipalInfo& GetPrincipalInfo() const { + AssertIsOnBackgroundThread(); + + return mPrincipalInfo; + } + + const nsCString& Origin() const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mOrigin.IsEmpty()); + + return mOrigin; + } + + const nsString& Name() const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mName.IsEmpty()); + + return mName; + } + + void OnNewRequest(); + + void OnRequestFinished(); + + void OnOpen( + const nsACString& aOrigin, const nsAString& aName, + already_AddRefed<DirectoryLock> aDirectoryLock, + already_AddRefed<nsIFileRandomAccessStream> aFileRandomAccessStream); + + void OnClose(); + + void AllowToClose(); + + private: + ~Connection(); + + void MaybeCloseStream(); + + bool VerifyRequestParams(const SDBRequestParams& aParams) const; + + // IPDL methods. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvDeleteMe() override; + + virtual PBackgroundSDBRequestParent* AllocPBackgroundSDBRequestParent( + const SDBRequestParams& aParams) override; + + virtual mozilla::ipc::IPCResult RecvPBackgroundSDBRequestConstructor( + PBackgroundSDBRequestParent* aActor, + const SDBRequestParams& aParams) override; + + virtual bool DeallocPBackgroundSDBRequestParent( + PBackgroundSDBRequestParent* aActor) override; +}; + +class ConnectionOperationBase : public Runnable, + public PBackgroundSDBRequestParent { + nsCOMPtr<nsIEventTarget> mOwningEventTarget; + RefPtr<Connection> mConnection; + nsresult mResultCode; + Atomic<bool> mOperationMayProceed; + bool mActorDestroyed; + + public: + nsIEventTarget* OwningEventTarget() const { + MOZ_ASSERT(mOwningEventTarget); + + return mOwningEventTarget; + } + + bool IsOnOwningThread() const { + MOZ_ASSERT(mOwningEventTarget); + + bool current; + return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && + current; + } + + void AssertIsOnOwningThread() const { + MOZ_ASSERT(IsOnBackgroundThread()); + MOZ_ASSERT(IsOnOwningThread()); + } + + Connection* GetConnection() const { + MOZ_ASSERT(mConnection); + + return mConnection; + } + + nsresult ResultCode() const { return mResultCode; } + + void MaybeSetFailureCode(nsresult aErrorCode) { + MOZ_ASSERT(NS_FAILED(aErrorCode)); + + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = aErrorCode; + } + } + + // May be called on any thread, but you should call IsActorDestroyed() if + // you know you're on the background thread because it is slightly faster. + bool OperationMayProceed() const { return mOperationMayProceed; } + + bool IsActorDestroyed() const { + AssertIsOnOwningThread(); + + return mActorDestroyed; + } + + // May be overridden by subclasses if they need to perform work on the + // background thread before being dispatched but must always call the base + // class implementation. Returning false will kill the child actors and + // prevent dispatch. + virtual bool Init(); + + virtual nsresult Dispatch(); + + // This callback will be called on the background thread before releasing the + // final reference to this request object. Subclasses may perform any + // additional cleanup here but must always call the base class implementation. + virtual void Cleanup(); + + protected: + ConnectionOperationBase(Connection* aConnection) + : Runnable("dom::ConnectionOperationBase"), + mOwningEventTarget(GetCurrentSerialEventTarget()), + mConnection(aConnection), + mResultCode(NS_OK), + mOperationMayProceed(true), + mActorDestroyed(false) { + AssertIsOnOwningThread(); + } + + ~ConnectionOperationBase() override; + + void SendResults(); + + void DatabaseWork(); + + // Methods that subclasses must implement. + virtual nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) = 0; + + // Subclasses use this override to set the IPDL response value. + virtual void GetResponse(SDBRequestResponse& aResponse) = 0; + + // A method that subclasses may implement. + virtual void OnSuccess(); + + private: + NS_IMETHOD + Run() override; + + // IPDL methods. + void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +class OpenOp final : public ConnectionOperationBase, + public OpenDirectoryListener { + enum class State { + // Just created on the PBackground thread, dispatched to the main thread. + // Next step is FinishOpen. + Initial, + + // Ensuring quota manager is created and opening directory on the + // PBackground thread. Next step is either SendingResults if quota manager + // is not available or DirectoryOpenPending if quota manager is available. + FinishOpen, + + // Waiting for directory open allowed on the PBackground thread. The next + // step is either SendingResults if directory lock failed to acquire, or + // DatabaseWorkOpen if directory lock is acquired. + DirectoryOpenPending, + + // Waiting to do/doing work on the QuotaManager IO thread. Its next step is + // SendingResults. + DatabaseWorkOpen, + + // Waiting to send/sending results on the PBackground thread. Next step is + // Completed. + SendingResults, + + // All done. + Completed + }; + + const SDBRequestOpenParams mParams; + RefPtr<DirectoryLock> mDirectoryLock; + nsCOMPtr<nsIFileRandomAccessStream> mFileRandomAccessStream; + // XXX Consider changing this to ClientMetadata. + quota::OriginMetadata mOriginMetadata; + State mState; + bool mFileRandomAccessStreamOpen; + + public: + OpenOp(Connection* aConnection, const SDBRequestParams& aParams); + + nsresult Dispatch() override; + + private: + ~OpenOp() override; + + nsresult Open(); + + nsresult FinishOpen(); + + nsresult SendToIOThread(); + + nsresult DatabaseWork(); + + void StreamClosedCallback(); + + // ConnectionOperationBase overrides + nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) override; + + void GetResponse(SDBRequestResponse& aResponse) override; + + void OnSuccess() override; + + void Cleanup() override; + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + Run() override; + + // OpenDirectoryListener overrides. + void DirectoryLockAcquired(DirectoryLock* aLock) override; + + void DirectoryLockFailed() override; +}; + +class SeekOp final : public ConnectionOperationBase { + const SDBRequestSeekParams mParams; + + public: + SeekOp(Connection* aConnection, const SDBRequestParams& aParams); + + private: + ~SeekOp() override = default; + + nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) override; + + void GetResponse(SDBRequestResponse& aResponse) override; +}; + +class ReadOp final : public ConnectionOperationBase { + const SDBRequestReadParams mParams; + + RefPtr<FixedBufferOutputStream> mOutputStream; + + public: + ReadOp(Connection* aConnection, const SDBRequestParams& aParams); + + bool Init() override; + + private: + ~ReadOp() override = default; + + nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) override; + + void GetResponse(SDBRequestResponse& aResponse) override; +}; + +class WriteOp final : public ConnectionOperationBase { + const SDBRequestWriteParams mParams; + + nsCOMPtr<nsIInputStream> mInputStream; + + uint64_t mSize; + + public: + WriteOp(Connection* aConnection, const SDBRequestParams& aParams); + + bool Init() override; + + private: + ~WriteOp() override = default; + + nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) override; + + void GetResponse(SDBRequestResponse& aResponse) override; +}; + +class CloseOp final : public ConnectionOperationBase { + public: + explicit CloseOp(Connection* aConnection); + + private: + ~CloseOp() override = default; + + nsresult DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) override; + + void GetResponse(SDBRequestResponse& aResponse) override; + + void OnSuccess() override; +}; + +/******************************************************************************* + * Other class declarations + ******************************************************************************/ + +class QuotaClient final : public mozilla::dom::quota::Client { + static QuotaClient* sInstance; + + public: + QuotaClient(); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override) + + Type GetType() override; + + Result<UsageInfo, nsresult> InitOrigin(PersistenceType aPersistenceType, + const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + nsresult InitOriginWithoutTracking(PersistenceType aPersistenceType, + const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + Result<UsageInfo, nsresult> GetUsageForOrigin( + PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + void OnOriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin) override; + + void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override; + + void ReleaseIOThreadObjects() override; + + void AbortOperationsForLocks( + const DirectoryLockIdTable& aDirectoryLockIds) override; + + void AbortOperationsForProcess(ContentParentId aContentParentId) override; + + void AbortAllOperations() override; + + void StartIdleMaintenance() override; + + void StopIdleMaintenance() override; + + private: + ~QuotaClient() override; + + void InitiateShutdown() override; + bool IsShutdownCompleted() const override; + nsCString GetShutdownStatus() const override; + void ForceKillActors() override; + void FinalizeShutdown() override; +}; + +/******************************************************************************* + * Globals + ******************************************************************************/ + +using ConnectionArray = nsTArray<NotNull<RefPtr<Connection>>>; + +StaticAutoPtr<ConnectionArray> gOpenConnections; + +template <typename Condition> +void AllowToCloseConnectionsMatching(const Condition& aCondition) { + AssertIsOnBackgroundThread(); + + if (gOpenConnections) { + for (const auto& connection : *gOpenConnections) { + if (aCondition(*connection)) { + connection->AllowToClose(); + } + } + } +} + +} // namespace + +/******************************************************************************* + * Exported functions + ******************************************************************************/ + +PBackgroundSDBConnectionParent* AllocPBackgroundSDBConnectionParent( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { + return nullptr; + } + + if (NS_WARN_IF(!IsValidPersistenceType(aPersistenceType))) { + MOZ_CRASH_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) { + MOZ_CRASH_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) { + MOZ_CRASH_UNLESS_FUZZING(); + return nullptr; + } + + RefPtr<Connection> actor = new Connection(aPersistenceType, aPrincipalInfo); + + return actor.forget().take(); +} + +bool RecvPBackgroundSDBConnectionConstructor( + PBackgroundSDBConnectionParent* aActor, + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); + + return true; +} + +bool DeallocPBackgroundSDBConnectionParent( + PBackgroundSDBConnectionParent* aActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<Connection> actor = dont_AddRef(static_cast<Connection*>(aActor)); + return true; +} + +namespace simpledb { + +already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() { + AssertIsOnBackgroundThread(); + + RefPtr<QuotaClient> client = new QuotaClient(); + return client.forget(); +} + +} // namespace simpledb + +/******************************************************************************* + * StreamHelper + ******************************************************************************/ + +StreamHelper::StreamHelper(nsIFileRandomAccessStream* aFileRandomAccessStream, + nsIRunnable* aCallback) + : Runnable("dom::StreamHelper"), + mOwningEventTarget(GetCurrentSerialEventTarget()), + mFileRandomAccessStream(aFileRandomAccessStream), + mCallback(aCallback) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileRandomAccessStream); + MOZ_ASSERT(aCallback); +} + +StreamHelper::~StreamHelper() { + MOZ_ASSERT(!mFileRandomAccessStream); + MOZ_ASSERT(!mCallback); +} + +void StreamHelper::AsyncClose() { + AssertIsOnBackgroundThread(); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MOZ_ALWAYS_SUCCEEDS( + quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void StreamHelper::RunOnBackgroundThread() { + AssertIsOnBackgroundThread(); + + nsCOMPtr<nsIFileRandomAccessStream> fileRandomAccessStream; + mFileRandomAccessStream.swap(fileRandomAccessStream); + + nsCOMPtr<nsIRunnable> callback; + mCallback.swap(callback); + + callback->Run(); +} + +void StreamHelper::RunOnIOThread() { + AssertIsOnIOThread(); + MOZ_ASSERT(mFileRandomAccessStream); + + nsCOMPtr<nsIInputStream> inputStream = + do_QueryInterface(mFileRandomAccessStream); + MOZ_ASSERT(inputStream); + + nsresult rv = inputStream->Close(); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +NS_IMETHODIMP +StreamHelper::Run() { + MOZ_ASSERT(mCallback); + + if (IsOnBackgroundThread()) { + RunOnBackgroundThread(); + } else { + RunOnIOThread(); + } + + return NS_OK; +} + +/******************************************************************************* + * Connection + ******************************************************************************/ + +Connection::Connection(PersistenceType aPersistenceType, + const PrincipalInfo& aPrincipalInfo) + : mPrincipalInfo(aPrincipalInfo), + mPersistenceType(aPersistenceType), + mRunningRequest(false), + mOpen(false), + mAllowedToClose(false), + mActorDestroyed(false) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); +} + +Connection::~Connection() { + MOZ_ASSERT(!mRunningRequest); + MOZ_ASSERT(!mOpen); + MOZ_ASSERT(mActorDestroyed); +} + +void Connection::OnNewRequest() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mRunningRequest); + + mRunningRequest = true; +} + +void Connection::OnRequestFinished() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRunningRequest); + + mRunningRequest = false; + + MaybeCloseStream(); +} + +void Connection::OnOpen( + const nsACString& aOrigin, const nsAString& aName, + already_AddRefed<DirectoryLock> aDirectoryLock, + already_AddRefed<nsIFileRandomAccessStream> aFileRandomAccessStream) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!aOrigin.IsEmpty()); + MOZ_ASSERT(!aName.IsEmpty()); + MOZ_ASSERT(mOrigin.IsEmpty()); + MOZ_ASSERT(mName.IsEmpty()); + MOZ_ASSERT(!mDirectoryLock); + MOZ_ASSERT(!mFileRandomAccessStream); + MOZ_ASSERT(!mOpen); + + mOrigin = aOrigin; + mName = aName; + mDirectoryLock = aDirectoryLock; + mFileRandomAccessStream = aFileRandomAccessStream; + mOpen = true; + + if (!gOpenConnections) { + gOpenConnections = new ConnectionArray(); + } + + gOpenConnections->AppendElement(WrapNotNullUnchecked(this)); +} + +void Connection::OnClose() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mOrigin.IsEmpty()); + MOZ_ASSERT(mDirectoryLock); + MOZ_ASSERT(mFileRandomAccessStream); + MOZ_ASSERT(mOpen); + + mOrigin.Truncate(); + mName.Truncate(); + mDirectoryLock = nullptr; + mFileRandomAccessStream = nullptr; + mOpen = false; + + MOZ_ASSERT(gOpenConnections); + gOpenConnections->RemoveElement(this); + + if (gOpenConnections->IsEmpty()) { + gOpenConnections = nullptr; + } + + if (mAllowedToClose && !mActorDestroyed) { + Unused << SendClosed(); + } +} + +void Connection::AllowToClose() { + AssertIsOnBackgroundThread(); + + if (mAllowedToClose) { + return; + } + + mAllowedToClose = true; + + if (!mActorDestroyed) { + Unused << SendAllowToClose(); + } + + MaybeCloseStream(); +} + +void Connection::MaybeCloseStream() { + AssertIsOnBackgroundThread(); + + if (!mRunningRequest && mOpen && mAllowedToClose) { + nsCOMPtr<nsIRunnable> callback = NewRunnableMethod( + "dom::Connection::OnClose", this, &Connection::OnClose); + + RefPtr<StreamHelper> helper = + new StreamHelper(mFileRandomAccessStream, callback); + helper->AsyncClose(); + } +} + +bool Connection::VerifyRequestParams(const SDBRequestParams& aParams) const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None); + + switch (aParams.type()) { + case SDBRequestParams::TSDBRequestOpenParams: { + if (NS_WARN_IF(mOpen)) { + MOZ_CRASH_UNLESS_FUZZING(); + return false; + } + + break; + } + + case SDBRequestParams::TSDBRequestSeekParams: + case SDBRequestParams::TSDBRequestReadParams: + case SDBRequestParams::TSDBRequestWriteParams: + case SDBRequestParams::TSDBRequestCloseParams: { + if (NS_WARN_IF(!mOpen)) { + MOZ_CRASH_UNLESS_FUZZING(); + return false; + } + + break; + } + + default: + MOZ_CRASH("Should never get here!"); + } + + return true; +} + +void Connection::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActorDestroyed); + + mActorDestroyed = true; + + AllowToClose(); +} + +mozilla::ipc::IPCResult Connection::RecvDeleteMe() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActorDestroyed); + + IProtocol* mgr = Manager(); + if (!PBackgroundSDBConnectionParent::Send__delete__(this)) { + return IPC_FAIL_NO_REASON(mgr); + } + + return IPC_OK(); +} + +PBackgroundSDBRequestParent* Connection::AllocPBackgroundSDBRequestParent( + const SDBRequestParams& aParams) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None); + + if (aParams.type() == SDBRequestParams::TSDBRequestOpenParams && + NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { + return nullptr; + } + + if (mAllowedToClose) { + return nullptr; + } + +#ifdef DEBUG + // Always verify parameters in DEBUG builds! + bool trustParams = false; +#else + PBackgroundParent* backgroundActor = Manager(); + MOZ_ASSERT(backgroundActor); + + bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor); +#endif + + if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) { + MOZ_CRASH_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(mRunningRequest)) { + MOZ_CRASH_UNLESS_FUZZING(); + return nullptr; + } + + QM_TRY(QuotaManager::EnsureCreated(), nullptr); + + RefPtr<ConnectionOperationBase> actor; + + switch (aParams.type()) { + case SDBRequestParams::TSDBRequestOpenParams: + actor = new OpenOp(this, aParams); + break; + + case SDBRequestParams::TSDBRequestSeekParams: + actor = new SeekOp(this, aParams); + break; + + case SDBRequestParams::TSDBRequestReadParams: + actor = new ReadOp(this, aParams); + break; + + case SDBRequestParams::TSDBRequestWriteParams: + actor = new WriteOp(this, aParams); + break; + + case SDBRequestParams::TSDBRequestCloseParams: + actor = new CloseOp(this); + break; + + default: + MOZ_CRASH("Should never get here!"); + } + + // Transfer ownership to IPDL. + return actor.forget().take(); +} + +mozilla::ipc::IPCResult Connection::RecvPBackgroundSDBRequestConstructor( + PBackgroundSDBRequestParent* aActor, const SDBRequestParams& aParams) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aParams.type() != SDBRequestParams::T__None); + MOZ_ASSERT_IF(aParams.type() == SDBRequestParams::TSDBRequestOpenParams, + !QuotaClient::IsShuttingDownOnBackgroundThread()); + MOZ_ASSERT(!mAllowedToClose); + MOZ_ASSERT(!mRunningRequest); + + auto* op = static_cast<ConnectionOperationBase*>(aActor); + + if (NS_WARN_IF(!op->Init())) { + op->Cleanup(); + return IPC_FAIL_NO_REASON(this); + } + + if (NS_WARN_IF(NS_FAILED(op->Dispatch()))) { + op->Cleanup(); + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +bool Connection::DeallocPBackgroundSDBRequestParent( + PBackgroundSDBRequestParent* aActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + // Transfer ownership back from IPDL. + RefPtr<ConnectionOperationBase> actor = + dont_AddRef(static_cast<ConnectionOperationBase*>(aActor)); + return true; +} + +/******************************************************************************* + * ConnectionOperationBase + ******************************************************************************/ + +ConnectionOperationBase::~ConnectionOperationBase() { + MOZ_ASSERT( + !mConnection, + "ConnectionOperationBase::Cleanup() was not called by a subclass!"); + MOZ_ASSERT(mActorDestroyed); +} + +bool ConnectionOperationBase::Init() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mConnection); + + mConnection->OnNewRequest(); + + return true; +} + +nsresult ConnectionOperationBase::Dispatch() { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsActorDestroyed()) { + return NS_ERROR_ABORT; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void ConnectionOperationBase::Cleanup() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mConnection); + + mConnection->OnRequestFinished(); + + mConnection = nullptr; +} + +void ConnectionOperationBase::SendResults() { + AssertIsOnOwningThread(); + + if (IsActorDestroyed()) { + MaybeSetFailureCode(NS_ERROR_FAILURE); + } else { + SDBRequestResponse response; + + if (NS_SUCCEEDED(mResultCode)) { + GetResponse(response); + + MOZ_ASSERT(response.type() != SDBRequestResponse::T__None); + MOZ_ASSERT(response.type() != SDBRequestResponse::Tnsresult); + } else { + response = mResultCode; + } + + Unused << PBackgroundSDBRequestParent::Send__delete__(this, response); + + if (NS_SUCCEEDED(mResultCode)) { + OnSuccess(); + } + } + + Cleanup(); +} + +void ConnectionOperationBase::DatabaseWork() { + AssertIsOnIOThread(); + MOZ_ASSERT(NS_SUCCEEDED(mResultCode)); + + if (!OperationMayProceed()) { + // The operation was canceled in some way, likely because the child process + // has crashed. + mResultCode = NS_ERROR_ABORT; + } else { + nsIFileRandomAccessStream* fileRandomAccessStream = + mConnection->GetFileRandomAccessStream(); + MOZ_ASSERT(fileRandomAccessStream); + + nsresult rv = DoDatabaseWork(fileRandomAccessStream); + if (NS_FAILED(rv)) { + mResultCode = rv; + } + } + + MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void ConnectionOperationBase::OnSuccess() { AssertIsOnOwningThread(); } + +NS_IMETHODIMP +ConnectionOperationBase::Run() { + if (IsOnBackgroundThread()) { + SendResults(); + } else { + DatabaseWork(); + } + + return NS_OK; +} + +void ConnectionOperationBase::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnBackgroundThread(); + + mOperationMayProceed = false; + mActorDestroyed = true; +} + +OpenOp::OpenOp(Connection* aConnection, const SDBRequestParams& aParams) + : ConnectionOperationBase(aConnection), + mParams(aParams.get_SDBRequestOpenParams()), + mState(State::Initial), + mFileRandomAccessStreamOpen(false) { + MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestOpenParams); +} + +OpenOp::~OpenOp() { + MOZ_ASSERT(!mDirectoryLock); + MOZ_ASSERT(!mFileRandomAccessStream); + MOZ_ASSERT(!mFileRandomAccessStreamOpen); + MOZ_ASSERT_IF(OperationMayProceed(), + mState == State::Initial || mState == State::Completed); +} + +nsresult OpenOp::Dispatch() { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + + return NS_OK; +} + +nsresult OpenOp::Open() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mState == State::Initial); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + !OperationMayProceed()) { + return NS_ERROR_ABORT; + } + + if (NS_WARN_IF(!Preferences::GetBool(kPrefSimpleDBEnabled, false))) { + return NS_ERROR_UNEXPECTED; + } + + mState = State::FinishOpen; + MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); + + return NS_OK; +} + +nsresult OpenOp::FinishOpen() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mOriginMetadata.mOrigin.IsEmpty()); + MOZ_ASSERT(!mDirectoryLock); + MOZ_ASSERT(mState == State::FinishOpen); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsActorDestroyed()) { + return NS_ERROR_ABORT; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + const PrincipalInfo& principalInfo = GetConnection()->GetPrincipalInfo(); + + PersistenceType persistenceType = GetConnection()->GetPersistenceType(); + + if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { + mOriginMetadata = {QuotaManager::GetInfoForChrome(), persistenceType}; + } else { + MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); + + QM_TRY_UNWRAP( + auto principalMetadata, + quotaManager->GetInfoFromValidatedPrincipalInfo(principalInfo)); + + mOriginMetadata = {std::move(principalMetadata), persistenceType}; + } + + if (gOpenConnections) { + for (const auto& connection : *gOpenConnections) { + if (connection->Origin() == mOriginMetadata.mOrigin && + connection->Name() == mParams.name()) { + return NS_ERROR_STORAGE_BUSY; + } + } + } + + // Open the directory + + RefPtr<DirectoryLock> directoryLock = quotaManager->CreateDirectoryLock( + GetConnection()->GetPersistenceType(), mOriginMetadata, + mozilla::dom::quota::Client::SDB, + /* aExclusive */ false); + + mState = State::DirectoryOpenPending; + directoryLock->Acquire(this); + + return NS_OK; +} + +nsresult OpenOp::SendToIOThread() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsActorDestroyed()) { + return NS_ERROR_ABORT; + } + + mFileRandomAccessStream = new FileRandomAccessStream( + GetConnection()->GetPersistenceType(), mOriginMetadata, + mozilla::dom::quota::Client::SDB); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + // Must set this before dispatching otherwise we will race with the IO thread. + mState = State::DatabaseWorkOpen; + + nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult OpenOp::DatabaseWork() { + AssertIsOnIOThread(); + MOZ_ASSERT(mState == State::DatabaseWorkOpen); + MOZ_ASSERT(mFileRandomAccessStream); + MOZ_ASSERT(!mFileRandomAccessStreamOpen); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + !OperationMayProceed()) { + return NS_ERROR_ABORT; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized())); + + QM_TRY_INSPECT( + const auto& dbDirectory, + ([persistenceType = GetConnection()->GetPersistenceType(), "aManager, + this]() + -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> { + if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) { + QM_TRY_RETURN(quotaManager->EnsurePersistentOriginIsInitialized( + mOriginMetadata)); + } + + QM_TRY( + MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized())); + QM_TRY_RETURN(quotaManager->EnsureTemporaryOriginIsInitialized( + persistenceType, mOriginMetadata)); + }() + .map([](const auto& res) { return res.first; }))); + + nsresult rv = + dbDirectory->Append(NS_LITERAL_STRING_FROM_CSTRING(SDB_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = dbDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } +#ifdef DEBUG + else { + bool isDirectory; + MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory))); + MOZ_ASSERT(isDirectory); + } +#endif + + nsCOMPtr<nsIFile> dbFile; + rv = dbDirectory->Clone(getter_AddRefs(dbFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = dbFile->Append(mParams.name() + kSDBSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString databaseFilePath; + rv = dbFile->GetPath(databaseFilePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mFileRandomAccessStream->Init(dbFile, PR_RDWR | PR_CREATE_FILE, 0644, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mFileRandomAccessStreamOpen = true; + + rv = DoDatabaseWork(mFileRandomAccessStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + mState = State::SendingResults; + + rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void OpenOp::StreamClosedCallback() { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(ResultCode())); + MOZ_ASSERT(mDirectoryLock); + MOZ_ASSERT(mFileRandomAccessStream); + MOZ_ASSERT(mFileRandomAccessStreamOpen); + + mDirectoryLock = nullptr; + mFileRandomAccessStream = nullptr; + mFileRandomAccessStreamOpen = false; +} + +nsresult OpenOp::DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) { + AssertIsOnIOThread(); + + return NS_OK; +} + +void OpenOp::GetResponse(SDBRequestResponse& aResponse) { + AssertIsOnOwningThread(); + + aResponse = SDBRequestOpenResponse(); +} + +void OpenOp::OnSuccess() { + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); + MOZ_ASSERT(!mOriginMetadata.mOrigin.IsEmpty()); + MOZ_ASSERT(mDirectoryLock); + MOZ_ASSERT(mFileRandomAccessStream); + MOZ_ASSERT(mFileRandomAccessStreamOpen); + + RefPtr<DirectoryLock> directoryLock; + nsCOMPtr<nsIFileRandomAccessStream> fileRandomAccessStream; + + mDirectoryLock.swap(directoryLock); + mFileRandomAccessStream.swap(fileRandomAccessStream); + mFileRandomAccessStreamOpen = false; + + GetConnection()->OnOpen(mOriginMetadata.mOrigin, mParams.name(), + directoryLock.forget(), + fileRandomAccessStream.forget()); +} + +void OpenOp::Cleanup() { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mFileRandomAccessStreamOpen, mFileRandomAccessStream); + + if (mFileRandomAccessStream && mFileRandomAccessStreamOpen) { + // If we have an initialized file stream then the operation must have failed + // and there must be a directory lock too. + MOZ_ASSERT(NS_FAILED(ResultCode())); + MOZ_ASSERT(mDirectoryLock); + + // We must close the stream on the I/O thread before releasing it on this + // thread. The directory lock can't be released either. + nsCOMPtr<nsIRunnable> callback = + NewRunnableMethod("dom::OpenOp::StreamClosedCallback", this, + &OpenOp::StreamClosedCallback); + + RefPtr<StreamHelper> helper = + new StreamHelper(mFileRandomAccessStream, callback); + helper->AsyncClose(); + } else { + MOZ_ASSERT(!mFileRandomAccessStreamOpen); + + mDirectoryLock = nullptr; + mFileRandomAccessStream = nullptr; + } + + ConnectionOperationBase::Cleanup(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(OpenOp, ConnectionOperationBase) + +NS_IMETHODIMP +OpenOp::Run() { + nsresult rv; + + switch (mState) { + case State::Initial: + rv = Open(); + break; + + case State::FinishOpen: + rv = FinishOpen(); + break; + + case State::DatabaseWorkOpen: + rv = DatabaseWork(); + break; + + case State::SendingResults: + SendResults(); + return NS_OK; + + default: + MOZ_CRASH("Bad state!"); + } + + if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) { + MaybeSetFailureCode(rv); + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + mState = State::SendingResults; + + if (IsOnOwningThread()) { + SendResults(); + } else { + MOZ_ALWAYS_SUCCEEDS( + OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); + } + } + + return NS_OK; +} + +void OpenOp::DirectoryLockAcquired(DirectoryLock* aLock) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + mDirectoryLock = aLock; + + nsresult rv = SendToIOThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MaybeSetFailureCode(rv); + + // The caller holds a strong reference to us, no need for a self reference + // before calling Run(). + + mState = State::SendingResults; + MOZ_ALWAYS_SUCCEEDS(Run()); + + return; + } +} + +void OpenOp::DirectoryLockFailed() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + MaybeSetFailureCode(NS_ERROR_FAILURE); + + // The caller holds a strong reference to us, no need for a self reference + // before calling Run(). + + mState = State::SendingResults; + MOZ_ALWAYS_SUCCEEDS(Run()); +} + +SeekOp::SeekOp(Connection* aConnection, const SDBRequestParams& aParams) + : ConnectionOperationBase(aConnection), + mParams(aParams.get_SDBRequestSeekParams()) { + MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestSeekParams); +} + +nsresult SeekOp::DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) { + AssertIsOnIOThread(); + MOZ_ASSERT(aFileRandomAccessStream); + + nsresult rv = aFileRandomAccessStream->Seek(nsISeekableStream::NS_SEEK_SET, + mParams.offset()); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void SeekOp::GetResponse(SDBRequestResponse& aResponse) { + aResponse = SDBRequestSeekResponse(); +} + +ReadOp::ReadOp(Connection* aConnection, const SDBRequestParams& aParams) + : ConnectionOperationBase(aConnection), + mParams(aParams.get_SDBRequestReadParams()) { + MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestReadParams); +} + +bool ReadOp::Init() { + AssertIsOnOwningThread(); + + if (NS_WARN_IF(!ConnectionOperationBase::Init())) { + return false; + } + + if (NS_WARN_IF(mParams.size() > std::numeric_limits<std::size_t>::max())) { + return false; + } + + mOutputStream = FixedBufferOutputStream::Create(mParams.size(), fallible); + if (NS_WARN_IF(!mOutputStream)) { + return false; + } + + return true; +} + +nsresult ReadOp::DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) { + AssertIsOnIOThread(); + MOZ_ASSERT(aFileRandomAccessStream); + + nsCOMPtr<nsIInputStream> inputStream = + do_QueryInterface(aFileRandomAccessStream); + MOZ_ASSERT(inputStream); + + nsresult rv; + + uint64_t offset = 0; + + do { + char copyBuffer[kCopyBufferSize]; + + uint64_t max = mParams.size() - offset; + if (max == 0) { + break; + } + + uint32_t count = sizeof(copyBuffer); + if (count > max) { + count = max; + } + + uint32_t numRead; + rv = inputStream->Read(copyBuffer, count, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!numRead) { + break; + } + + uint32_t numWrite; + rv = mOutputStream->Write(copyBuffer, numRead, &numWrite); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(numWrite != numRead)) { + return NS_ERROR_FAILURE; + } + + offset += numWrite; + } while (true); + + MOZ_ASSERT(offset == mParams.size()); + + MOZ_ALWAYS_SUCCEEDS(mOutputStream->Close()); + + return NS_OK; +} + +void ReadOp::GetResponse(SDBRequestResponse& aResponse) { + aResponse = SDBRequestReadResponse(nsCString(mOutputStream->WrittenData())); +} + +WriteOp::WriteOp(Connection* aConnection, const SDBRequestParams& aParams) + : ConnectionOperationBase(aConnection), + mParams(aParams.get_SDBRequestWriteParams()), + mSize(0) { + MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestWriteParams); +} + +bool WriteOp::Init() { + AssertIsOnOwningThread(); + + if (NS_WARN_IF(!ConnectionOperationBase::Init())) { + return false; + } + + const nsCString& string = mParams.data(); + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), string); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + mInputStream = std::move(inputStream); + mSize = string.Length(); + + return true; +} + +nsresult WriteOp::DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) { + AssertIsOnIOThread(); + MOZ_ASSERT(aFileRandomAccessStream); + + nsCOMPtr<nsIOutputStream> outputStream = + do_QueryInterface(aFileRandomAccessStream); + MOZ_ASSERT(outputStream); + + nsresult rv; + + do { + char copyBuffer[kCopyBufferSize]; + + uint32_t numRead; + rv = mInputStream->Read(copyBuffer, sizeof(copyBuffer), &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (!numRead) { + break; + } + + uint32_t numWrite; + rv = outputStream->Write(copyBuffer, numRead, &numWrite); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(numWrite != numRead)) { + return NS_ERROR_FAILURE; + } + } while (true); + + MOZ_ALWAYS_SUCCEEDS(mInputStream->Close()); + + return NS_OK; +} + +void WriteOp::GetResponse(SDBRequestResponse& aResponse) { + aResponse = SDBRequestWriteResponse(); +} + +CloseOp::CloseOp(Connection* aConnection) + : ConnectionOperationBase(aConnection) {} + +nsresult CloseOp::DoDatabaseWork( + nsIFileRandomAccessStream* aFileRandomAccessStream) { + AssertIsOnIOThread(); + MOZ_ASSERT(aFileRandomAccessStream); + + nsCOMPtr<nsIInputStream> inputStream = + do_QueryInterface(aFileRandomAccessStream); + MOZ_ASSERT(inputStream); + + nsresult rv = inputStream->Close(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void CloseOp::GetResponse(SDBRequestResponse& aResponse) { + aResponse = SDBRequestCloseResponse(); +} + +void CloseOp::OnSuccess() { + AssertIsOnOwningThread(); + + GetConnection()->OnClose(); +} + +/******************************************************************************* + * QuotaClient + ******************************************************************************/ + +QuotaClient* QuotaClient::sInstance = nullptr; + +QuotaClient::QuotaClient() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); + + sInstance = this; +} + +QuotaClient::~QuotaClient() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); + + sInstance = nullptr; +} + +mozilla::dom::quota::Client::Type QuotaClient::GetType() { + return QuotaClient::SDB; +} + +Result<UsageInfo, nsresult> QuotaClient::InitOrigin( + PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) { + AssertIsOnIOThread(); + + return GetUsageForOrigin(aPersistenceType, aOriginMetadata, aCanceled); +} + +nsresult QuotaClient::InitOriginWithoutTracking( + PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) { + AssertIsOnIOThread(); + + return NS_OK; +} + +Result<UsageInfo, nsresult> QuotaClient::GetUsageForOrigin( + PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) { + AssertIsOnIOThread(); + MOZ_ASSERT(aOriginMetadata.mPersistenceType == aPersistenceType); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + QM_TRY_UNWRAP(auto directory, + quotaManager->GetOriginDirectory(aOriginMetadata)); + + MOZ_ASSERT(directory); + + nsresult rv = + directory->Append(NS_LITERAL_STRING_FROM_CSTRING(SDB_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + DebugOnly<bool> exists; + MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); + + QM_TRY_RETURN(ReduceEachFileAtomicCancelable( + *directory, aCanceled, UsageInfo{}, + [](UsageInfo usageInfo, + const nsCOMPtr<nsIFile>& file) -> Result<UsageInfo, nsresult> { + QM_TRY_INSPECT(const bool& isDirectory, + MOZ_TO_RESULT_INVOKE_MEMBER(file, IsDirectory)); + + if (isDirectory) { + Unused << WARN_IF_FILE_IS_UNKNOWN(*file); + return usageInfo; + } + + nsString leafName; + QM_TRY(MOZ_TO_RESULT(file->GetLeafName(leafName))); + + if (StringEndsWith(leafName, kSDBSuffix)) { + QM_TRY_INSPECT(const int64_t& fileSize, + MOZ_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)); + + MOZ_ASSERT(fileSize >= 0); + + return usageInfo + + UsageInfo{DatabaseUsageType(Some(uint64_t(fileSize)))}; + } + + Unused << WARN_IF_FILE_IS_UNKNOWN(*file); + + return usageInfo; + })); +} + +void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin) { + AssertIsOnIOThread(); +} + +void QuotaClient::OnRepositoryClearCompleted(PersistenceType aPersistenceType) { + AssertIsOnIOThread(); +} + +void QuotaClient::ReleaseIOThreadObjects() { AssertIsOnIOThread(); } + +void QuotaClient::AbortOperationsForLocks( + const DirectoryLockIdTable& aDirectoryLockIds) { + AssertIsOnBackgroundThread(); + + AllowToCloseConnectionsMatching([&aDirectoryLockIds](const auto& connection) { + // If the connections is registered in gOpenConnections then it must have + // a directory lock. + return IsLockForObjectContainedInLockTable(connection, aDirectoryLockIds); + }); +} + +void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { + AssertIsOnBackgroundThread(); +} + +void QuotaClient::AbortAllOperations() { + AssertIsOnBackgroundThread(); + + AllowToCloseConnectionsMatching([](const auto&) { return true; }); +} + +void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); } + +void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); } + +void QuotaClient::InitiateShutdown() { + AssertIsOnBackgroundThread(); + + if (gOpenConnections) { + for (const auto& connection : *gOpenConnections) { + connection->AllowToClose(); + } + } +} + +bool QuotaClient::IsShutdownCompleted() const { return !gOpenConnections; } + +void QuotaClient::ForceKillActors() { + // Currently we don't implement killing actors (are there any to kill here?). +} + +nsCString QuotaClient::GetShutdownStatus() const { + // XXX Gather information here. + return "To be implemented"_ns; +} + +void QuotaClient::FinalizeShutdown() { + // Nothing to do here. +} + +} // namespace mozilla::dom |