/* -*- 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" #include "mozilla/Unused.h" #include "mozilla/dom/PBackgroundSDBConnectionParent.h" #include "mozilla/dom/PBackgroundSDBRequestParent.h" #include "mozilla/dom/quota/Client.h" #include "mozilla/dom/quota/FileStreams.h" #include "mozilla/dom/quota/MemoryOutputStream.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/UsageInfo.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsIFileStreams.h" #include "nsIDirectoryEnumerator.h" #include "nsStringStream.h" #include "prio.h" #include "SimpleDBCommon.h" #define DISABLE_ASSERTS_FOR_FUZZING 0 #if DISABLE_ASSERTS_FOR_FUZZING #define ASSERT_UNLESS_FUZZING(...) do { } while (0) #else #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) #endif namespace mozilla { namespace dom { using namespace mozilla::dom::quota; using namespace mozilla::ipc; namespace { /******************************************************************************* * Constants ******************************************************************************/ const uint32_t kCopyBufferSize = 32768; /******************************************************************************* * Actor class declarations ******************************************************************************/ class StreamHelper final : public Runnable { nsCOMPtr mOwningEventTarget; nsCOMPtr mFileStream; nsCOMPtr mCallback; public: StreamHelper(nsIFileStream* aFileStream, nsIRunnable* aCallback); void AsyncClose(); private: ~StreamHelper() override; void RunOnBackgroundThread(); void RunOnIOThread(); NS_DECL_NSIRUNNABLE }; class Connection final : public PBackgroundSDBConnectionParent { RefPtr mDirectoryLock; nsCOMPtr mFileStream; const PrincipalInfo mPrincipalInfo; nsCString mOrigin; nsString mName; bool mRunningRequest; bool mOpen; bool mAllowedToClose; bool mActorDestroyed; public: explicit Connection(const PrincipalInfo& aPrincipalInfo); NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::Connection) nsIFileStream* GetFileStream() const { AssertIsOnIOThread(); return mFileStream; } const PrincipalInfo& GetPrincipalInfo() const { MOZ_ASSERT(NS_IsMainThread()); 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 aDirectoryLock, already_AddRefed aFileStream); 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 mOwningEventTarget; RefPtr mConnection; nsresult mResultCode; Atomic 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(GetCurrentThreadEventTarget()) , mConnection(aConnection) , mResultCode(NS_OK) , mOperationMayProceed(true) , mActorDestroyed(false) { AssertIsOnOwningThread(); } ~ConnectionOperationBase() override; void SendResults(); void DatabaseWork(); // Methods that subclasses must implement. virtual nsresult DoDatabaseWork(nsIFileStream* aFileStream) = 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, // Opening directory or initializing quota manager on the PBackground // thread. Next step is either DirectoryOpenPending if quota manager is // already initialized or QuotaManagerPending if quota manager needs to be // initialized. FinishOpen, // Waiting for quota manager initialization to complete on the PBackground // thread. Next step is either SendingResults if initialization failed or // DirectoryOpenPending if initialization succeeded. QuotaManagerPending, // 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 mDirectoryLock; nsCOMPtr mFileStream; nsCString mSuffix; nsCString mGroup; nsCString mOrigin; State mState; bool mFileStreamOpen; public: OpenOp(Connection* aConnection, const SDBRequestParams& aParams); nsresult Dispatch() override; private: ~OpenOp() override; nsresult Open(); nsresult FinishOpen(); nsresult QuotaManagerOpen(); nsresult OpenDirectory(); nsresult SendToIOThread(); nsresult DatabaseWork(); void StreamClosedCallback(); // ConnectionOperationBase overrides nsresult DoDatabaseWork(nsIFileStream* aFileStream) 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(nsIFileStream* aFileStream) override; void GetResponse(SDBRequestResponse& aResponse) override; }; class ReadOp final : public ConnectionOperationBase { const SDBRequestReadParams mParams; RefPtr mOutputStream; public: ReadOp(Connection* aConnection, const SDBRequestParams& aParams); bool Init() override; private: ~ReadOp() override = default; nsresult DoDatabaseWork(nsIFileStream* aFileStream) override; void GetResponse(SDBRequestResponse& aResponse) override; }; class WriteOp final : public ConnectionOperationBase { const SDBRequestWriteParams mParams; nsCOMPtr mInputStream; uint64_t mSize; public: WriteOp(Connection* aConnection, const SDBRequestParams& aParams); bool Init() override; private: ~WriteOp() override = default; nsresult DoDatabaseWork(nsIFileStream* aFileStream) override; void GetResponse(SDBRequestResponse& aResponse) override; }; class CloseOp final : public ConnectionOperationBase { public: explicit CloseOp(Connection* aConnection); private: ~CloseOp() override = default; nsresult DoDatabaseWork(nsIFileStream* aFileStream) override; void GetResponse(SDBRequestResponse& aResponse) override; void OnSuccess() override; }; /******************************************************************************* * Other class declarations ******************************************************************************/ class QuotaClient final : public mozilla::dom::quota::Client { static QuotaClient* sInstance; bool mShutdownRequested; public: QuotaClient(); static bool IsShuttingDownOnBackgroundThread() { AssertIsOnBackgroundThread(); if (sInstance) { return sInstance->IsShuttingDown(); } return QuotaManager::IsShuttingDown(); } static bool IsShuttingDownOnNonBackgroundThread() { MOZ_ASSERT(!IsOnBackgroundThread()); return QuotaManager::IsShuttingDown(); } bool IsShuttingDown() const { AssertIsOnBackgroundThread(); return mShutdownRequested; } NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override) Type GetType() override; nsresult InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override; nsresult GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override; void OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) override; void ReleaseIOThreadObjects() override; void AbortOperations(const nsACString& aOrigin) override; void AbortOperationsForProcess(ContentParentId aContentParentId) override; void StartIdleMaintenance() override; void StopIdleMaintenance() override; void ShutdownWorkThreads() override; private: ~QuotaClient() override; }; /******************************************************************************* * Globals ******************************************************************************/ typedef nsTArray> ConnectionArray; StaticAutoPtr gOpenConnections; } // namespace /******************************************************************************* * Exported functions ******************************************************************************/ PBackgroundSDBConnectionParent* AllocPBackgroundSDBConnectionParent(const PrincipalInfo& aPrincipalInfo) { AssertIsOnBackgroundThread(); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) { return nullptr; } if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) { ASSERT_UNLESS_FUZZING(); return nullptr; } RefPtr actor = new Connection(aPrincipalInfo); return actor.forget().take(); } bool RecvPBackgroundSDBConnectionConstructor(PBackgroundSDBConnectionParent* aActor, const PrincipalInfo& aPrincipalInfo) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); return true; } bool DeallocPBackgroundSDBConnectionParent(PBackgroundSDBConnectionParent* aActor) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } namespace simpledb { already_AddRefed CreateQuotaClient() { AssertIsOnBackgroundThread(); RefPtr client = new QuotaClient(); return client.forget(); } } // namespace simpledb /******************************************************************************* * StreamHelper ******************************************************************************/ StreamHelper::StreamHelper(nsIFileStream* aFileStream, nsIRunnable* aCallback) : Runnable("dom::StreamHelper") , mOwningEventTarget(GetCurrentThreadEventTarget()) , mFileStream(aFileStream) , mCallback(aCallback) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aFileStream); MOZ_ASSERT(aCallback); } StreamHelper::~StreamHelper() { MOZ_ASSERT(!mFileStream); 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 fileStream; mFileStream.swap(fileStream); nsCOMPtr callback; mCallback.swap(callback); callback->Run(); } void StreamHelper::RunOnIOThread() { AssertIsOnIOThread(); MOZ_ASSERT(mFileStream); nsCOMPtr inputStream = do_QueryInterface(mFileStream); 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(const PrincipalInfo& aPrincipalInfo) : mPrincipalInfo(aPrincipalInfo) , 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 aDirectoryLock, already_AddRefed aFileStream) { AssertIsOnBackgroundThread(); MOZ_ASSERT(!aOrigin.IsEmpty()); MOZ_ASSERT(!aName.IsEmpty()); MOZ_ASSERT(mOrigin.IsEmpty()); MOZ_ASSERT(mName.IsEmpty()); MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!mFileStream); MOZ_ASSERT(!mOpen); mOrigin = aOrigin; mName = aName; mDirectoryLock = aDirectoryLock; mFileStream = aFileStream; mOpen = true; if (!gOpenConnections) { gOpenConnections = new ConnectionArray(); } gOpenConnections->AppendElement(this); } void Connection::OnClose() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(mFileStream); MOZ_ASSERT(mOpen); mOrigin.Truncate(); mName.Truncate(); mDirectoryLock = nullptr; mFileStream = 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 callback = NewRunnableMethod("dom::Connection::OnClose", this, &Connection::OnClose); RefPtr helper = new StreamHelper(mFileStream, 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)) { ASSERT_UNLESS_FUZZING(); return false; } break; } case SDBRequestParams::TSDBRequestSeekParams: case SDBRequestParams::TSDBRequestReadParams: case SDBRequestParams::TSDBRequestWriteParams: case SDBRequestParams::TSDBRequestCloseParams: { if (NS_WARN_IF(!mOpen)) { ASSERT_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))) { ASSERT_UNLESS_FUZZING(); return nullptr; } if (NS_WARN_IF(mRunningRequest)) { ASSERT_UNLESS_FUZZING(); return nullptr; } RefPtr 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(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 actor = dont_AddRef(static_cast(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_FAILURE; } 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_FAILURE; } else { nsIFileStream* fileStream = mConnection->GetFileStream(); MOZ_ASSERT(fileStream); nsresult rv = DoDatabaseWork(fileStream); 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) , mFileStreamOpen(false) { MOZ_ASSERT(aParams.type() == SDBRequestParams::TSDBRequestOpenParams); } OpenOp::~OpenOp() { MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!mFileStream); MOZ_ASSERT(!mFileStreamOpen); 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_FAILURE; } if (NS_WARN_IF(!Preferences::GetBool(kPrefSimpleDBEnabled, false))) { return NS_ERROR_UNEXPECTED; } const PrincipalInfo& principalInfo = GetConnection()->GetPrincipalInfo(); if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) { QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin); } else { MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo); nsresult rv; nsCOMPtr principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mState = State::FinishOpen; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } nsresult OpenOp::FinishOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::FinishOpen); if (gOpenConnections) { for (Connection* connection : *gOpenConnections) { if (connection->Origin() == mOrigin && connection->Name() == mParams.name()) { return NS_ERROR_STORAGE_BUSY; } } } if (QuotaManager::Get()) { nsresult rv = OpenDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } mState = State::QuotaManagerPending; QuotaManager::GetOrCreate(this); return NS_OK; } nsresult OpenOp::QuotaManagerOpen() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::QuotaManagerPending); if (NS_WARN_IF(!QuotaManager::Get())) { return NS_ERROR_FAILURE; } nsresult rv = OpenDirectory(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult OpenOp::OpenDirectory() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::FinishOpen || mState == State::QuotaManagerPending); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); MOZ_ASSERT(QuotaManager::Get()); mState = State::DirectoryOpenPending; QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin, mozilla::dom::quota::Client::SDB, /* aExclusive */ false, this); return NS_OK; } nsresult OpenOp::SendToIOThread() { AssertIsOnOwningThread(); MOZ_ASSERT(mState == State::DirectoryOpenPending); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || IsActorDestroyed()) { return NS_ERROR_FAILURE; } mFileStream = new FileStream(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin); 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(mFileStream); MOZ_ASSERT(!mFileStreamOpen); if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || !OperationMayProceed()) { return NS_ERROR_FAILURE; } QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsCOMPtr dbDirectory; nsresult rv = quotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin, getter_AddRefs(dbDirectory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = dbDirectory->Append(NS_LITERAL_STRING(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 dbFile; rv = dbDirectory->Clone(getter_AddRefs(dbFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = dbFile->Append(mParams.name()); 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 = mFileStream->Init(dbFile, PR_RDWR | PR_CREATE_FILE, 0644, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mFileStreamOpen = true; rv = DoDatabaseWork(mFileStream); 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(mFileStream); MOZ_ASSERT(mFileStreamOpen); mDirectoryLock = nullptr; mFileStream = nullptr; mFileStreamOpen = false; } nsresult OpenOp::DoDatabaseWork(nsIFileStream* aFileStream) { AssertIsOnIOThread(); return NS_OK; } void OpenOp::GetResponse(SDBRequestResponse& aResponse) { AssertIsOnOwningThread(); aResponse = SDBRequestOpenResponse(); } void OpenOp::OnSuccess() { AssertIsOnOwningThread(); MOZ_ASSERT(NS_SUCCEEDED(ResultCode())); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(mFileStream); MOZ_ASSERT(mFileStreamOpen); RefPtr directoryLock; nsCOMPtr fileStream; mDirectoryLock.swap(directoryLock); mFileStream.swap(fileStream); mFileStreamOpen = false; GetConnection()->OnOpen(mOrigin, mParams.name(), directoryLock.forget(), fileStream.forget()); } void OpenOp::Cleanup() { AssertIsOnOwningThread(); MOZ_ASSERT_IF(mFileStreamOpen, mFileStream); if (mFileStream && mFileStreamOpen) { // 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 callback = NewRunnableMethod("dom::OpenOp::StreamClosedCallback", this, &OpenOp::StreamClosedCallback); RefPtr helper = new StreamHelper(mFileStream, callback); helper->AsyncClose(); } else { MOZ_ASSERT(!mFileStreamOpen); mDirectoryLock = nullptr; mFileStream = 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::QuotaManagerPending: rv = QuotaManagerOpen(); 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(nsIFileStream* aFileStream) { AssertIsOnIOThread(); MOZ_ASSERT(aFileStream); nsCOMPtr seekableStream = do_QueryInterface(aFileStream); MOZ_ASSERT(seekableStream); nsresult rv = seekableStream->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; } mOutputStream = MemoryOutputStream::Create(mParams.size()); if (NS_WARN_IF(!mOutputStream)) { return false; } return true; } nsresult ReadOp::DoDatabaseWork(nsIFileStream* aFileStream) { AssertIsOnIOThread(); MOZ_ASSERT(aFileStream); nsCOMPtr inputStream = do_QueryInterface(aFileStream); 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(mOutputStream->Data()); } 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 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(nsIFileStream* aFileStream) { AssertIsOnIOThread(); MOZ_ASSERT(aFileStream); nsCOMPtr outputStream = do_QueryInterface(aFileStream); 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(nsIFileStream* aFileStream) { AssertIsOnIOThread(); MOZ_ASSERT(aFileStream); nsCOMPtr inputStream = do_QueryInterface(aFileStream); 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() : mShutdownRequested(false) { 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; } nsresult QuotaClient::InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); if (!aUsageInfo) { return NS_OK; } return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aCanceled, aUsageInfo); } nsresult QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); MOZ_ASSERT(aUsageInfo); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); nsCOMPtr directory; nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(directory)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(directory); rv = directory->Append(NS_LITERAL_STRING(SDB_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } DebugOnly exists; MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); nsCOMPtr entries; rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore && !aCanceled) { nsCOMPtr entry; rv = entries->GetNext(getter_AddRefs(entry)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr file = do_QueryInterface(entry); MOZ_ASSERT(file); int64_t fileSize; rv = file->GetFileSize(&fileSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(fileSize >= 0); aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) { AssertIsOnIOThread(); } void QuotaClient::ReleaseIOThreadObjects() { AssertIsOnIOThread(); } void QuotaClient::AbortOperations(const nsACString& aOrigin) { AssertIsOnBackgroundThread(); if (gOpenConnections) { for (Connection* connection : *gOpenConnections) { if (aOrigin.IsVoid() || connection->Origin() == aOrigin) { connection->AllowToClose(); } } } } void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { AssertIsOnBackgroundThread(); } void QuotaClient::StartIdleMaintenance() { AssertIsOnBackgroundThread(); } void QuotaClient::StopIdleMaintenance() { AssertIsOnBackgroundThread(); } void QuotaClient::ShutdownWorkThreads() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mShutdownRequested); mShutdownRequested = true; if (gOpenConnections) { for (Connection* connection : *gOpenConnections) { connection->AllowToClose(); } MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return !gOpenConnections; })); } } } // namespace dom } // namespace mozilla