michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Native implementation of some OS.File operations. michael@0: */ michael@0: michael@0: #include "nsString.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOMCID.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsProxyRelease.h" michael@0: michael@0: #include "nsINativeOSFileInternals.h" michael@0: #include "NativeOSFileInternals.h" michael@0: #include "mozilla/dom/NativeOSFileInternalsBinding.h" michael@0: michael@0: #include "nsIUnicodeDecoder.h" michael@0: #include "nsIEventTarget.h" michael@0: michael@0: #include "mozilla/dom/EncodingUtils.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Scoped.h" michael@0: #include "mozilla/HoldDropJSObjects.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: michael@0: #include "prio.h" michael@0: #include "prerror.h" michael@0: #include "private/pprio.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: #include "js/Utility.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: #include michael@0: #if defined(XP_UNIX) michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #endif // defined (XP_UNIX) michael@0: michael@0: #if defined(XP_WIN) michael@0: #include michael@0: #endif // defined (XP_WIN) michael@0: michael@0: namespace mozilla { michael@0: michael@0: MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close) michael@0: michael@0: namespace { michael@0: michael@0: // Utilities for safely manipulating ArrayBuffer contents even in the michael@0: // absence of a JSContext. michael@0: michael@0: /** michael@0: * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate michael@0: * this instead of a void* buffer, as this lets us transfer data across threads michael@0: * and into JavaScript without copy. michael@0: */ michael@0: struct ArrayBufferContents { michael@0: /** michael@0: * The data of the ArrayBuffer. This is the pointer manipulated to michael@0: * read/write the contents of the buffer. michael@0: */ michael@0: uint8_t* data; michael@0: /** michael@0: * The number of bytes in the ArrayBuffer. michael@0: */ michael@0: size_t nbytes; michael@0: }; michael@0: michael@0: /** michael@0: * RAII for ArrayBufferContents. michael@0: */ michael@0: struct ScopedArrayBufferContentsTraits { michael@0: typedef ArrayBufferContents type; michael@0: const static type empty() { michael@0: type result = {0, 0}; michael@0: return result; michael@0: } michael@0: const static void release(type ptr) { michael@0: js_free(ptr.data); michael@0: ptr.data = nullptr; michael@0: ptr.nbytes = 0; michael@0: } michael@0: }; michael@0: michael@0: struct ScopedArrayBufferContents: public Scoped { michael@0: ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): michael@0: Scoped(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) michael@0: { } michael@0: ScopedArrayBufferContents(const ArrayBufferContents& v michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM): michael@0: Scoped(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) michael@0: { } michael@0: ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) { michael@0: Scoped::operator=(ptr); michael@0: return *this; michael@0: } michael@0: michael@0: /** michael@0: * Request memory for this ArrayBufferContent. This memory may later michael@0: * be used to create an ArrayBuffer object (possibly on another michael@0: * thread) without copy. michael@0: * michael@0: * @return true In case of success, false otherwise. michael@0: */ michael@0: bool Allocate(uint32_t length) { michael@0: dispose(); michael@0: ArrayBufferContents& value = rwget(); michael@0: void *ptr = JS_AllocateArrayBufferContents(/*no context available*/nullptr, length); michael@0: if (ptr) { michael@0: value.data = (uint8_t *) ptr; michael@0: value.nbytes = length; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: private: michael@0: explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE; michael@0: ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE; michael@0: }; michael@0: michael@0: ///////// Cross-platform issues michael@0: michael@0: // Platform specific constants. As OS.File always uses OS-level michael@0: // errors, we need to map a few high-level errors to OS-level michael@0: // constants. michael@0: #if defined(XP_UNIX) michael@0: #define OS_ERROR_NOMEM ENOMEM michael@0: #define OS_ERROR_INVAL EINVAL michael@0: #define OS_ERROR_TOO_LARGE EFBIG michael@0: #define OS_ERROR_RACE EIO michael@0: #elif defined(XP_WIN) michael@0: #define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY michael@0: #define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS michael@0: #define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE michael@0: #define OS_ERROR_RACE ERROR_SHARING_VIOLATION michael@0: #else michael@0: #error "We do not have platform-specific constants for this platform" michael@0: #endif michael@0: michael@0: ///////// Results of OS.File operations michael@0: michael@0: /** michael@0: * Base class for results passed to the callbacks. michael@0: * michael@0: * This base class implements caching of JS values returned to the client. michael@0: * We make use of this caching in derived classes e.g. to avoid accidents michael@0: * when we transfer data allocated on another thread into JS. Note that michael@0: * this caching can lead to cycles (e.g. if a client adds a back-reference michael@0: * in the JS value), so we implement all Cycle Collector primitives in michael@0: * AbstractResult. michael@0: */ michael@0: class AbstractResult: public nsINativeOSFileResult { michael@0: public: michael@0: NS_DECL_NSINATIVEOSFILERESULT michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult) michael@0: michael@0: /** michael@0: * Construct the result object. Must be called on the main thread michael@0: * as the AbstractResult is cycle-collected. michael@0: * michael@0: * @param aStartDate The instant at which the operation was michael@0: * requested. Used to collect Telemetry statistics. michael@0: */ michael@0: AbstractResult(TimeStamp aStartDate) michael@0: : mStartDate(aStartDate) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: virtual ~AbstractResult() { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: DropJSData(); michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: /** michael@0: * Setup the AbstractResult once data is available. michael@0: * michael@0: * @param aDispatchDate The instant at which the IO thread received michael@0: * the operation request. Used to collect Telemetry statistics. michael@0: * @param aExecutionDuration The duration of the operation on the michael@0: * IO thread. michael@0: */ michael@0: void Init(TimeStamp aDispatchDate, michael@0: TimeDuration aExecutionDuration) { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: mDispatchDuration = (aDispatchDate - mStartDate); michael@0: mExecutionDuration = aExecutionDuration; michael@0: } michael@0: michael@0: /** michael@0: * Drop any data that could lead to a cycle. michael@0: */ michael@0: void DropJSData() { michael@0: mCachedResult = JS::UndefinedValue(); michael@0: } michael@0: michael@0: protected: michael@0: virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0; michael@0: michael@0: private: michael@0: TimeStamp mStartDate; michael@0: TimeDuration mDispatchDuration; michael@0: TimeDuration mExecutionDuration; michael@0: JS::Heap mCachedResult; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult) michael@0: NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult) michael@0: tmp->DropJSData(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMETHODIMP michael@0: AbstractResult::GetDispatchDurationMS(double *aDispatchDuration) michael@0: { michael@0: *aDispatchDuration = mDispatchDuration.ToMilliseconds(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AbstractResult::GetExecutionDurationMS(double *aExecutionDuration) michael@0: { michael@0: *aExecutionDuration = mExecutionDuration.ToMilliseconds(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult) michael@0: { michael@0: if (mCachedResult.isUndefined()) { michael@0: nsresult rv = GetCacheableResult(cx, aResult); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: mCachedResult = aResult; michael@0: return NS_OK; michael@0: } michael@0: aResult.set(mCachedResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Return a result as a string. michael@0: * michael@0: * In this implementation, attribute |result| is a string. Strings are michael@0: * passed to JS without copy. michael@0: */ michael@0: class StringResult MOZ_FINAL : public AbstractResult michael@0: { michael@0: public: michael@0: StringResult(TimeStamp aStartDate) michael@0: : AbstractResult(aStartDate) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Initialize the object once the contents of the result as available. michael@0: * michael@0: * @param aContents The string to pass to JavaScript. Ownership of the michael@0: * string and its contents is passed to StringResult. The string must michael@0: * be valid UTF-16. michael@0: */ michael@0: void Init(TimeStamp aDispatchDate, michael@0: TimeDuration aExecutionDuration, michael@0: nsString& aContents) { michael@0: AbstractResult::Init(aDispatchDate, aExecutionDuration); michael@0: mContents = aContents; michael@0: } michael@0: michael@0: protected: michael@0: nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: nsString mContents; michael@0: }; michael@0: michael@0: nsresult michael@0: StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mContents.get()); michael@0: michael@0: // Convert mContents to a js string without copy. Note that this michael@0: // may have the side-effect of stealing the contents of the string michael@0: // from XPCOM and into JS. michael@0: if (!xpc::StringToJsval(cx, mContents, aResult)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Return a result as a Uint8Array. michael@0: * michael@0: * In this implementation, attribute |result| is a Uint8Array. The array michael@0: * is passed to JS without memory copy. michael@0: */ michael@0: class TypedArrayResult MOZ_FINAL : public AbstractResult michael@0: { michael@0: public: michael@0: TypedArrayResult(TimeStamp aStartDate) michael@0: : AbstractResult(aStartDate) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * @param aContents The contents to pass to JS. Calling this method. michael@0: * transmits ownership of the ArrayBufferContents to the TypedArrayResult. michael@0: * Do not reuse this value anywhere else. michael@0: */ michael@0: void Init(TimeStamp aDispatchDate, michael@0: TimeDuration aExecutionDuration, michael@0: ArrayBufferContents aContents) { michael@0: AbstractResult::Init(aDispatchDate, aExecutionDuration); michael@0: mContents = aContents; michael@0: } michael@0: michael@0: protected: michael@0: nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; michael@0: private: michael@0: ScopedArrayBufferContents mContents; michael@0: }; michael@0: michael@0: nsresult michael@0: TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle aResult) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // We cannot simply construct a typed array using contents.data as michael@0: // this would allow us to have several otherwise unrelated michael@0: // ArrayBuffers with the same underlying C buffer. As this would be michael@0: // very unsafe, we need to cache the result once we have it. michael@0: michael@0: const ArrayBufferContents& contents = mContents.get(); michael@0: MOZ_ASSERT(contents.data); michael@0: michael@0: JS::Rooted michael@0: arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data)); michael@0: if (!arrayBuffer) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: JS::Rooted michael@0: result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, michael@0: 0, contents.nbytes)); michael@0: if (!result) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: // The memory of contents has been allocated on a thread that michael@0: // doesn't have a JSRuntime, hence without a context. Now that we michael@0: // have a context, attach the memory to where it belongs. michael@0: JS_updateMallocCounter(cx, contents.nbytes); michael@0: mContents.forget(); michael@0: michael@0: aResult.setObject(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////// Callback events michael@0: michael@0: /** michael@0: * An event used to notify asynchronously of an error. michael@0: */ michael@0: class ErrorEvent MOZ_FINAL : public nsRunnable { michael@0: public: michael@0: /** michael@0: * @param aOnSuccess The success callback. michael@0: * @param aOnError The error callback. michael@0: * @param aDiscardedResult The discarded result. michael@0: * @param aOperation The name of the operation, used for error reporting. michael@0: * @param aOSError The OS error of the operation, as returned by errno/ michael@0: * GetLastError(). michael@0: * michael@0: * Note that we pass both the success callback and the error michael@0: * callback, as well as the discarded result to ensure that they are michael@0: * all released on the main thread, rather than on the IO thread michael@0: * (which would hopefully segfault). Also, we pass the callbacks as michael@0: * alread_AddRefed to ensure that we do not manipulate main-thread michael@0: * only refcounters off the main thread. michael@0: */ michael@0: ErrorEvent(already_AddRefed&& aOnSuccess, michael@0: already_AddRefed&& aOnError, michael@0: already_AddRefed& aDiscardedResult, michael@0: const nsACString& aOperation, michael@0: int32_t aOSError) michael@0: : mOnSuccess(aOnSuccess) michael@0: , mOnError(aOnError) michael@0: , mDiscardedResult(aDiscardedResult) michael@0: , mOSError(aOSError) michael@0: , mOperation(aOperation) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_METHOD Run() { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: (void)mOnError->Complete(mOperation, mOSError); michael@0: michael@0: // Ensure that the callbacks are released on the main thread. michael@0: mOnSuccess = nullptr; michael@0: mOnError = nullptr; michael@0: mDiscardedResult = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: // The callbacks. Maintained as nsRefPtr as they are generally michael@0: // xpconnect values, which cannot be manipulated with nsCOMPtr off michael@0: // the main thread. We store both the success callback and the michael@0: // error callback to ensure that they are safely released on the michael@0: // main thread. michael@0: nsRefPtr mOnSuccess; michael@0: nsRefPtr mOnError; michael@0: nsRefPtr mDiscardedResult; michael@0: int32_t mOSError; michael@0: nsCString mOperation; michael@0: }; michael@0: michael@0: /** michael@0: * An event used to notify of a success. michael@0: */ michael@0: class SuccessEvent MOZ_FINAL : public nsRunnable { michael@0: public: michael@0: /** michael@0: * @param aOnSuccess The success callback. michael@0: * @param aOnError The error callback. michael@0: * michael@0: * Note that we pass both the success callback and the error michael@0: * callback to ensure that they are both released on the main michael@0: * thread, rather than on the IO thread (which would hopefully michael@0: * segfault). Also, we pass them as alread_AddRefed to ensure that michael@0: * we do not manipulate xpconnect refcounters off the main thread michael@0: * (which is illegal). michael@0: */ michael@0: SuccessEvent(already_AddRefed&& aOnSuccess, michael@0: already_AddRefed&& aOnError, michael@0: already_AddRefed& aResult) michael@0: : mOnSuccess(aOnSuccess) michael@0: , mOnError(aOnError) michael@0: , mResult(aResult) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_METHOD Run() { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: (void)mOnSuccess->Complete(mResult); michael@0: michael@0: // Ensure that the callbacks are released on the main thread. michael@0: mOnSuccess = nullptr; michael@0: mOnError = nullptr; michael@0: mResult = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: // The callbacks. Maintained as nsRefPtr as they are generally michael@0: // xpconnect values, which cannot be manipulated with nsCOMPtr off michael@0: // the main thread. We store both the success callback and the michael@0: // error callback to ensure that they are safely released on the michael@0: // main thread. michael@0: nsRefPtr mOnSuccess; michael@0: nsRefPtr mOnError; michael@0: nsRefPtr mResult; michael@0: }; michael@0: michael@0: michael@0: //////// Action events michael@0: michael@0: /** michael@0: * Base class shared by actions. michael@0: */ michael@0: class AbstractDoEvent: public nsRunnable { michael@0: public: michael@0: AbstractDoEvent(already_AddRefed& aOnSuccess, michael@0: already_AddRefed& aOnError) michael@0: : mOnSuccess(aOnSuccess) michael@0: , mOnError(aOnError) michael@0: #if defined(DEBUG) michael@0: , mResolved(false) michael@0: #endif // defined(DEBUG) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: /** michael@0: * Fail, asynchronously. michael@0: */ michael@0: void Fail(const nsACString& aOperation, michael@0: already_AddRefed&& aDiscardedResult, michael@0: int32_t aOSError = 0) { michael@0: Resolve(); michael@0: nsRefPtr event = new ErrorEvent(mOnSuccess.forget(), michael@0: mOnError.forget(), michael@0: aDiscardedResult, michael@0: aOperation, michael@0: aOSError); michael@0: nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: // Last ditch attempt to release on the main thread - some of michael@0: // the members of event are not thread-safe, so letting the michael@0: // pointer go out of scope would cause a crash. michael@0: nsCOMPtr main = do_GetMainThread(); michael@0: NS_ProxyRelease(main, event); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Succeed, asynchronously. michael@0: */ michael@0: void Succeed(already_AddRefed&& aResult) { michael@0: Resolve(); michael@0: nsRefPtr event = new SuccessEvent(mOnSuccess.forget(), michael@0: mOnError.forget(), michael@0: aResult); michael@0: nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: // Last ditch attempt to release on the main thread - some of michael@0: // the members of event are not thread-safe, so letting the michael@0: // pointer go out of scope would cause a crash. michael@0: nsCOMPtr main = do_GetMainThread(); michael@0: NS_ProxyRelease(main, event); michael@0: } michael@0: michael@0: } michael@0: michael@0: private: michael@0: michael@0: /** michael@0: * Mark the event as complete, for debugging purposes. michael@0: */ michael@0: void Resolve() { michael@0: #if defined(DEBUG) michael@0: MOZ_ASSERT(!mResolved); michael@0: mResolved = true; michael@0: #endif // defined(DEBUG) michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mOnSuccess; michael@0: nsRefPtr mOnError; michael@0: #if defined(DEBUG) michael@0: // |true| once the action is complete michael@0: bool mResolved; michael@0: #endif // defined(DEBUG) michael@0: }; michael@0: michael@0: /** michael@0: * An abstract event implementing reading from a file. michael@0: * michael@0: * Concrete subclasses are responsible for handling the michael@0: * data obtained from the file and possibly post-processing it. michael@0: */ michael@0: class AbstractReadEvent: public AbstractDoEvent { michael@0: public: michael@0: /** michael@0: * @param aPath The path of the file. michael@0: */ michael@0: AbstractReadEvent(const nsAString& aPath, michael@0: const uint64_t aBytes, michael@0: already_AddRefed& aOnSuccess, michael@0: already_AddRefed& aOnError) michael@0: : AbstractDoEvent(aOnSuccess, aOnError) michael@0: , mPath(aPath) michael@0: , mBytes(aBytes) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_METHOD Run() MOZ_OVERRIDE { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: TimeStamp dispatchDate = TimeStamp::Now(); michael@0: michael@0: nsresult rv = BeforeRead(); michael@0: if (NS_FAILED(rv)) { michael@0: // Error reporting is handled by BeforeRead(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ScopedArrayBufferContents buffer; michael@0: rv = Read(buffer); michael@0: if (NS_FAILED(rv)) { michael@0: // Error reporting is handled by Read(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: AfterRead(dispatchDate, buffer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: /** michael@0: * Read synchronously. michael@0: * michael@0: * Must be called off the main thread. michael@0: * michael@0: * @param aBuffer The destination buffer. michael@0: */ michael@0: nsresult Read(ScopedArrayBufferContents& aBuffer) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: ScopedPRFileDesc file; michael@0: #if defined(XP_WIN) michael@0: // On Windows, we can't use PR_OpenFile because it doesn't michael@0: // handle UTF-16 encoding, which is pretty bad. In addition, michael@0: // PR_OpenFile opens files without sharing, which is not the michael@0: // general semantics of OS.File. michael@0: HANDLE handle = michael@0: ::CreateFileW(mPath.get(), michael@0: GENERIC_READ, michael@0: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, michael@0: /*Security attributes*/nullptr, michael@0: OPEN_EXISTING, michael@0: FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, michael@0: /*Template file*/ nullptr); michael@0: michael@0: if (handle == INVALID_HANDLE_VALUE) { michael@0: Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: file = PR_ImportFile((PROsfd)handle); michael@0: if (!file) { michael@0: // |file| is closed by PR_ImportFile michael@0: Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: #else michael@0: // On other platforms, PR_OpenFile will do. michael@0: NS_ConvertUTF16toUTF8 path(mPath); michael@0: file = PR_OpenFile(path.get(), PR_RDONLY, 0); michael@0: if (!file) { michael@0: Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: #endif // defined(XP_XIN) michael@0: michael@0: PRFileInfo64 stat; michael@0: if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) { michael@0: Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint64_t bytes = std::min((uint64_t)stat.size, mBytes); michael@0: if (bytes > UINT32_MAX) { michael@0: Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!aBuffer.Allocate(bytes)) { michael@0: Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint64_t total_read = 0; michael@0: int32_t just_read = 0; michael@0: char* dest_chars = reinterpret_cast(aBuffer.rwget().data); michael@0: do { michael@0: just_read = PR_Read(file, dest_chars + total_read, michael@0: std::min(uint64_t(PR_INT32_MAX), bytes - total_read)); michael@0: if (just_read == -1) { michael@0: Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: total_read += just_read; michael@0: } while (just_read != 0 && total_read < bytes); michael@0: if (total_read != bytes) { michael@0: // We seem to have a race condition here. michael@0: Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: /** michael@0: * Any steps that need to be taken before reading. michael@0: * michael@0: * In case of error, this method should call Fail() and return michael@0: * a failure code. michael@0: */ michael@0: virtual michael@0: nsresult BeforeRead() { michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Proceed after reading. michael@0: */ michael@0: virtual michael@0: void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0; michael@0: michael@0: protected: michael@0: const nsString mPath; michael@0: const uint64_t mBytes; michael@0: }; michael@0: michael@0: /** michael@0: * An implementation of a Read event that provides the data michael@0: * as a TypedArray. michael@0: */ michael@0: class DoReadToTypedArrayEvent MOZ_FINAL : public AbstractReadEvent { michael@0: public: michael@0: DoReadToTypedArrayEvent(const nsAString& aPath, michael@0: const uint32_t aBytes, michael@0: already_AddRefed&& aOnSuccess, michael@0: already_AddRefed&& aOnError) michael@0: : AbstractReadEvent(aPath, aBytes, michael@0: aOnSuccess, aOnError) michael@0: , mResult(new TypedArrayResult(TimeStamp::Now())) michael@0: { } michael@0: michael@0: ~DoReadToTypedArrayEvent() { michael@0: // If AbstractReadEvent::Run() has bailed out, we may need to cleanup michael@0: // mResult, which is main-thread only data michael@0: if (!mResult) { michael@0: return; michael@0: } michael@0: nsCOMPtr main = do_GetMainThread(); michael@0: (void)NS_ProxyRelease(main, mResult); michael@0: } michael@0: michael@0: protected: michael@0: void AfterRead(TimeStamp aDispatchDate, michael@0: ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget()); michael@0: Succeed(mResult.forget()); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mResult; michael@0: }; michael@0: michael@0: /** michael@0: * An implementation of a Read event that provides the data michael@0: * as a JavaScript string. michael@0: */ michael@0: class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent { michael@0: public: michael@0: DoReadToStringEvent(const nsAString& aPath, michael@0: const nsACString& aEncoding, michael@0: const uint32_t aBytes, michael@0: already_AddRefed&& aOnSuccess, michael@0: already_AddRefed&& aOnError) michael@0: : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) michael@0: , mEncoding(aEncoding) michael@0: , mResult(new StringResult(TimeStamp::Now())) michael@0: { } michael@0: michael@0: ~DoReadToStringEvent() { michael@0: // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup michael@0: // mResult, which is main-thread only data michael@0: if (!mResult) { michael@0: return; michael@0: } michael@0: nsCOMPtr main = do_GetMainThread(); michael@0: (void)NS_ProxyRelease(main, mResult); michael@0: } michael@0: michael@0: protected: michael@0: nsresult BeforeRead() MOZ_OVERRIDE { michael@0: // Obtain the decoder. We do this before reading to avoid doing michael@0: // any unnecessary I/O in case the name of the encoding is incorrect. michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: nsAutoCString encodingName; michael@0: if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) { michael@0: Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName); michael@0: if (!mDecoder) { michael@0: Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void AfterRead(TimeStamp aDispatchDate, michael@0: ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: int32_t maxChars; michael@0: const char* sourceChars = reinterpret_cast(aBuffer.get().data); michael@0: int32_t sourceBytes = aBuffer.get().nbytes; michael@0: if (sourceBytes < 0) { michael@0: Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL); michael@0: return; michael@0: } michael@0: michael@0: if (maxChars < 0) { michael@0: Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); michael@0: return; michael@0: } michael@0: michael@0: nsString resultString; michael@0: resultString.SetLength(maxChars); michael@0: if (resultString.Length() != (nsString::size_type)maxChars) { michael@0: Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE); michael@0: return; michael@0: } michael@0: michael@0: michael@0: rv = mDecoder->Convert(sourceChars, &sourceBytes, michael@0: resultString.BeginWriting(), &maxChars); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: resultString.SetLength(maxChars); michael@0: michael@0: mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString); michael@0: Succeed(mResult.forget()); michael@0: } michael@0: michael@0: private: michael@0: nsCString mEncoding; michael@0: nsCOMPtr mDecoder; michael@0: nsRefPtr mResult; michael@0: }; michael@0: michael@0: } // osfile michael@0: michael@0: // The OS.File service michael@0: michael@0: NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService); michael@0: michael@0: NS_IMETHODIMP michael@0: NativeOSFileInternalsService::Read(const nsAString& aPath, michael@0: JS::HandleValue aOptions, michael@0: nsINativeOSFileSuccessCallback *aOnSuccess, michael@0: nsINativeOSFileErrorCallback *aOnError, michael@0: JSContext* cx) michael@0: { michael@0: // Extract options michael@0: nsCString encoding; michael@0: uint64_t bytes = UINT64_MAX; michael@0: michael@0: if (aOptions.isObject()) { michael@0: dom::NativeOSFileReadOptions dict; michael@0: if (!dict.Init(cx, aOptions)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (dict.mEncoding.WasPassed()) { michael@0: CopyUTF16toUTF8(dict.mEncoding.Value(), encoding); michael@0: } michael@0: michael@0: if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) { michael@0: bytes = dict.mBytes.Value().Value(); michael@0: } michael@0: } michael@0: michael@0: // Prepare the off main thread event and dispatch it michael@0: nsCOMPtr onSuccess(aOnSuccess); michael@0: nsCOMPtr onError(aOnError); michael@0: michael@0: nsRefPtr event; michael@0: if (encoding.IsEmpty()) { michael@0: event = new DoReadToTypedArrayEvent(aPath, bytes, michael@0: onSuccess.forget(), michael@0: onError.forget()); michael@0: } else { michael@0: event = new DoReadToStringEvent(aPath, encoding, bytes, michael@0: onSuccess.forget(), michael@0: onError.forget()); michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: return target->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: