forked from mirrors/gecko-dev
		
	 298aa82710
			
		
	
	
		298aa82710
		
	
	
	
	
		
			
			This was automatically generated by the script modeline.py. MozReview-Commit-ID: BgulzkGteAL --HG-- extra : rebase_source : a4b9d16a4c06c4e85d7d85f485221b1e4ebdfede
		
			
				
	
	
		
			2671 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2671 lines
		
	
	
	
		
			60 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "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/PBackgroundFileHandleParent.h"
 | |
| #include "mozilla/dom/PBackgroundFileRequestParent.h"
 | |
| #include "mozilla/dom/indexedDB/ActorsParent.h"
 | |
| #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 | |
| #include "mozilla/dom/IPCBlobUtils.h"
 | |
| #include "mozilla/dom/ipc/PendingIPCBlobParent.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;
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   nsCOMPtr<nsIEventTarget> mThreadPoolEventTarget;
 | |
| #endif
 | |
| 
 | |
| 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 mozilla::ipc::IPCResult
 | |
|   RecvDeleteMe() override;
 | |
| 
 | |
|   virtual mozilla::ipc::IPCResult
 | |
|   RecvFinish() override;
 | |
| 
 | |
|   virtual mozilla::ipc::IPCResult
 | |
|   RecvAbort() override;
 | |
| 
 | |
|   virtual PBackgroundFileRequestParent*
 | |
|   AllocPBackgroundFileRequestParent(const FileRequestParams& aParams) override;
 | |
| 
 | |
|   virtual mozilla::ipc::IPCResult
 | |
|   RecvPBackgroundFileRequestConstructor(PBackgroundFileRequestParent* aActor,
 | |
|                                         const FileRequestParams& aParams)
 | |
|                                         override;
 | |
| 
 | |
|   virtual bool
 | |
|   DeallocPBackgroundFileRequestParent(PBackgroundFileRequestParent* aActor)
 | |
|                                       override;
 | |
| };
 | |
| 
 | |
| class FileHandleOp
 | |
| {
 | |
| protected:
 | |
|   nsCOMPtr<nsIEventTarget> mOwningEventTarget;
 | |
|   RefPtr<FileHandle> mFileHandle;
 | |
| 
 | |
| public:
 | |
|   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileHandleOp)
 | |
| 
 | |
|   void
 | |
|   AssertIsOnOwningThread() const
 | |
|   {
 | |
|     AssertIsOnBackgroundThread();
 | |
|     MOZ_ASSERT(mOwningEventTarget);
 | |
|     DebugOnly<bool> current;
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)));
 | |
|     MOZ_ASSERT(current);
 | |
|   }
 | |
| 
 | |
|   nsIEventTarget*
 | |
|   OwningThread() const
 | |
|   {
 | |
|     return mOwningEventTarget;
 | |
|   }
 | |
| 
 | |
|   void
 | |
|   AssertIsOnThreadPool() const
 | |
|   {
 | |
|     MOZ_ASSERT(mFileHandle);
 | |
|     mFileHandle->AssertIsOnThreadPool();
 | |
|   }
 | |
| 
 | |
|   void
 | |
|   Enqueue();
 | |
| 
 | |
|   virtual void
 | |
|   RunOnThreadPool() = 0;
 | |
| 
 | |
|   virtual void
 | |
|   RunOnOwningThread() = 0;
 | |
| 
 | |
| protected:
 | |
|   FileHandleOp(FileHandle* aFileHandle)
 | |
|     : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
 | |
|     , 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;
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   bool mResponseSent;
 | |
| #endif
 | |
| 
 | |
| 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())
 | |
| #ifdef DEBUG
 | |
|     , mResponseSent(false)
 | |
| #endif
 | |
|   {
 | |
|     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)
 | |
|     : Runnable("dom::CopyFileHandleOp::ProgressRunnable")
 | |
|     , 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()
 | |
|   : mOwningEventTarget(GetCurrentThreadSerialEventTarget())
 | |
