mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
2664 lines
59 KiB
C++
2664 lines
59 KiB
C++
/* 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/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;
|
|
|
|
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 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> 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(¤t)));
|
|
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(¤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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobData.blob());
|
|
if (NS_WARN_IF(!blobImpl)) {
|
|
return false;
|
|
}
|
|
|
|
IgnoredErrorResult rv;
|
|
blobImpl->GetInternalStream(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
|