summaryrefslogtreecommitdiffstats
path: root/dom/filehandle/ActorsParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/filehandle/ActorsParent.cpp')
-rw-r--r--dom/filehandle/ActorsParent.cpp2666
1 files changed, 2666 insertions, 0 deletions
diff --git a/dom/filehandle/ActorsParent.cpp b/dom/filehandle/ActorsParent.cpp
new file mode 100644
index 000000000..6b81c782f
--- /dev/null
+++ b/dom/filehandle/ActorsParent.cpp
@@ -0,0 +1,2666 @@
+/* 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/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileHandleCommon.h"
+#include "mozilla/dom/PBackgroundFileHandleParent.h"
+#include "mozilla/dom/PBackgroundFileRequestParent.h"
+#include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIEventTarget.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsISeekableStream.h"
+#include "nsIThread.h"
+#include "nsIThreadPool.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.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::ipc;
+
+namespace {
+
+/******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+const uint32_t kStreamCopyBlockSize = 32768;
+
+} // namespace
+
+class FileHandleThreadPool::FileHandleQueue final
+ : public Runnable
+{
+ friend class FileHandleThreadPool;
+
+ RefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+ RefPtr<FileHandle> mFileHandle;
+ nsTArray<RefPtr<FileHandleOp>> mQueue;
+ RefPtr<FileHandleOp> mCurrentOp;
+ bool mShouldFinish;
+
+public:
+ explicit
+ FileHandleQueue(FileHandleThreadPool* aFileHandleThreadPool,
+ FileHandle* aFileHandle);
+
+ void
+ Enqueue(FileHandleOp* aFileHandleOp);
+
+ void
+ Finish();
+
+ void
+ ProcessQueue();
+
+private:
+ ~FileHandleQueue() {}
+
+ NS_DECL_NSIRUNNABLE
+};
+
+struct FileHandleThreadPool::DelayedEnqueueInfo
+{
+ RefPtr<FileHandle> mFileHandle;
+ RefPtr<FileHandleOp> mFileHandleOp;
+ bool mFinish;
+};
+
+class FileHandleThreadPool::DirectoryInfo
+{
+ friend class FileHandleThreadPool;
+
+ RefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+ nsTArray<RefPtr<FileHandleQueue>> mFileHandleQueues;
+ nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
+ nsTHashtable<nsStringHashKey> mFilesReading;
+ nsTHashtable<nsStringHashKey> mFilesWriting;
+
+public:
+ FileHandleQueue*
+ CreateFileHandleQueue(FileHandle* aFileHandle);
+
+ FileHandleQueue*
+ GetFileHandleQueue(FileHandle* aFileHandle);
+
+ void
+ RemoveFileHandleQueue(FileHandle* aFileHandle);
+
+ bool
+ HasRunningFileHandles()
+ {
+ return !mFileHandleQueues.IsEmpty();
+ }
+
+ DelayedEnqueueInfo*
+ CreateDelayedEnqueueInfo(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp,
+ bool aFinish);
+
+ void
+ LockFileForReading(const nsAString& aFileName)
+ {
+ mFilesReading.PutEntry(aFileName);
+ }
+
+ void
+ LockFileForWriting(const nsAString& aFileName)
+ {
+ mFilesWriting.PutEntry(aFileName);
+ }
+
+ bool
+ IsFileLockedForReading(const nsAString& aFileName)
+ {
+ return mFilesReading.Contains(aFileName);
+ }
+
+ bool
+ IsFileLockedForWriting(const nsAString& aFileName)
+ {
+ return mFilesWriting.Contains(aFileName);
+ }
+
+private:
+ explicit DirectoryInfo(FileHandleThreadPool* aFileHandleThreadPool)
+ : mOwningFileHandleThreadPool(aFileHandleThreadPool)
+ { }
+};
+
+struct FileHandleThreadPool::StoragesCompleteCallback final
+{
+ friend class nsAutoPtr<StoragesCompleteCallback>;
+
+ nsTArray<nsCString> mDirectoryIds;
+ nsCOMPtr<nsIRunnable> mCallback;
+
+ StoragesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback);
+
+private:
+ ~StoragesCompleteCallback();
+};
+
+/******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class FileHandle
+ : public PBackgroundFileHandleParent
+{
+ friend class BackgroundMutableFileParentBase;
+
+ class FinishOp;
+
+ RefPtr<BackgroundMutableFileParentBase> mMutableFile;
+ nsCOMPtr<nsISupports> mStream;
+ uint64_t mActiveRequestCount;
+ FileHandleStorage mStorage;
+ Atomic<bool> mInvalidatedOnAnyThread;
+ FileMode mMode;
+ bool mHasBeenActive;
+ bool mActorDestroyed;
+ bool mInvalidated;
+ bool mAborted;
+ bool mFinishOrAbortReceived;
+ bool mFinishedOrAborted;
+ bool mForceAborted;
+
+ DEBUGONLY(nsCOMPtr<nsIEventTarget> mThreadPoolEventTarget;)
+
+public:
+ void
+ AssertIsOnThreadPool() const;
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mActorDestroyed;
+ }
+
+ // Must be called on the background thread.
+ bool
+ IsInvalidated() const
+ {
+ MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
+ MOZ_ASSERT_IF(mInvalidated, mAborted);
+
+ return mInvalidated;
+ }
+
+ // May be called on any thread, but is more expensive than IsInvalidated().
+ bool
+ IsInvalidatedOnAnyThread() const
+ {
+ return mInvalidatedOnAnyThread;
+ }
+
+ void
+ SetActive()
+ {
+ AssertIsOnBackgroundThread();
+
+ mHasBeenActive = true;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::FileHandle)
+
+ nsresult
+ GetOrCreateStream(nsISupports** aStream);
+
+ void
+ Abort(bool aForce);
+
+ FileHandleStorage
+ Storage() const
+ {
+ return mStorage;
+ }
+
+ FileMode
+ Mode() const
+ {
+ return mMode;
+ }
+
+ BackgroundMutableFileParentBase*
+ GetMutableFile() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mMutableFile);
+
+ return mMutableFile;
+ }
+
+ bool
+ IsAborted() const
+ {
+ AssertIsOnBackgroundThread();
+
+ return mAborted;
+ }
+
+ PBackgroundParent*
+ GetBackgroundParent() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return GetMutableFile()->GetBackgroundParent();
+ }
+
+ void
+ NoteActiveRequest();
+
+ void
+ NoteFinishedRequest();
+
+ void
+ Invalidate();
+
+private:
+ // This constructor is only called by BackgroundMutableFileParentBase.
+ FileHandle(BackgroundMutableFileParentBase* aMutableFile,
+ FileMode aMode);
+
+ // Reference counted.
+ ~FileHandle();
+
+ void
+ MaybeFinishOrAbort()
+ {
+ AssertIsOnBackgroundThread();
+
+ // If we've already finished or aborted then there's nothing else to do.
+ if (mFinishedOrAborted) {
+ return;
+ }
+
+ // If there are active requests then we have to wait for those requests to
+ // complete (see NoteFinishedRequest).
+ if (mActiveRequestCount) {
+ return;
+ }
+
+ // If we haven't yet received a finish or abort message then there could be
+ // additional requests coming so we should wait unless we're being forced to
+ // abort.
+ if (!mFinishOrAbortReceived && !mForceAborted) {
+ return;
+ }
+
+ FinishOrAbort();
+ }
+
+ void
+ SendCompleteNotification(bool aAborted);
+
+ bool
+ VerifyRequestParams(const FileRequestParams& aParams) const;
+
+ bool
+ VerifyRequestData(const FileRequestData& aData) const;
+
+ void
+ FinishOrAbort();
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ RecvDeleteMe() override;
+
+ virtual bool
+ RecvFinish() override;
+
+ virtual bool
+ RecvAbort() override;
+
+ virtual PBackgroundFileRequestParent*
+ AllocPBackgroundFileRequestParent(const FileRequestParams& aParams) override;
+
+ virtual bool
+ RecvPBackgroundFileRequestConstructor(PBackgroundFileRequestParent* aActor,
+ const FileRequestParams& aParams)
+ override;
+
+ virtual bool
+ DeallocPBackgroundFileRequestParent(PBackgroundFileRequestParent* aActor)
+ override;
+};
+
+class FileHandleOp
+{
+protected:
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+ RefPtr<FileHandle> mFileHandle;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileHandleOp)
+
+ void
+ AssertIsOnOwningThread() const
+ {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningThread);
+ DebugOnly<bool> current;
+ MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+ }
+
+ nsIEventTarget*
+ OwningThread() const
+ {
+ return mOwningThread;
+ }
+
+ void
+ AssertIsOnThreadPool() const
+ {
+ MOZ_ASSERT(mFileHandle);
+ mFileHandle->AssertIsOnThreadPool();
+ }
+
+ void
+ Enqueue();
+
+ virtual void
+ RunOnThreadPool() = 0;
+
+ virtual void
+ RunOnOwningThread() = 0;
+
+protected:
+ FileHandleOp(FileHandle* aFileHandle)
+ : mOwningThread(NS_GetCurrentThread())
+ , mFileHandle(aFileHandle)
+ {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ virtual
+ ~FileHandleOp()
+ { }
+};
+
+class FileHandle::FinishOp
+ : public FileHandleOp
+{
+ friend class FileHandle;
+
+ bool mAborted;
+
+private:
+ FinishOp(FileHandle* aFileHandle,
+ bool aAborted)
+ : FileHandleOp(aFileHandle)
+ , mAborted(aAborted)
+ {
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ ~FinishOp()
+ { }
+
+ virtual void
+ RunOnThreadPool() override;
+
+ virtual void
+ RunOnOwningThread() override;
+};
+
+class NormalFileHandleOp
+ : public FileHandleOp
+ , public PBackgroundFileRequestParent
+{
+ nsresult mResultCode;
+ Atomic<bool> mOperationMayProceed;
+ bool mActorDestroyed;
+ const bool mFileHandleIsAborted;
+
+ DEBUGONLY(bool mResponseSent;)
+
+protected:
+ nsCOMPtr<nsISupports> mFileStream;
+
+public:
+ void
+ NoteActorDestroyed()
+ {
+ AssertIsOnOwningThread();
+
+ mActorDestroyed = true;
+ mOperationMayProceed = false;
+ }
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnOwningThread();
+
+ return mActorDestroyed;
+ }
+
+ // 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;
+ }
+
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being enqueued. Returning false will kill the
+ // child actors and prevent enqueue.
+ virtual bool
+ Init(FileHandle* aFileHandle);
+
+ // 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:
+ NormalFileHandleOp(FileHandle* aFileHandle)
+ : FileHandleOp(aFileHandle)
+ , mResultCode(NS_OK)
+ , mOperationMayProceed(true)
+ , mActorDestroyed(false)
+ , mFileHandleIsAborted(aFileHandle->IsAborted())
+ DEBUGONLY(, mResponseSent(false))
+ {
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ virtual
+ ~NormalFileHandleOp();
+
+ // Must be overridden in subclasses. Called on the target thread to allow the
+ // subclass to perform necessary file operations. A successful return value
+ // will trigger a SendSuccessResult callback on the background thread while
+ // a failure value will trigger a SendFailureResult callback.
+ virtual nsresult
+ DoFileWork(FileHandle* aFileHandle) = 0;
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) = 0;
+
+private:
+ nsresult
+ SendSuccessResult();
+
+ bool
+ SendFailureResult(nsresult aResultCode);
+
+ virtual void
+ RunOnThreadPool() override;
+
+ virtual void
+ RunOnOwningThread() override;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class CopyFileHandleOp
+ : public NormalFileHandleOp
+{
+ class ProgressRunnable;
+
+protected:
+ nsCOMPtr<nsISupports> mBufferStream;
+
+ uint64_t mOffset;
+ uint64_t mSize;
+
+ bool mRead;
+
+protected:
+ CopyFileHandleOp(FileHandle* aFileHandle)
+ : NormalFileHandleOp(aFileHandle)
+ , mOffset(0)
+ , mSize(0)
+ , mRead(true)
+ { }
+
+ virtual nsresult
+ DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void
+ Cleanup() override;
+};
+
+class CopyFileHandleOp::ProgressRunnable final
+ : public Runnable
+{
+ RefPtr<CopyFileHandleOp> mCopyFileHandleOp;
+ uint64_t mProgress;
+ uint64_t mProgressMax;
+
+public:
+ ProgressRunnable(CopyFileHandleOp* aCopyFileHandleOp,
+ uint64_t aProgress,
+ uint64_t aProgressMax)
+ : mCopyFileHandleOp(aCopyFileHandleOp)
+ , mProgress(aProgress)
+ , mProgressMax(aProgressMax)
+ { }
+
+private:
+ ~ProgressRunnable() {}
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class GetMetadataOp
+ : public NormalFileHandleOp
+{
+ friend class FileHandle;
+
+ const FileRequestGetMetadataParams mParams;
+
+protected:
+ FileRequestMetadata mMetadata;
+
+protected:
+ // Only created by FileHandle.
+ GetMetadataOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~GetMetadataOp()
+ { }
+
+ virtual nsresult
+ DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class ReadOp final
+ : public CopyFileHandleOp
+{
+ friend class FileHandle;
+
+ class MemoryOutputStream;
+
+ const FileRequestReadParams mParams;
+
+private:
+ // Only created by FileHandle.
+ ReadOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~ReadOp()
+ { }
+
+ virtual bool
+ Init(FileHandle* aFileHandle) override;
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class ReadOp::MemoryOutputStream final
+ : public nsIOutputStream
+{
+ nsCString mData;
+ uint64_t mOffset;
+
+public:
+ static already_AddRefed<MemoryOutputStream>
+ Create(uint64_t aSize);
+
+ const nsCString&
+ Data() const
+ {
+ return mData;
+ }
+
+private:
+ MemoryOutputStream()
+ : mOffset(0)
+ { }
+
+ virtual ~MemoryOutputStream()
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+};
+
+class WriteOp final
+ : public CopyFileHandleOp
+{
+ friend class FileHandle;
+
+ const FileRequestWriteParams mParams;
+
+private:
+ // Only created by FileHandle.
+ WriteOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~WriteOp()
+ { }
+
+ virtual bool
+ Init(FileHandle* aFileHandle) override;
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class TruncateOp final
+ : public NormalFileHandleOp
+{
+ friend class FileHandle;
+
+ const FileRequestTruncateParams mParams;
+
+private:
+ // Only created by FileHandle.
+ TruncateOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~TruncateOp()
+ { }
+
+ virtual nsresult
+ DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class FlushOp final
+ : public NormalFileHandleOp
+{
+ friend class FileHandle;
+
+ const FileRequestFlushParams mParams;
+
+private:
+ // Only created by FileHandle.
+ FlushOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~FlushOp()
+ { }
+
+ virtual nsresult
+ DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class GetFileOp final
+ : public GetMetadataOp
+{
+ friend class FileHandle;
+
+ PBackgroundParent* mBackgroundParent;
+
+private:
+ // Only created by FileHandle.
+ GetFileOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams);
+
+ ~GetFileOp()
+ { }
+
+ virtual void
+ GetResponse(FileRequestResponse& aResponse) override;
+};
+
+namespace {
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+FileHandleThreadPool*
+GetFileHandleThreadPoolFor(FileHandleStorage aStorage)
+{
+ switch (aStorage) {
+ case FILE_HANDLE_STORAGE_IDB:
+ return mozilla::dom::indexedDB::GetFileHandleThreadPool();
+
+ default:
+ MOZ_CRASH("Bad file handle storage value!");
+ }
+}
+
+} // namespace
+
+/*******************************************************************************
+ * FileHandleThreadPool implementation
+ ******************************************************************************/
+
+FileHandleThreadPool::FileHandleThreadPool()
+ : mOwningThread(NS_GetCurrentThread())
+ , mShutdownRequested(false)
+ , mShutdownComplete(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningThread);
+ AssertIsOnOwningThread();
+}
+
+FileHandleThreadPool::~FileHandleThreadPool()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+ MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(mShutdownComplete);
+}
+
+// static
+already_AddRefed<FileHandleThreadPool>
+FileHandleThreadPool::Create()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ new FileHandleThreadPool();
+ fileHandleThreadPool->AssertIsOnOwningThread();
+
+ if (NS_WARN_IF(NS_FAILED(fileHandleThreadPool->Init()))) {
+ return nullptr;
+ }
+
+ return fileHandleThreadPool.forget();
+}
+
+#ifdef DEBUG
+
+void
+FileHandleThreadPool::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mOwningThread);
+
+ bool current;
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->IsOnCurrentThread(&current));
+ MOZ_ASSERT(current);
+}
+
+nsIEventTarget*
+FileHandleThreadPool::GetThreadPoolEventTarget() const
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mThreadPool);
+
+ return mThreadPool;
+}
+
+#endif // DEBUG
+
+void
+FileHandleThreadPool::Enqueue(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp,
+ bool aFinish)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(!mShutdownRequested);
+
+ BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+
+ const nsACString& directoryId = mutableFile->DirectoryId();
+ const nsAString& fileName = mutableFile->FileName();
+ bool modeIsWrite = aFileHandle->Mode() == FileMode::Readwrite;
+
+ DirectoryInfo* directoryInfo;
+ if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) {
+ nsAutoPtr<DirectoryInfo> newDirectoryInfo(new DirectoryInfo(this));
+
+ mDirectoryInfos.Put(directoryId, newDirectoryInfo);
+
+ directoryInfo = newDirectoryInfo.forget();
+ }
+
+ FileHandleQueue* existingFileHandleQueue =
+ directoryInfo->GetFileHandleQueue(aFileHandle);
+
+ if (existingFileHandleQueue) {
+ existingFileHandleQueue->Enqueue(aFileHandleOp);
+ if (aFinish) {
+ existingFileHandleQueue->Finish();
+ }
+ return;
+ }
+
+ bool lockedForReading = directoryInfo->IsFileLockedForReading(fileName);
+ bool lockedForWriting = directoryInfo->IsFileLockedForWriting(fileName);
+
+ if (modeIsWrite) {
+ if (!lockedForWriting) {
+ directoryInfo->LockFileForWriting(fileName);
+ }
+ }
+ else {
+ if (!lockedForReading) {
+ directoryInfo->LockFileForReading(fileName);
+ }
+ }
+
+ if (lockedForWriting || (lockedForReading && modeIsWrite)) {
+ directoryInfo->CreateDelayedEnqueueInfo(aFileHandle,
+ aFileHandleOp,
+ aFinish);
+ }
+ else {
+ FileHandleQueue* fileHandleQueue =
+ directoryInfo->CreateFileHandleQueue(aFileHandle);
+
+ if (aFileHandleOp) {
+ fileHandleQueue->Enqueue(aFileHandleOp);
+ if (aFinish) {
+ fileHandleQueue->Finish();
+ }
+ }
+ }
+}
+
+void
+FileHandleThreadPool::WaitForDirectoriesToComplete(
+ nsTArray<nsCString>&& aDirectoryIds,
+ nsIRunnable* aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ nsAutoPtr<StoragesCompleteCallback> callback(
+ new StoragesCompleteCallback(Move(aDirectoryIds), aCallback));
+
+ if (!MaybeFireCallback(callback)) {
+ mCompleteCallbacks.AppendElement(callback.forget());
+ }
+}
+
+void
+FileHandleThreadPool::Shutdown()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+
+ mShutdownRequested = true;
+
+ if (!mThreadPool) {
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+ MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+
+ mShutdownComplete = true;
+ return;
+ }
+
+ if (!mDirectoryInfos.Count()) {
+ Cleanup();
+
+ MOZ_ASSERT(mShutdownComplete);
+ return;
+ }
+
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ while (!mShutdownComplete) {
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+ }
+}
+
+nsresult
+FileHandleThreadPool::Init()
+{
+ AssertIsOnOwningThread();
+
+ mThreadPool = new nsThreadPool();
+
+ nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandles"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetThreadLimit(kThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+FileHandleThreadPool::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mThreadPool);
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+
+ MOZ_ALWAYS_SUCCEEDS(mThreadPool->Shutdown());
+
+ if (!mCompleteCallbacks.IsEmpty()) {
+ // Run all callbacks manually now.
+ for (uint32_t count = mCompleteCallbacks.Length(), index = 0;
+ index < count;
+ index++) {
+ nsAutoPtr<StoragesCompleteCallback> completeCallback(
+ mCompleteCallbacks[index].forget());
+ MOZ_ASSERT(completeCallback);
+ MOZ_ASSERT(completeCallback->mCallback);
+
+ Unused << completeCallback->mCallback->Run();
+ }
+
+ mCompleteCallbacks.Clear();
+
+ // And make sure they get processed.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
+ }
+
+ mShutdownComplete = true;
+}
+
+void
+FileHandleThreadPool::FinishFileHandle(FileHandle* aFileHandle)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+ const nsACString& directoryId = mutableFile->DirectoryId();
+
+ DirectoryInfo* directoryInfo;
+ if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) {
+ NS_ERROR("We don't know anyting about this directory?!");
+ return;
+ }
+
+ directoryInfo->RemoveFileHandleQueue(aFileHandle);
+
+ if (!directoryInfo->HasRunningFileHandles()) {
+ mDirectoryInfos.Remove(directoryId);
+
+ // See if we need to fire any complete callbacks.
+ uint32_t index = 0;
+ while (index < mCompleteCallbacks.Length()) {
+ if (MaybeFireCallback(mCompleteCallbacks[index])) {
+ mCompleteCallbacks.RemoveElementAt(index);
+ }
+ else {
+ index++;
+ }
+ }
+
+ if (mShutdownRequested && !mDirectoryInfos.Count()) {
+ Cleanup();
+ }
+ }
+}
+
+bool
+FileHandleThreadPool::MaybeFireCallback(StoragesCompleteCallback* aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(!aCallback->mDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback->mCallback);
+
+ for (uint32_t count = aCallback->mDirectoryIds.Length(), index = 0;
+ index < count;
+ index++) {
+ const nsCString& directoryId = aCallback->mDirectoryIds[index];
+ MOZ_ASSERT(!directoryId.IsEmpty());
+
+ if (mDirectoryInfos.Get(directoryId, nullptr)) {
+ return false;
+ }
+ }
+
+ aCallback->mCallback->Run();
+ return true;
+}
+
+FileHandleThreadPool::
+FileHandleQueue::FileHandleQueue(FileHandleThreadPool* aFileHandleThreadPool,
+ FileHandle* aFileHandle)
+ : mOwningFileHandleThreadPool(aFileHandleThreadPool)
+ , mFileHandle(aFileHandle)
+ , mShouldFinish(false)
+{
+ MOZ_ASSERT(aFileHandleThreadPool);
+ aFileHandleThreadPool->AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::Enqueue(FileHandleOp* aFileHandleOp)
+{
+ MOZ_ASSERT(!mShouldFinish, "Enqueue called after Finish!");
+
+ mQueue.AppendElement(aFileHandleOp);
+
+ ProcessQueue();
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::Finish()
+{
+ MOZ_ASSERT(!mShouldFinish, "Finish called more than once!");
+
+ mShouldFinish = true;
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::ProcessQueue()
+{
+ if (mCurrentOp) {
+ return;
+ }
+
+ if (mQueue.IsEmpty()) {
+ if (mShouldFinish) {
+ mOwningFileHandleThreadPool->FinishFileHandle(mFileHandle);
+
+ // Make sure this is released on this thread.
+ mOwningFileHandleThreadPool = nullptr;
+ }
+
+ return;
+ }
+
+ mCurrentOp = mQueue[0];
+ mQueue.RemoveElementAt(0);
+
+ nsCOMPtr<nsIThreadPool> threadPool = mOwningFileHandleThreadPool->mThreadPool;
+ MOZ_ASSERT(threadPool);
+
+ MOZ_ALWAYS_SUCCEEDS(threadPool->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+FileHandleThreadPool::
+FileHandleQueue::Run()
+{
+ MOZ_ASSERT(mCurrentOp);
+
+ if (IsOnBackgroundThread()) {
+ RefPtr<FileHandleOp> currentOp;
+
+ mCurrentOp.swap(currentOp);
+ ProcessQueue();
+
+ currentOp->RunOnOwningThread();
+ } else {
+ mCurrentOp->RunOnThreadPool();
+
+ nsCOMPtr<nsIEventTarget> backgroundThread = mCurrentOp->OwningThread();
+
+ MOZ_ALWAYS_SUCCEEDS(
+ backgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+
+ return NS_OK;
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::CreateFileHandleQueue(FileHandle* aFileHandle)
+ -> FileHandleQueue*
+{
+ RefPtr<FileHandleQueue>* fileHandleQueue =
+ mFileHandleQueues.AppendElement();
+ *fileHandleQueue = new FileHandleQueue(mOwningFileHandleThreadPool,
+ aFileHandle);
+ return fileHandleQueue->get();
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::GetFileHandleQueue(FileHandle* aFileHandle) -> FileHandleQueue*
+{
+ uint32_t count = mFileHandleQueues.Length();
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<FileHandleQueue>& fileHandleQueue = mFileHandleQueues[index];
+ if (fileHandleQueue->mFileHandle == aFileHandle) {
+ return fileHandleQueue;
+ }
+ }
+ return nullptr;
+}
+
+void
+FileHandleThreadPool::
+DirectoryInfo::RemoveFileHandleQueue(FileHandle* aFileHandle)
+{
+ for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
+ if (mDelayedEnqueueInfos[index].mFileHandle == aFileHandle) {
+ MOZ_ASSERT(!mDelayedEnqueueInfos[index].mFileHandleOp, "Should be null!");
+ mDelayedEnqueueInfos.RemoveElementAt(index);
+ return;
+ }
+ }
+
+ uint32_t fileHandleCount = mFileHandleQueues.Length();
+
+ // We can't just remove entries from lock hash tables, we have to rebuild
+ // them instead. Multiple FileHandle objects may lock the same file
+ // (one entry can represent multiple locks).
+
+ mFilesReading.Clear();
+ mFilesWriting.Clear();
+
+ for (uint32_t index = 0, count = fileHandleCount; index < count; index++) {
+ FileHandle* fileHandle = mFileHandleQueues[index]->mFileHandle;
+ if (fileHandle == aFileHandle) {
+ MOZ_ASSERT(count == fileHandleCount, "More than one match?!");
+
+ mFileHandleQueues.RemoveElementAt(index);
+ index--;
+ count--;
+
+ continue;
+ }
+
+ const nsAString& fileName = fileHandle->GetMutableFile()->FileName();
+
+ if (fileHandle->Mode() == FileMode::Readwrite) {
+ if (!IsFileLockedForWriting(fileName)) {
+ LockFileForWriting(fileName);
+ }
+ }
+ else {
+ if (!IsFileLockedForReading(fileName)) {
+ LockFileForReading(fileName);
+ }
+ }
+ }
+
+ MOZ_ASSERT(mFileHandleQueues.Length() == fileHandleCount - 1,
+ "Didn't find the file handle we were looking for!");
+
+ nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos;
+ delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos);
+
+ for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) {
+ DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index];
+ mOwningFileHandleThreadPool->Enqueue(delayedEnqueueInfo.mFileHandle,
+ delayedEnqueueInfo.mFileHandleOp,
+ delayedEnqueueInfo.mFinish);
+ }
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::CreateDelayedEnqueueInfo(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp,
+ bool aFinish) -> DelayedEnqueueInfo*
+{
+ DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
+ info->mFileHandle = aFileHandle;
+ info->mFileHandleOp = aFileHandleOp;
+ info->mFinish = aFinish;
+ return info;
+}
+
+FileHandleThreadPool::
+StoragesCompleteCallback::StoragesCompleteCallback(
+ nsTArray<nsCString>&& aDirectoryIds,
+ nsIRunnable* aCallback)
+ : mDirectoryIds(Move(aDirectoryIds))
+ , mCallback(aCallback)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ MOZ_COUNT_CTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+FileHandleThreadPool::
+StoragesCompleteCallback::~StoragesCompleteCallback()
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+/*******************************************************************************
+ * BackgroundMutableFileParentBase
+ ******************************************************************************/
+
+BackgroundMutableFileParentBase::BackgroundMutableFileParentBase(
+ FileHandleStorage aStorage,
+ const nsACString& aDirectoryId,
+ const nsAString& aFileName,
+ nsIFile* aFile)
+ : mDirectoryId(aDirectoryId)
+ , mFileName(aFileName)
+ , mStorage(aStorage)
+ , mInvalidated(false)
+ , mActorWasAlive(false)
+ , mActorDestroyed(false)
+ , mFile(aFile)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aStorage != FILE_HANDLE_STORAGE_MAX);
+ MOZ_ASSERT(!aDirectoryId.IsEmpty());
+ MOZ_ASSERT(!aFileName.IsEmpty());
+ MOZ_ASSERT(aFile);
+}
+
+BackgroundMutableFileParentBase::~BackgroundMutableFileParentBase()
+{
+ MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
+}
+
+void
+BackgroundMutableFileParentBase::Invalidate()
+{
+ AssertIsOnBackgroundThread();
+
+ class MOZ_STACK_CLASS Helper final
+ {
+ public:
+ static bool
+ InvalidateFileHandles(nsTHashtable<nsPtrHashKey<FileHandle>>& aTable)
+ {
+ AssertIsOnBackgroundThread();
+
+ const uint32_t count = aTable.Count();
+ if (!count) {
+ return true;
+ }
+
+ FallibleTArray<RefPtr<FileHandle>> fileHandles;
+ if (NS_WARN_IF(!fileHandles.SetCapacity(count, fallible))) {
+ return false;
+ }
+
+ for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
+ if (NS_WARN_IF(!fileHandles.AppendElement(iter.Get()->GetKey(),
+ fallible))) {
+ return false;
+ }
+ }
+
+ if (count) {
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<FileHandle> fileHandle = fileHandles[index].forget();
+ MOZ_ASSERT(fileHandle);
+
+ fileHandle->Invalidate();
+ }
+ }
+
+ return true;
+ }
+ };
+
+ if (mInvalidated) {
+ return;
+ }
+
+ mInvalidated = true;
+
+ if (!Helper::InvalidateFileHandles(mFileHandles)) {
+ NS_WARNING("Failed to abort all file handles!");
+ }
+}
+
+bool
+BackgroundMutableFileParentBase::RegisterFileHandle(FileHandle* aFileHandle)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(!mFileHandles.GetEntry(aFileHandle));
+ MOZ_ASSERT(!mInvalidated);
+
+ if (NS_WARN_IF(!mFileHandles.PutEntry(aFileHandle, fallible))) {
+ return false;
+ }
+
+ if (mFileHandles.Count() == 1) {
+ NoteActiveState();
+ }
+
+ return true;
+}
+
+void
+BackgroundMutableFileParentBase::UnregisterFileHandle(FileHandle* aFileHandle)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(mFileHandles.GetEntry(aFileHandle));
+
+ mFileHandles.RemoveEntry(aFileHandle);
+
+ if (!mFileHandles.Count()) {
+ NoteInactiveState();
+ }
+}
+
+void
+BackgroundMutableFileParentBase::SetActorAlive()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorWasAlive);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorWasAlive = true;
+
+ // This reference will be absorbed by IPDL and released when the actor is
+ // destroyed.
+ AddRef();
+}
+
+already_AddRefed<nsISupports>
+BackgroundMutableFileParentBase::CreateStream(bool aReadOnly)
+{
+ AssertIsOnBackgroundThread();
+
+ nsresult rv;
+
+ if (aReadOnly) {
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), mFile, -1, -1,
+ nsIFileInputStream::DEFER_OPEN);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return stream.forget();
+ }
+
+ nsCOMPtr<nsIFileStream> stream;
+ rv = NS_NewLocalFileStream(getter_AddRefs(stream), mFile, -1, -1,
+ nsIFileStream::DEFER_OPEN);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return stream.forget();
+}
+
+void
+BackgroundMutableFileParentBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (!IsInvalidated()) {
+ Invalidate();
+ }
+}
+
+PBackgroundFileHandleParent*
+BackgroundMutableFileParentBase::AllocPBackgroundFileHandleParent(
+ const FileMode& aMode)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(aMode != FileMode::Readonly &&
+ aMode != FileMode::Readwrite)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<FileHandle> fileHandle = new FileHandle(this, aMode);
+
+ return fileHandle.forget().take();
+}
+
+bool
+BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
+ PBackgroundFileHandleParent* aActor,
+ const FileMode& aMode)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aMode == FileMode::Readonly || aMode == FileMode::Readwrite);
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ auto* fileHandle = static_cast<FileHandle*>(aActor);
+
+ // Add a placeholder for this file handle immediately.
+ fileHandleThreadPool->Enqueue(fileHandle, nullptr, false);
+
+ fileHandle->SetActive();
+
+ if (NS_WARN_IF(!RegisterFileHandle(fileHandle))) {
+ fileHandle->Abort(/* aForce */ false);
+ return true;
+ }
+
+ return true;
+}
+
+bool
+BackgroundMutableFileParentBase::DeallocPBackgroundFileHandleParent(
+ PBackgroundFileHandleParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FileHandle> fileHandle =
+ dont_AddRef(static_cast<FileHandle*>(aActor));
+ return true;
+}
+
+bool
+BackgroundMutableFileParentBase::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ return PBackgroundMutableFileParent::Send__delete__(this);
+}
+
+bool
+BackgroundMutableFileParentBase::RecvGetFileId(int64_t* aFileId)
+{
+ AssertIsOnBackgroundThread();
+
+ *aFileId = -1;
+ return true;
+}
+
+/*******************************************************************************
+ * FileHandle
+ ******************************************************************************/
+
+FileHandle::FileHandle(BackgroundMutableFileParentBase* aMutableFile,
+ FileMode aMode)
+ : mMutableFile(aMutableFile)
+ , mActiveRequestCount(0)
+ , mStorage(aMutableFile->Storage())
+ , mInvalidatedOnAnyThread(false)
+ , mMode(aMode)
+ , mHasBeenActive(false)
+ , mActorDestroyed(false)
+ , mInvalidated(false)
+ , mAborted(false)
+ , mFinishOrAbortReceived(false)
+ , mFinishedOrAborted(false)
+ , mForceAborted(false)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMutableFile);
+
+#ifdef DEBUG
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ mThreadPoolEventTarget = fileHandleThreadPool->GetThreadPoolEventTarget();
+#endif
+}
+
+FileHandle::~FileHandle()
+{
+ MOZ_ASSERT(!mActiveRequestCount);
+ MOZ_ASSERT(mActorDestroyed);
+ MOZ_ASSERT_IF(mHasBeenActive, mFinishedOrAborted);
+}
+
+void
+FileHandle::AssertIsOnThreadPool() const
+{
+ MOZ_ASSERT(mThreadPoolEventTarget);
+ DebugOnly<bool> current;
+ MOZ_ASSERT(NS_SUCCEEDED(mThreadPoolEventTarget->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+}
+
+nsresult
+FileHandle::GetOrCreateStream(nsISupports** aStream)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!mStream) {
+ nsCOMPtr<nsISupports> stream =
+ mMutableFile->CreateStream(mMode == FileMode::Readonly);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.swap(mStream);
+ }
+
+ nsCOMPtr<nsISupports> stream(mStream);
+ stream.forget(aStream);
+
+ return NS_OK;
+}
+
+void
+FileHandle::Abort(bool aForce)
+{
+ AssertIsOnBackgroundThread();
+
+ mAborted = true;
+
+ if (aForce) {
+ mForceAborted = true;
+ }
+
+ MaybeFinishOrAbort();
+}
+
+void
+FileHandle::NoteActiveRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
+
+ mActiveRequestCount++;
+}
+
+void
+FileHandle::NoteFinishedRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount);
+
+ mActiveRequestCount--;
+
+ MaybeFinishOrAbort();
+}
+
+void
+FileHandle::Invalidate()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
+
+ if (!mInvalidated) {
+ mInvalidated = true;
+ mInvalidatedOnAnyThread = true;
+
+ Abort(/* aForce */ true);
+ }
+}
+
+void
+FileHandle::SendCompleteNotification(bool aAborted)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!IsActorDestroyed()) {
+ Unused << SendComplete(aAborted);
+ }
+}
+
+bool
+FileHandle::VerifyRequestParams(const FileRequestParams& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+ switch (aParams.type()) {
+ case FileRequestParams::TFileRequestGetMetadataParams: {
+ const FileRequestGetMetadataParams& params =
+ aParams.get_FileRequestGetMetadataParams();
+
+ if (NS_WARN_IF(!params.size() && !params.lastModified())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestReadParams: {
+ const FileRequestReadParams& params =
+ aParams.get_FileRequestReadParams();
+
+ if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!params.size())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestWriteParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const FileRequestWriteParams& params =
+ aParams.get_FileRequestWriteParams();
+
+
+ if (NS_WARN_IF(!params.dataLength())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!VerifyRequestData(params.data()))) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestTruncateParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ const FileRequestTruncateParams& params =
+ aParams.get_FileRequestTruncateParams();
+
+ if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestFlushParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestGetFileParams: {
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+bool
+FileHandle::VerifyRequestData(const FileRequestData& aData) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aData.type() != FileRequestData::T__None);
+
+ switch (aData.type()) {
+ case FileRequestData::TFileRequestStringData: {
+ const FileRequestStringData& data =
+ aData.get_FileRequestStringData();
+
+ if (NS_WARN_IF(data.string().IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestData::TFileRequestBlobData: {
+ const FileRequestBlobData& data =
+ aData.get_FileRequestBlobData();
+
+ if (NS_WARN_IF(data.blobChild())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!data.blobParent())) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+void
+FileHandle::FinishOrAbort()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mFinishedOrAborted);
+
+ mFinishedOrAborted = true;
+
+ if (!mHasBeenActive) {
+ return;
+ }
+
+ RefPtr<FinishOp> finishOp = new FinishOp(this, mAborted);
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ fileHandleThreadPool->Enqueue(this, finishOp, true);
+}
+
+void
+FileHandle::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (!mFinishedOrAborted) {
+ mAborted = true;
+
+ mForceAborted = true;
+
+ MaybeFinishOrAbort();
+ }
+}
+
+bool
+FileHandle::RecvDeleteMe()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return PBackgroundFileHandleParent::Send__delete__(this);
+}
+
+bool
+FileHandle::RecvFinish()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mFinishOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ mFinishOrAbortReceived = true;
+
+ MaybeFinishOrAbort();
+ return true;
+}
+
+bool
+FileHandle::RecvAbort()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mFinishOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ mFinishOrAbortReceived = true;
+
+ Abort(/* aForce */ false);
+ return true;
+}
+
+PBackgroundFileRequestParent*
+FileHandle::AllocPBackgroundFileRequestParent(const FileRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ bool trustParams = false;
+#else
+ PBackgroundParent* backgroundActor = GetBackgroundParent();
+ 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(mFinishOrAbortReceived)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<NormalFileHandleOp> actor;
+
+ switch (aParams.type()) {
+ case FileRequestParams::TFileRequestGetMetadataParams:
+ actor = new GetMetadataOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestReadParams:
+ actor = new ReadOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestWriteParams:
+ actor = new WriteOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestTruncateParams:
+ actor = new TruncateOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestFlushParams:
+ actor = new FlushOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestGetFileParams:
+ actor = new GetFileOp(this, aParams);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+FileHandle::RecvPBackgroundFileRequestConstructor(
+ PBackgroundFileRequestParent* aActor,
+ const FileRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+ auto* op = static_cast<NormalFileHandleOp*>(aActor);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return false;
+ }
+
+ op->Enqueue();
+ return true;
+}
+
+bool
+FileHandle::DeallocPBackgroundFileRequestParent(
+ PBackgroundFileRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<NormalFileHandleOp> actor =
+ dont_AddRef(static_cast<NormalFileHandleOp*>(aActor));
+ return true;
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+FileHandleOp::Enqueue()
+{
+ AssertIsOnOwningThread();
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mFileHandle->Storage());
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ fileHandleThreadPool->Enqueue(mFileHandle, this, false);
+
+ mFileHandle->NoteActiveRequest();
+}
+
+void
+FileHandle::
+FinishOp::RunOnThreadPool()
+{
+ AssertIsOnThreadPool();
+ MOZ_ASSERT(mFileHandle);
+
+ nsCOMPtr<nsISupports>& stream = mFileHandle->mStream;
+
+ if (!stream) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream);
+ MOZ_ASSERT(inputStream);
+
+ MOZ_ALWAYS_SUCCEEDS(inputStream->Close());
+
+ stream = nullptr;
+}
+
+void
+FileHandle::
+FinishOp::RunOnOwningThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+
+ mFileHandle->SendCompleteNotification(mAborted);
+
+ mFileHandle->GetMutableFile()->UnregisterFileHandle(mFileHandle);
+
+ mFileHandle = nullptr;
+}
+
+NormalFileHandleOp::~NormalFileHandleOp()
+{
+ MOZ_ASSERT(!mFileHandle,
+ "NormalFileHandleOp::Cleanup() was not called by a subclass!");
+}
+
+bool
+NormalFileHandleOp::Init(FileHandle* aFileHandle)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ nsresult rv = aFileHandle->GetOrCreateStream(getter_AddRefs(mFileStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+NormalFileHandleOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+ MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
+
+ mFileHandle = nullptr;
+}
+
+nsresult
+NormalFileHandleOp::SendSuccessResult()
+{
+ AssertIsOnOwningThread();
+
+ if (!IsActorDestroyed()) {
+ FileRequestResponse response;
+ GetResponse(response);
+
+ MOZ_ASSERT(response.type() != FileRequestResponse::T__None);
+
+ if (response.type() == FileRequestResponse::Tnsresult) {
+ MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
+
+ return response.get_nsresult();
+ }
+
+ if (NS_WARN_IF(!PBackgroundFileRequestParent::Send__delete__(this,
+ response))) {
+ return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ }
+ }
+
+ DEBUGONLY(mResponseSent = true;)
+
+ return NS_OK;
+}
+
+bool
+NormalFileHandleOp::SendFailureResult(nsresult aResultCode)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+
+ bool result = false;
+
+ if (!IsActorDestroyed()) {
+ result =
+ PBackgroundFileRequestParent::Send__delete__(this, aResultCode);
+ }
+
+ DEBUGONLY(mResponseSent = true;)
+
+ return result;
+}
+
+void
+NormalFileHandleOp::RunOnThreadPool()
+{
+ AssertIsOnThreadPool();
+ MOZ_ASSERT(mFileHandle);
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+ // There are several cases where we don't actually have to to any work here.
+
+ if (mFileHandleIsAborted) {
+ // This transaction is already set to be aborted.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+ } else if (mFileHandle->IsInvalidatedOnAnyThread()) {
+ // This file handle is being invalidated.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else if (!OperationMayProceed()) {
+ // The operation was canceled in some way, likely because the child process
+ // has crashed.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else {
+ nsresult rv = DoFileWork(mFileHandle);
+ if (NS_FAILED(rv)) {
+ mResultCode = rv;
+ }
+ }
+}
+
+void
+NormalFileHandleOp::RunOnOwningThread()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+
+ if (NS_WARN_IF(IsActorDestroyed())) {
+ // Don't send any notifications if the actor was destroyed already.
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ }
+ } else {
+ if (mFileHandle->IsInvalidated()) {
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else if (mFileHandle->IsAborted()) {
+ // Aborted file handles always see their requests fail with ABORT_ERR,
+ // even if the request succeeded or failed with another error.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+ } else if (NS_SUCCEEDED(mResultCode)) {
+ // This may release the IPDL reference.
+ mResultCode = SendSuccessResult();
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ // This should definitely release the IPDL reference.
+ if (!SendFailureResult(mResultCode)) {
+ // Abort the file handle.
+ mFileHandle->Abort(/* aForce */ false);
+ }
+ }
+ }
+
+ mFileHandle->NoteFinishedRequest();
+
+ Cleanup();
+}
+
+void
+NormalFileHandleOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+nsresult
+CopyFileHandleOp::DoFileWork(FileHandle* aFileHandle)
+{
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ if (mRead) {
+ inputStream = do_QueryInterface(mFileStream);
+ outputStream = do_QueryInterface(mBufferStream);
+ } else {
+ inputStream = do_QueryInterface(mBufferStream);
+ outputStream = do_QueryInterface(mFileStream);
+ }
+
+ MOZ_ASSERT(inputStream);
+ MOZ_ASSERT(outputStream);
+
+ nsCOMPtr<nsISeekableStream> seekableStream =
+ do_QueryInterface(mFileStream);
+
+ nsresult rv;
+
+ if (seekableStream) {
+ if (mOffset == UINT64_MAX) {
+ rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ }
+ else {
+ rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mOffset = 0;
+
+ do {
+ char copyBuffer[kStreamCopyBlockSize];
+
+ uint64_t max = mSize - mOffset;
+ 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 = outputStream->Write(copyBuffer, numRead, &numWrite);
+ if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ rv = NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(numWrite != numRead)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mOffset += numWrite;
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new ProgressRunnable(this, mOffset, mSize);
+
+ mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } while (true);
+
+ MOZ_ASSERT(mOffset == mSize);
+
+ if (mRead) {
+ MOZ_ALWAYS_SUCCEEDS(outputStream->Close());
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(inputStream->Close());
+ }
+
+ return NS_OK;
+}
+
+void
+CopyFileHandleOp::Cleanup()
+{
+ AssertIsOnOwningThread();
+
+ mBufferStream = nullptr;
+
+ NormalFileHandleOp::Cleanup();
+}
+
+NS_IMETHODIMP
+CopyFileHandleOp::
+ProgressRunnable::Run()
+{
+ AssertIsOnBackgroundThread();
+
+ Unused << mCopyFileHandleOp->SendProgress(mProgress, mProgressMax);
+
+ mCopyFileHandleOp = nullptr;
+
+ return NS_OK;
+}
+
+GetMetadataOp::GetMetadataOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle)
+ , mParams(aParams.get_FileRequestGetMetadataParams())
+{
+ MOZ_ASSERT(aParams.type() ==
+ FileRequestParams::TFileRequestGetMetadataParams);
+}
+
+nsresult
+GetMetadataOp::DoFileWork(FileHandle* aFileHandle)
+{
+ AssertIsOnThreadPool();
+
+ nsresult rv;
+
+ if (mFileHandle->Mode() == FileMode::Readwrite) {
+ // Force a flush (so all pending writes are flushed to the disk and file
+ // metadata is updated too).
+
+ nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(ostream);
+
+ rv = ostream->Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFileMetadata> metadata = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(metadata);
+
+ if (mParams.size()) {
+ int64_t size;
+ rv = metadata->GetSize(&size);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(size < 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mMetadata.size() = uint64_t(size);
+ } else {
+ mMetadata.size() = void_t();
+ }
+
+ if (mParams.lastModified()) {
+ int64_t lastModified;
+ rv = metadata->GetLastModified(&lastModified);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mMetadata.lastModified() = lastModified;
+ } else {
+ mMetadata.lastModified() = void_t();
+ }
+
+ return NS_OK;
+}
+
+void
+GetMetadataOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ aResponse = FileRequestGetMetadataResponse(mMetadata);
+}
+
+ReadOp::ReadOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : CopyFileHandleOp(aFileHandle)
+ , mParams(aParams.get_FileRequestReadParams())
+{
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestReadParams);
+}
+
+bool
+ReadOp::Init(FileHandle* aFileHandle)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+ return false;
+ }
+
+ mBufferStream = MemoryOutputStream::Create(mParams.size());
+ if (NS_WARN_IF(!mBufferStream)) {
+ return false;
+ }
+
+ mOffset = mParams.offset();
+ mSize = mParams.size();
+ mRead = true;
+
+ return true;
+}
+
+void
+ReadOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ auto* stream = static_cast<MemoryOutputStream*>(mBufferStream.get());
+
+ aResponse = FileRequestReadResponse(stream->Data());
+}
+
+// static
+already_AddRefed<ReadOp::MemoryOutputStream>
+ReadOp::
+MemoryOutputStream::Create(uint64_t aSize)
+{
+ MOZ_ASSERT(aSize, "Passed zero size!");
+
+ if (NS_WARN_IF(aSize > UINT32_MAX)) {
+ return nullptr;
+ }
+
+ RefPtr<MemoryOutputStream> stream = new MemoryOutputStream();
+
+ char* dummy;
+ uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible);
+ if (NS_WARN_IF(length != aSize)) {
+ return nullptr;
+ }
+
+ return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS(ReadOp::MemoryOutputStream, nsIOutputStream)
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Close()
+{
+ mData.Truncate(mOffset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+ return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval)
+{
+ NS_ASSERTION(mData.Length() >= mOffset, "Bad stream state!");
+
+ uint32_t maxCount = mData.Length() - mOffset;
+ if (maxCount == 0) {
+ *_retval = 0;
+ return NS_OK;
+ }
+
+ if (aCount > maxCount) {
+ aCount = maxCount;
+ }
+
+ nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0,
+ aCount, _retval);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(*_retval <= aCount,
+ "Reader should not read more than we asked it to read!");
+ mOffset += *_retval;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::IsNonBlocking(bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+WriteOp::WriteOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : CopyFileHandleOp(aFileHandle)
+ , mParams(aParams.get_FileRequestWriteParams())
+{
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestWriteParams);
+}
+
+bool
+WriteOp::Init(FileHandle* aFileHandle)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ const FileRequestData& data = mParams.data();
+ switch (data.type()) {
+ case FileRequestData::TFileRequestStringData: {
+ const FileRequestStringData& stringData =
+ data.get_FileRequestStringData();
+
+ const nsCString& string = stringData.string();
+
+ nsresult rv =
+ NS_NewCStringInputStream(getter_AddRefs(inputStream), string);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ break;
+ }
+ case FileRequestData::TFileRequestBlobData: {
+ const FileRequestBlobData& blobData =
+ data.get_FileRequestBlobData();
+
+ auto blobActor = static_cast<BlobParent*>(blobData.blobParent());
+
+ RefPtr<BlobImpl> blobImpl = blobActor->GetBlobImpl();
+
+ ErrorResult rv;
+ blobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ mBufferStream = inputStream;
+ mOffset = mParams.offset();
+ mSize = mParams.dataLength();
+ mRead = false;
+
+ return true;
+}
+
+void
+WriteOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ aResponse = FileRequestWriteResponse();
+}
+
+TruncateOp::TruncateOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle)
+ , mParams(aParams.get_FileRequestTruncateParams())
+{
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestTruncateParams);
+}
+
+nsresult
+TruncateOp::DoFileWork(FileHandle* aFileHandle)
+{
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(sstream);
+
+ nsresult rv = sstream->Seek(nsISeekableStream::NS_SEEK_SET, mParams.offset());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sstream->SetEOF();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+TruncateOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ aResponse = FileRequestTruncateResponse();
+}
+
+FlushOp::FlushOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle)
+ , mParams(aParams.get_FileRequestFlushParams())
+{
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestFlushParams);
+}
+
+nsresult
+FlushOp::DoFileWork(FileHandle* aFileHandle)
+{
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(ostream);
+
+ nsresult rv = ostream->Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+FlushOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ aResponse = FileRequestFlushResponse();
+}
+
+GetFileOp::GetFileOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : GetMetadataOp(aFileHandle,
+ FileRequestGetMetadataParams(true, true))
+ , mBackgroundParent(aFileHandle->GetBackgroundParent())
+{
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestGetFileParams);
+ MOZ_ASSERT(mBackgroundParent);
+}
+
+void
+GetFileOp::GetResponse(FileRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ RefPtr<BlobImpl> blobImpl = mFileHandle->GetMutableFile()->CreateBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ PBlobParent* actor =
+ BackgroundParent::GetOrCreateActorForBlobImpl(mBackgroundParent, blobImpl);
+ if (NS_WARN_IF(!actor)) {
+ // This can only fail if the child has crashed.
+ aResponse = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ return;
+ }
+
+ FileRequestGetFileResponse response;
+ response.fileParent() = actor;
+ response.metadata() = mMetadata;
+
+ aResponse = response;
+}
+
+} // namespace dom
+} // namespace mozilla