Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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, override)
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(&current)) &&
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 {
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_IMETHOD
Run() override;
void DirectoryLockAcquired(DirectoryLock* aLock);
void DirectoryLockFailed();
};
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
******************************************************************************/
already_AddRefed<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();
}
bool RecvPBackgroundSDBConnectionConstructor(
PBackgroundSDBConnectionParent* aActor,
const PersistenceType& aPersistenceType,
const PrincipalInfo& aPrincipalInfo) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
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
mState = State::DirectoryOpenPending;
quotaManager
->OpenClientDirectory({mOriginMetadata, mozilla::dom::quota::Client::SDB})
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr(this)](
const ClientDirectoryLockPromise::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
self->DirectoryLockAcquired(aValue.ResolveValue());
} else {
self->DirectoryLockFailed();
}
});
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_INSPECT(
const auto& dbDirectory,
([persistenceType = GetConnection()->GetPersistenceType(), &quotaManager,
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->EnsureTemporaryStorageIsInitializedInternal()));
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_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*