|   , mShutdownRequested(false)
 | |
|   , mShutdownComplete(false)
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
|   MOZ_ASSERT(mOwningEventTarget);
 | |
|   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(mOwningEventTarget);
 | |
| 
 | |
|   bool current;
 | |
|   MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->IsOnCurrentThread(¤t));
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
 | |
| }
 | |
| 
 | |
| 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)
 | |
|   : Runnable("dom::FileHandleThreadPool::FileHandleQueue")
 | |
|   , 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();
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| 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 IPC_OK();
 | |
|   }
 | |
| 
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| bool
 | |
| BackgroundMutableFileParentBase::DeallocPBackgroundFileHandleParent(
 | |
|                                             PBackgroundFileHandleParent* aActor)
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
|   MOZ_ASSERT(aActor);
 | |
| 
 | |
|   RefPtr<FileHandle> fileHandle =
 | |
|     dont_AddRef(static_cast<FileHandle*>(aActor));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| BackgroundMutableFileParentBase::RecvDeleteMe()
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
|   MOZ_ASSERT(!mActorDestroyed);
 | |
| 
 | |
|   IProtocol* mgr = Manager();
 | |
|   if (!PBackgroundMutableFileParent::Send__delete__(this)) {
 | |
|     return IPC_FAIL_NO_REASON(mgr);
 | |
|   }
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| BackgroundMutableFileParentBase::RecvGetFileId(int64_t* aFileId)
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
| 
 | |
|   *aFileId = -1;
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| /*******************************************************************************
 | |
|  * 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(¤t)));
 | |
|   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: {
 | |
|       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();
 | |
|   }
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| FileHandle::RecvDeleteMe()
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
|   MOZ_ASSERT(!IsActorDestroyed());
 | |
| 
 | |
|   IProtocol* mgr = Manager();
 | |
|   if (!PBackgroundFileHandleParent::Send__delete__(this)) {
 | |
|     return IPC_FAIL_NO_REASON(mgr);
 | |
|   }
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| FileHandle::RecvFinish()
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
| 
 | |
|   if (NS_WARN_IF(mFinishOrAbortReceived)) {
 | |
|     ASSERT_UNLESS_FUZZING();
 | |
|     return IPC_FAIL_NO_REASON(this);
 | |
|   }
 | |
| 
 | |
|   mFinishOrAbortReceived = true;
 | |
| 
 | |
|   MaybeFinishOrAbort();
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| FileHandle::RecvAbort()
 | |
| {
 | |
|   AssertIsOnBackgroundThread();
 | |
| 
 | |
|   if (NS_WARN_IF(mFinishOrAbortReceived)) {
 | |
|     ASSERT_UNLESS_FUZZING();
 | |
|     return IPC_FAIL_NO_REASON(this);
 | |
|   }
 | |
| 
 | |
|   mFinishOrAbortReceived = true;
 | |
| 
 | |
|   Abort(/* aForce */ false);
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| 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();
 | |
| }
 | |
| 
 | |
| mozilla::ipc::IPCResult
 | |
| 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 IPC_FAIL_NO_REASON(this);
 | |
|   }
 | |
| 
 | |
|   op->Enqueue();
 | |
|   return IPC_OK();
 | |
| }
 | |
| 
 | |
| 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;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   mResponseSent = true;
 | |
| #endif
 | |
| 
 | |
|   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);
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   mResponseSent = true;
 | |
| #endif
 | |
| 
 | |
|   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);
 | |
| 
 | |
|     mOwningEventTarget->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();
 | |
| 
 | |
|       RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobData.blob());
 | |
|       if (NS_WARN_IF(!blobImpl)) {
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       IgnoredErrorResult rv;
 | |
|       blobImpl->CreateInputStream(getter_AddRefs(inputStream), rv);
 | |
|       if (NS_WARN_IF(rv.Failed())) {
 | |
|         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);
 | |
| 
 | |
|   PendingIPCBlobParent* actor =
 | |
|     PendingIPCBlobParent::Create(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
 |