forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1263 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1263 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * Native implementation of some OS.File operations.
 | |
|  */
 | |
| 
 | |
| #include "NativeOSFileInternals.h"
 | |
| 
 | |
| #include "nsString.h"
 | |
| #include "nsNetCID.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsCycleCollectionParticipant.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsProxyRelease.h"
 | |
| 
 | |
| #include "mozilla/dom/NativeOSFileInternalsBinding.h"
 | |
| 
 | |
| #include "mozilla/Encoding.h"
 | |
| #include "nsIEventTarget.h"
 | |
| 
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Scoped.h"
 | |
| #include "mozilla/HoldDropJSObjects.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| 
 | |
| #include "prio.h"
 | |
| #include "prerror.h"
 | |
| #include "private/pprio.h"
 | |
| 
 | |
| #include "jsapi.h"
 | |
| #include "jsfriendapi.h"
 | |
| #include "js/ArrayBuffer.h"  // JS::GetArrayBufferByteLength,IsArrayBufferObject,NewArrayBufferWithContents,StealArrayBufferContents
 | |
| #include "js/experimental/TypedData.h"  // JS_NewUint8ArrayWithBuffer
 | |
| #include "js/Utility.h"
 | |
| #include "xpcpublic.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #if defined(XP_UNIX)
 | |
| #  include <errno.h>
 | |
| #endif  // defined (XP_UNIX)
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
| #  include <windows.h>
 | |
| #endif  // defined (XP_WIN)
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
 | |
|                                           PR_Close)
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // Utilities for safely manipulating ArrayBuffer contents even in the
 | |
| // absence of a JSContext.
 | |
| 
 | |
| /**
 | |
|  * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
 | |
|  * this instead of a void* buffer, as this lets us transfer data across threads
 | |
|  * and into JavaScript without copy.
 | |
|  */
 | |
| struct ArrayBufferContents {
 | |
|   /**
 | |
|    * The data of the ArrayBuffer. This is the pointer manipulated to
 | |
|    * read/write the contents of the buffer.
 | |
|    */
 | |
|   uint8_t* data;
 | |
|   /**
 | |
|    * The number of bytes in the ArrayBuffer.
 | |
|    */
 | |
|   size_t nbytes;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * RAII for ArrayBufferContents.
 | |
|  */
 | |
| struct ScopedArrayBufferContentsTraits {
 | |
|   typedef ArrayBufferContents type;
 | |
|   const static type empty() {
 | |
|     type result = {0, 0};
 | |
|     return result;
 | |
|   }
 | |
|   static void release(type ptr) {
 | |
|     js_free(ptr.data);
 | |
|     ptr.data = nullptr;
 | |
|     ptr.nbytes = 0;
 | |
|   }
 | |
| };
 | |
| 
 | |
| struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents
 | |
|     : public Scoped<ScopedArrayBufferContentsTraits> {
 | |
|   explicit ScopedArrayBufferContents()
 | |
|       : Scoped<ScopedArrayBufferContentsTraits>() {}
 | |
| 
 | |
|   ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
 | |
|     Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
 | |
|     return *this;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Request memory for this ArrayBufferContent. This memory may later
 | |
|    * be used to create an ArrayBuffer object (possibly on another
 | |
|    * thread) without copy.
 | |
|    *
 | |
|    * @return true In case of success, false otherwise.
 | |
|    */
 | |
|   bool Allocate(uint32_t length) {
 | |
|     dispose();
 | |
|     ArrayBufferContents& value = rwget();
 | |
|     void* ptr = js_calloc(1, length);
 | |
|     if (ptr) {
 | |
|       value.data = (uint8_t*)ptr;
 | |
|       value.nbytes = length;
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) =
 | |
|       delete;
 | |
|   ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) =
 | |
|       delete;
 | |
| };
 | |
| 
 | |
| ///////// Cross-platform issues
 | |
| 
 | |
| // Platform specific constants. As OS.File always uses OS-level
 | |
| // errors, we need to map a few high-level errors to OS-level
 | |
| // constants.
 | |
| #if defined(XP_UNIX)
 | |
| #  define OS_ERROR_FILE_EXISTS EEXIST
 | |
| #  define OS_ERROR_NOMEM ENOMEM
 | |
| #  define OS_ERROR_INVAL EINVAL
 | |
| #  define OS_ERROR_TOO_LARGE EFBIG
 | |
| #  define OS_ERROR_RACE EIO
 | |
| #elif defined(XP_WIN)
 | |
| #  define OS_ERROR_FILE_EXISTS ERROR_ALREADY_EXISTS
 | |
| #  define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
 | |
| #  define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
 | |
| #  define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
 | |
| #  define OS_ERROR_RACE ERROR_SHARING_VIOLATION
 | |
| #else
 | |
| #  error "We do not have platform-specific constants for this platform"
 | |
| #endif
 | |
| 
 | |
| ///////// Results of OS.File operations
 | |
| 
 | |
| /**
 | |
|  * Base class for results passed to the callbacks.
 | |
|  *
 | |
|  * This base class implements caching of JS values returned to the client.
 | |
|  * We make use of this caching in derived classes e.g. to avoid accidents
 | |
|  * when we transfer data allocated on another thread into JS. Note that
 | |
|  * this caching can lead to cycles (e.g. if a client adds a back-reference
 | |
|  * in the JS value), so we implement all Cycle Collector primitives in
 | |
|  * AbstractResult.
 | |
|  */
 | |
| class AbstractResult : public nsINativeOSFileResult {
 | |
|  public:
 | |
|   NS_DECL_NSINATIVEOSFILERESULT
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
 | |
| 
 | |
|   /**
 | |
|    * Construct the result object. Must be called on the main thread
 | |
|    * as the AbstractResult is cycle-collected.
 | |
|    *
 | |
|    * @param aStartDate The instant at which the operation was
 | |
|    * requested.  Used to collect Telemetry statistics.
 | |
|    */
 | |
|   explicit AbstractResult(TimeStamp aStartDate) : mStartDate(aStartDate) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     mozilla::HoldJSObjects(this);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Setup the AbstractResult once data is available.
 | |
|    *
 | |
|    * @param aDispatchDate The instant at which the IO thread received
 | |
|    * the operation request. Used to collect Telemetry statistics.
 | |
|    * @param aExecutionDuration The duration of the operation on the
 | |
|    * IO thread.
 | |
|    */
 | |
|   void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     mDispatchDuration = (aDispatchDate - mStartDate);
 | |
|     mExecutionDuration = aExecutionDuration;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Drop any data that could lead to a cycle.
 | |
|    */
 | |
|   void DropJSData() { mCachedResult = JS::UndefinedValue(); }
 | |
| 
 | |
|  protected:
 | |
|   virtual ~AbstractResult() {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     mozilla::DropJSObjects(this);
 | |
|   }
 | |
| 
 | |
|   virtual nsresult GetCacheableResult(JSContext* cx,
 | |
|                                       JS::MutableHandle<JS::Value> aResult) = 0;
 | |
| 
 | |
|  private:
 | |
|   TimeStamp mStartDate;
 | |
|   TimeDuration mDispatchDuration;
 | |
|   TimeDuration mExecutionDuration;
 | |
|   JS::Heap<JS::Value> mCachedResult;
 | |
| };
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedResult)
 | |
| NS_IMPL_CYCLE_COLLECTION_TRACE_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
 | |
|   tmp->DropJSData();
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractResult::GetDispatchDurationMS(double* aDispatchDuration) {
 | |
|   *aDispatchDuration = mDispatchDuration.ToMilliseconds();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractResult::GetExecutionDurationMS(double* aExecutionDuration) {
 | |
|   *aExecutionDuration = mExecutionDuration.ToMilliseconds();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractResult::GetResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
 | |
|   if (mCachedResult.isUndefined()) {
 | |
|     nsresult rv = GetCacheableResult(cx, aResult);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return rv;
 | |
|     }
 | |
|     mCachedResult = aResult;
 | |
|     return NS_OK;
 | |
|   }
 | |
|   aResult.set(mCachedResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a result as a string.
 | |
|  *
 | |
|  * In this implementation, attribute |result| is a string. Strings are
 | |
|  * passed to JS without copy.
 | |
|  */
 | |
| class StringResult final : public AbstractResult {
 | |
|  public:
 | |
|   explicit StringResult(TimeStamp aStartDate) : AbstractResult(aStartDate) {}
 | |
| 
 | |
|   /**
 | |
|    * Initialize the object once the contents of the result as available.
 | |
|    *
 | |
|    * @param aContents The string to pass to JavaScript. Ownership of the
 | |
|    * string and its contents is passed to StringResult. The string must
 | |
|    * be valid UTF-16.
 | |
|    */
 | |
|   void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration,
 | |
|             nsString& aContents) {
 | |
|     AbstractResult::Init(aDispatchDate, aExecutionDuration);
 | |
|     mContents = aContents;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   nsresult GetCacheableResult(JSContext* cx,
 | |
|                               JS::MutableHandle<JS::Value> aResult) override;
 | |
| 
 | |
|  private:
 | |
|   nsString mContents;
 | |
| };
 | |
| 
 | |
| nsresult StringResult::GetCacheableResult(
 | |
|     JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(mContents.get());
 | |
| 
 | |
|   // Convert mContents to a js string without copy. Note that this
 | |
|   // may have the side-effect of stealing the contents of the string
 | |
|   // from XPCOM and into JS.
 | |
|   if (!xpc::StringToJsval(cx, mContents, aResult)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a result as a Uint8Array.
 | |
|  *
 | |
|  * In this implementation, attribute |result| is a Uint8Array. The array
 | |
|  * is passed to JS without memory copy.
 | |
|  */
 | |
| class TypedArrayResult final : public AbstractResult {
 | |
|  public:
 | |
|   explicit TypedArrayResult(TimeStamp aStartDate)
 | |
|       : AbstractResult(aStartDate) {}
 | |
| 
 | |
|   /**
 | |
|    * @param aContents The contents to pass to JS. Calling this method.
 | |
|    * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
 | |
|    * Do not reuse this value anywhere else.
 | |
|    */
 | |
|   void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration,
 | |
|             ArrayBufferContents aContents) {
 | |
|     AbstractResult::Init(aDispatchDate, aExecutionDuration);
 | |
|     mContents = aContents;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   nsresult GetCacheableResult(JSContext* cx,
 | |
|                               JS::MutableHandle<JS::Value> aResult) override;
 | |
| 
 | |
|  private:
 | |
|   ScopedArrayBufferContents mContents;
 | |
| };
 | |
| 
 | |
| nsresult TypedArrayResult::GetCacheableResult(
 | |
|     JSContext* cx, JS::MutableHandle<JS::Value> aResult) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   // We cannot simply construct a typed array using contents.data as
 | |
|   // this would allow us to have several otherwise unrelated
 | |
|   // ArrayBuffers with the same underlying C buffer. As this would be
 | |
|   // very unsafe, we need to cache the result once we have it.
 | |
| 
 | |
|   const ArrayBufferContents& contents = mContents.get();
 | |
|   MOZ_ASSERT(contents.data);
 | |
| 
 | |
|   // This takes ownership of the buffer and notes the memory allocation.
 | |
|   JS::Rooted<JSObject*> arrayBuffer(
 | |
|       cx, JS::NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
 | |
|   if (!arrayBuffer) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   JS::Rooted<JSObject*> result(
 | |
|       cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, 0, contents.nbytes));
 | |
|   if (!result) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
|   mContents.forget();
 | |
| 
 | |
|   aResult.setObject(*result);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a result as an int32_t.
 | |
|  *
 | |
|  * In this implementation, attribute |result| is an int32_t.
 | |
|  */
 | |
| class Int32Result final : public AbstractResult {
 | |
|  public:
 | |
|   explicit Int32Result(TimeStamp aStartDate)
 | |
|       : AbstractResult(aStartDate), mContents(0) {}
 | |
| 
 | |
|   /**
 | |
|    * Initialize the object once the contents of the result are available.
 | |
|    *
 | |
|    * @param aContents The contents to pass to JS. This is an int32_t.
 | |
|    */
 | |
|   void Init(TimeStamp aDispatchDate, TimeDuration aExecutionDuration,
 | |
|             int32_t aContents) {
 | |
|     AbstractResult::Init(aDispatchDate, aExecutionDuration);
 | |
|     mContents = aContents;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   nsresult GetCacheableResult(JSContext* cx,
 | |
|                               JS::MutableHandle<JS::Value> aResult) override;
 | |
| 
 | |
|  private:
 | |
|   int32_t mContents;
 | |
| };
 | |
| 
 | |
| nsresult Int32Result::GetCacheableResult(JSContext* cx,
 | |
|                                          JS::MutableHandle<JS::Value> aResult) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   aResult.set(JS::NumberValue(mContents));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //////// Callback events
 | |
| 
 | |
| /**
 | |
|  * An event used to notify asynchronously of an error.
 | |
|  */
 | |
| class OSFileErrorEvent final : public Runnable {
 | |
|  public:
 | |
|   /**
 | |
|    * @param aOnSuccess The success callback.
 | |
|    * @param aOnError The error callback.
 | |
|    * @param aDiscardedResult The discarded result.
 | |
|    * @param aOperation The name of the operation, used for error reporting.
 | |
|    * @param aOSError The OS error of the operation, as returned by errno/
 | |
|    * GetLastError().
 | |
|    *
 | |
|    * Note that we pass both the success callback and the error
 | |
|    * callback, as well as the discarded result to ensure that they are
 | |
|    * all released on the main thread, rather than on the IO thread
 | |
|    * (which would hopefully segfault). Also, we pass the callbacks as
 | |
|    * alread_AddRefed to ensure that we do not manipulate main-thread
 | |
|    * only refcounters off the main thread.
 | |
|    */
 | |
|   OSFileErrorEvent(
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
 | |
|       already_AddRefed<AbstractResult>& aDiscardedResult,
 | |
|       const nsACString& aOperation, int32_t aOSError)
 | |
|       : Runnable("OSFileErrorEvent"),
 | |
|         mOnSuccess(aOnSuccess),
 | |
|         mOnError(aOnError),
 | |
|         mDiscardedResult(aDiscardedResult),
 | |
|         mOSError(aOSError),
 | |
|         mOperation(aOperation) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     (void)mOnError->Complete(mOperation, mOSError);
 | |
| 
 | |
|     // Ensure that the callbacks are released on the main thread.
 | |
|     mOnSuccess = nullptr;
 | |
|     mOnError = nullptr;
 | |
|     mDiscardedResult = nullptr;
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
 | |
|   // xpconnect values, which cannot be manipulated with nsCOMPtr off
 | |
|   // the main thread. We store both the success callback and the
 | |
|   // error callback to ensure that they are safely released on the
 | |
|   // main thread.
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
 | |
|   RefPtr<AbstractResult> mDiscardedResult;
 | |
|   int32_t mOSError;
 | |
|   nsCString mOperation;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * An event used to notify of a success.
 | |
|  */
 | |
| class SuccessEvent final : public Runnable {
 | |
|  public:
 | |
|   /**
 | |
|    * @param aOnSuccess The success callback.
 | |
|    * @param aOnError The error callback.
 | |
|    *
 | |
|    * Note that we pass both the success callback and the error
 | |
|    * callback to ensure that they are both released on the main
 | |
|    * thread, rather than on the IO thread (which would hopefully
 | |
|    * segfault). Also, we pass them as alread_AddRefed to ensure that
 | |
|    * we do not manipulate xpconnect refcounters off the main thread
 | |
|    * (which is illegal).
 | |
|    */
 | |
|   SuccessEvent(
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
 | |
|       already_AddRefed<nsINativeOSFileResult>& aResult)
 | |
|       : Runnable("SuccessEvent"),
 | |
|         mOnSuccess(aOnSuccess),
 | |
|         mOnError(aOnError),
 | |
|         mResult(aResult) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     (void)mOnSuccess->Complete(mResult);
 | |
| 
 | |
|     // Ensure that the callbacks are released on the main thread.
 | |
|     mOnSuccess = nullptr;
 | |
|     mOnError = nullptr;
 | |
|     mResult = nullptr;
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
 | |
|   // xpconnect values, which cannot be manipulated with nsCOMPtr off
 | |
|   // the main thread. We store both the success callback and the
 | |
|   // error callback to ensure that they are safely released on the
 | |
|   // main thread.
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
 | |
|   RefPtr<nsINativeOSFileResult> mResult;
 | |
| };
 | |
| 
 | |
| //////// Action events
 | |
| 
 | |
| /**
 | |
|  * Base class shared by actions.
 | |
|  */
 | |
| class AbstractDoEvent : public Runnable {
 | |
|  public:
 | |
|   AbstractDoEvent(
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
 | |
|       : Runnable("AbstractDoEvent"),
 | |
|         mOnSuccess(aOnSuccess),
 | |
|         mOnError(aOnError)
 | |
| #if defined(DEBUG)
 | |
|         ,
 | |
|         mResolved(false)
 | |
| #endif  // defined(DEBUG)
 | |
|   {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Fail, asynchronously.
 | |
|    */
 | |
|   void Fail(const nsACString& aOperation,
 | |
|             already_AddRefed<AbstractResult>&& aDiscardedResult,
 | |
|             int32_t aOSError = 0) {
 | |
|     Resolve();
 | |
| 
 | |
|     RefPtr<OSFileErrorEvent> event = new OSFileErrorEvent(
 | |
|         mOnSuccess, mOnError, aDiscardedResult, aOperation, aOSError);
 | |
|     nsresult rv = NS_DispatchToMainThread(event);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Last ditch attempt to release on the main thread - some of
 | |
|       // the members of event are not thread-safe, so letting the
 | |
|       // pointer go out of scope would cause a crash.
 | |
|       NS_ReleaseOnMainThread("AbstractDoEvent::OSFileErrorEvent",
 | |
|                              event.forget());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Succeed, asynchronously.
 | |
|    */
 | |
|   void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
 | |
|     Resolve();
 | |
|     RefPtr<SuccessEvent> event =
 | |
|         new SuccessEvent(mOnSuccess, mOnError, aResult);
 | |
|     nsresult rv = NS_DispatchToMainThread(event);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Last ditch attempt to release on the main thread - some of
 | |
|       // the members of event are not thread-safe, so letting the
 | |
|       // pointer go out of scope would cause a crash.
 | |
|       NS_ReleaseOnMainThread("AbstractDoEvent::SuccessEvent", event.forget());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   /**
 | |
|    * Mark the event as complete, for debugging purposes.
 | |
|    */
 | |
|   void Resolve() {
 | |
| #if defined(DEBUG)
 | |
|     MOZ_ASSERT(!mResolved);
 | |
|     mResolved = true;
 | |
| #endif  // defined(DEBUG)
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
 | |
| #if defined(DEBUG)
 | |
|   // |true| once the action is complete
 | |
|   bool mResolved;
 | |
| #endif  // defined(DEBUG)
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * An abstract event implementing reading from a file.
 | |
|  *
 | |
|  * Concrete subclasses are responsible for handling the
 | |
|  * data obtained from the file and possibly post-processing it.
 | |
|  */
 | |
| class AbstractReadEvent : public AbstractDoEvent {
 | |
|  public:
 | |
|   /**
 | |
|    * @param aPath The path of the file.
 | |
|    */
 | |
|   AbstractReadEvent(
 | |
|       const nsAString& aPath, const uint64_t aBytes,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
 | |
|       : AbstractDoEvent(aOnSuccess, aOnError), mPath(aPath), mBytes(aBytes) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     TimeStamp dispatchDate = TimeStamp::Now();
 | |
| 
 | |
|     nsresult rv = BeforeRead();
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Error reporting is handled by BeforeRead();
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     ScopedArrayBufferContents buffer;
 | |
|     rv = Read(buffer);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Error reporting is handled by Read();
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     AfterRead(dispatchDate, buffer);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   /**
 | |
|    * Read synchronously.
 | |
|    *
 | |
|    * Must be called off the main thread.
 | |
|    *
 | |
|    * @param aBuffer The destination buffer.
 | |
|    */
 | |
|   nsresult Read(ScopedArrayBufferContents& aBuffer) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     ScopedPRFileDesc file;
 | |
| #if defined(XP_WIN)
 | |
|     // On Windows, we can't use PR_OpenFile because it doesn't
 | |
|     // handle UTF-16 encoding, which is pretty bad. In addition,
 | |
|     // PR_OpenFile opens files without sharing, which is not the
 | |
|     // general semantics of OS.File.
 | |
|     HANDLE handle =
 | |
|         ::CreateFileW(mPath.get(), GENERIC_READ,
 | |
|                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
 | |
|                       /*Security attributes*/ nullptr, OPEN_EXISTING,
 | |
|                       FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
 | |
|                       /*Template file*/ nullptr);
 | |
| 
 | |
|     if (handle == INVALID_HANDLE_VALUE) {
 | |
|       Fail("open"_ns, nullptr, ::GetLastError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     file = PR_ImportFile((PROsfd)handle);
 | |
|     if (!file) {
 | |
|       // |file| is closed by PR_ImportFile
 | |
|       Fail("ImportFile"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
| #else
 | |
|     // On other platforms, PR_OpenFile will do.
 | |
|     NS_ConvertUTF16toUTF8 path(mPath);
 | |
|     file = PR_OpenFile(path.get(), PR_RDONLY, 0);
 | |
|     if (!file) {
 | |
|       Fail("open"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
| #endif  // defined(XP_XIN)
 | |
| 
 | |
|     PRFileInfo64 stat;
 | |
|     if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
 | |
|       Fail("stat"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
 | |
|     if (bytes > UINT32_MAX) {
 | |
|       Fail("Arithmetics"_ns, nullptr, OS_ERROR_INVAL);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     if (!aBuffer.Allocate(bytes)) {
 | |
|       Fail("allocate"_ns, nullptr, OS_ERROR_NOMEM);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     uint64_t total_read = 0;
 | |
|     int32_t just_read = 0;
 | |
|     char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
 | |
|     do {
 | |
|       just_read = PR_Read(file, dest_chars + total_read,
 | |
|                           std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
 | |
|       if (just_read == -1) {
 | |
|         Fail("read"_ns, nullptr, PR_GetOSError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
|       total_read += just_read;
 | |
|     } while (just_read != 0 && total_read < bytes);
 | |
|     if (total_read != bytes) {
 | |
|       // We seem to have a race condition here.
 | |
|       Fail("read"_ns, nullptr, OS_ERROR_RACE);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   /**
 | |
|    * Any steps that need to be taken before reading.
 | |
|    *
 | |
|    * In case of error, this method should call Fail() and return
 | |
|    * a failure code.
 | |
|    */
 | |
|   virtual nsresult BeforeRead() { return NS_OK; }
 | |
| 
 | |
|   /**
 | |
|    * Proceed after reading.
 | |
|    */
 | |
|   virtual void AfterRead(TimeStamp aDispatchDate,
 | |
|                          ScopedArrayBufferContents& aBuffer) = 0;
 | |
| 
 | |
|  protected:
 | |
|   const nsString mPath;
 | |
|   const uint64_t mBytes;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * An implementation of a Read event that provides the data
 | |
|  * as a TypedArray.
 | |
|  */
 | |
| class DoReadToTypedArrayEvent final : public AbstractReadEvent {
 | |
|  public:
 | |
|   DoReadToTypedArrayEvent(
 | |
|       const nsAString& aPath, const uint32_t aBytes,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
 | |
|       : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError),
 | |
|         mResult(new TypedArrayResult(TimeStamp::Now())) {}
 | |
| 
 | |
|   ~DoReadToTypedArrayEvent() override {
 | |
|     // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
 | |
|     // mResult, which is main-thread only data
 | |
|     if (!mResult) {
 | |
|       return;
 | |
|     }
 | |
|     NS_ReleaseOnMainThread("DoReadToTypedArrayEvent::mResult",
 | |
|                            mResult.forget());
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   void AfterRead(TimeStamp aDispatchDate,
 | |
|                  ScopedArrayBufferContents& aBuffer) override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate,
 | |
|                   aBuffer.forget());
 | |
|     Succeed(mResult.forget());
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<TypedArrayResult> mResult;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * An implementation of a Read event that provides the data
 | |
|  * as a JavaScript string.
 | |
|  */
 | |
| class DoReadToStringEvent final : public AbstractReadEvent {
 | |
|  public:
 | |
|   DoReadToStringEvent(
 | |
|       const nsAString& aPath, const nsACString& aEncoding,
 | |
|       const uint32_t aBytes,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
 | |
|       : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError),
 | |
|         mEncoding(aEncoding),
 | |
|         mResult(new StringResult(TimeStamp::Now())) {}
 | |
| 
 | |
|   ~DoReadToStringEvent() override {
 | |
|     // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
 | |
|     // mResult, which is main-thread only data
 | |
|     if (!mResult) {
 | |
|       return;
 | |
|     }
 | |
|     NS_ReleaseOnMainThread("DoReadToStringEvent::mResult", mResult.forget());
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   nsresult BeforeRead() override {
 | |
|     // Obtain the decoder. We do this before reading to avoid doing
 | |
|     // any unnecessary I/O in case the name of the encoding is incorrect.
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     const Encoding* encoding = Encoding::ForLabel(mEncoding);
 | |
|     if (!encoding) {
 | |
|       Fail("Decode"_ns, mResult.forget(), OS_ERROR_INVAL);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
|     mDecoder = encoding->NewDecoderWithBOMRemoval();
 | |
|     if (!mDecoder) {
 | |
|       Fail("DecoderForEncoding"_ns, mResult.forget(), OS_ERROR_INVAL);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   void AfterRead(TimeStamp aDispatchDate,
 | |
|                  ScopedArrayBufferContents& aBuffer) override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     auto src = Span(aBuffer.get().data, aBuffer.get().nbytes);
 | |
| 
 | |
|     CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(src.Length());
 | |
|     if (!needed.isValid() ||
 | |
|         needed.value() > std::numeric_limits<nsAString::size_type>::max()) {
 | |
|       Fail("arithmetics"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsString resultString;
 | |
|     auto resultSpan = resultString.GetMutableData(needed.value(), fallible);
 | |
|     if (!resultSpan) {
 | |
|       Fail("allocation"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Yoric said on IRC that this method is normally called for the entire
 | |
|     // file, but that's not guaranteed. Retaining the bug that EOF in conversion
 | |
|     // isn't handled anywhere.
 | |
|     uint32_t result;
 | |
|     size_t read;
 | |
|     size_t written;
 | |
|     std::tie(result, read, written, std::ignore) =
 | |
|         mDecoder->DecodeToUTF16(src, *resultSpan, false);
 | |
|     MOZ_ASSERT(result == kInputEmpty);
 | |
|     MOZ_ASSERT(read == src.Length());
 | |
|     MOZ_ASSERT(written <= needed.value());
 | |
|     bool ok = resultString.SetLength(written, fallible);
 | |
|     if (!ok) {
 | |
|       Fail("allocation"_ns, mResult.forget(), OS_ERROR_TOO_LARGE);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate,
 | |
|                   resultString);
 | |
|     Succeed(mResult.forget());
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   nsCString mEncoding;
 | |
|   mozilla::UniquePtr<mozilla::Decoder> mDecoder;
 | |
|   RefPtr<StringResult> mResult;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * An event implenting writing atomically to a file.
 | |
|  */
 | |
| class DoWriteAtomicEvent : public AbstractDoEvent {
 | |
|  public:
 | |
|   /**
 | |
|    * @param aPath The path of the file.
 | |
|    */
 | |
|   DoWriteAtomicEvent(
 | |
|       const nsAString& aPath, UniquePtr<char[], JS::FreePolicy> aBuffer,
 | |
|       const uint64_t aBytes, const nsAString& aTmpPath,
 | |
|       const nsAString& aBackupTo, const bool aFlush, const bool aNoOverwrite,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
 | |
|       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
 | |
|       : AbstractDoEvent(aOnSuccess, aOnError),
 | |
|         mPath(aPath),
 | |
|         mBuffer(std::move(aBuffer)),
 | |
|         mBytes(aBytes),
 | |
|         mTmpPath(aTmpPath),
 | |
|         mBackupTo(aBackupTo),
 | |
|         mFlush(aFlush),
 | |
|         mNoOverwrite(aNoOverwrite),
 | |
|         mResult(new Int32Result(TimeStamp::Now())) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|   }
 | |
| 
 | |
|   ~DoWriteAtomicEvent() override {
 | |
|     // If Run() has bailed out, we may need to cleanup
 | |
|     // mResult, which is main-thread only data
 | |
|     if (!mResult) {
 | |
|       return;
 | |
|     }
 | |
|     NS_ReleaseOnMainThread("DoWriteAtomicEvent::mResult", mResult.forget());
 | |
|   }
 | |
| 
 | |
|   NS_IMETHODIMP Run() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     TimeStamp dispatchDate = TimeStamp::Now();
 | |
|     int32_t bytesWritten;
 | |
| 
 | |
|     nsresult rv = WriteAtomic(&bytesWritten);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     AfterWriteAtomic(dispatchDate, bytesWritten);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   /**
 | |
|    * Write atomically to a file.
 | |
|    * Must be called off the main thread.
 | |
|    * @param aBytesWritten will contain the total bytes written.
 | |
|    * This does not support compression in this implementation.
 | |
|    */
 | |
|   nsresult WriteAtomic(int32_t* aBytesWritten) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     // Note: In Windows, many NSPR File I/O functions which act on pathnames
 | |
|     // do not handle UTF-16 encoding. Thus, we use the following functions
 | |
|     // to overcome this.
 | |
|     // PR_Access : GetFileAttributesW
 | |
|     // PR_Delete : DeleteFileW
 | |
|     // PR_OpenFile : CreateFileW followed by PR_ImportFile
 | |
|     // PR_Rename : MoveFileW
 | |
| 
 | |
|     ScopedPRFileDesc file;
 | |
|     NS_ConvertUTF16toUTF8 path(mPath);
 | |
|     NS_ConvertUTF16toUTF8 tmpPath(mTmpPath);
 | |
|     NS_ConvertUTF16toUTF8 backupTo(mBackupTo);
 | |
|     bool fileExists = false;
 | |
| 
 | |
|     if (!mTmpPath.IsVoid() || !mBackupTo.IsVoid() || mNoOverwrite) {
 | |
|       // fileExists needs to be computed in the case of tmpPath, since
 | |
|       // the rename behaves differently depending on whether the
 | |
|       // file already exists. It's also computed for backupTo since the
 | |
|       // backup can be skipped if the file does not exist in the first place.
 | |
| #if defined(XP_WIN)
 | |
|       fileExists = ::GetFileAttributesW(mPath.get()) != INVALID_FILE_ATTRIBUTES;
 | |
| #else
 | |
|       fileExists = PR_Access(path.get(), PR_ACCESS_EXISTS) == PR_SUCCESS;
 | |
| #endif  // defined(XP_WIN)
 | |
|     }
 | |
| 
 | |
|     // Check noOverwrite.
 | |
|     if (mNoOverwrite && fileExists) {
 | |
|       Fail("noOverwrite"_ns, nullptr, OS_ERROR_FILE_EXISTS);
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     // Backup the original file if it exists.
 | |
|     if (!mBackupTo.IsVoid() && fileExists) {
 | |
| #if defined(XP_WIN)
 | |
|       if (::GetFileAttributesW(mBackupTo.get()) != INVALID_FILE_ATTRIBUTES) {
 | |
|         // The file specified by mBackupTo exists, so we need to delete it
 | |
|         // first.
 | |
|         if (::DeleteFileW(mBackupTo.get()) == false) {
 | |
|           Fail("delete"_ns, nullptr, ::GetLastError());
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (::MoveFileW(mPath.get(), mBackupTo.get()) == false) {
 | |
|         Fail("rename"_ns, nullptr, ::GetLastError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
| #else
 | |
|       if (PR_Access(backupTo.get(), PR_ACCESS_EXISTS) == PR_SUCCESS) {
 | |
|         // The file specified by mBackupTo exists, so we need to delete it
 | |
|         // first.
 | |
|         if (PR_Delete(backupTo.get()) == PR_FAILURE) {
 | |
|           Fail("delete"_ns, nullptr, PR_GetOSError());
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (PR_Rename(path.get(), backupTo.get()) == PR_FAILURE) {
 | |
|         Fail("rename"_ns, nullptr, PR_GetOSError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
| #endif  // defined(XP_WIN)
 | |
|     }
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
|     // In addition to not handling UTF-16 encoding in file paths,
 | |
|     // PR_OpenFile opens files without sharing, which is not the
 | |
|     // general semantics of OS.File.
 | |
|     HANDLE handle;
 | |
|     // if we're dealing with a tmpFile, we need to write there.
 | |
|     if (!mTmpPath.IsVoid()) {
 | |
|       handle = ::CreateFileW(
 | |
|           mTmpPath.get(), GENERIC_WRITE,
 | |
|           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
 | |
|           /*Security attributes*/ nullptr,
 | |
|           // CREATE_ALWAYS is used since since we need to create the temporary
 | |
|           // file, which we don't care about overwriting.
 | |
|           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
 | |
|           /*Template file*/ nullptr);
 | |
|     } else {
 | |
|       handle = ::CreateFileW(
 | |
|           mPath.get(), GENERIC_WRITE,
 | |
|           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
 | |
|           /*Security attributes*/ nullptr,
 | |
|           // CREATE_ALWAYS is used since since have already checked the
 | |
|           // noOverwrite condition, and thus can overwrite safely.
 | |
|           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
 | |
|           /*Template file*/ nullptr);
 | |
|     }
 | |
| 
 | |
|     if (handle == INVALID_HANDLE_VALUE) {
 | |
|       Fail("open"_ns, nullptr, ::GetLastError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     file = PR_ImportFile((PROsfd)handle);
 | |
|     if (!file) {
 | |
|       // |file| is closed by PR_ImportFile
 | |
|       Fail("ImportFile"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
| #else
 | |
|     // if we're dealing with a tmpFile, we need to write there.
 | |
|     if (!mTmpPath.IsVoid()) {
 | |
|       file =
 | |
|           PR_OpenFile(tmpPath.get(), PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
 | |
|                       PR_IRUSR | PR_IWUSR);
 | |
|     } else {
 | |
|       file = PR_OpenFile(path.get(), PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
 | |
|                          PR_IRUSR | PR_IWUSR);
 | |
|     }
 | |
| 
 | |
|     if (!file) {
 | |
|       Fail("open"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| #endif  // defined(XP_WIN)
 | |
| 
 | |
|     int32_t bytesWrittenSuccess =
 | |
|         PR_Write(file, (void*)(mBuffer.get()), mBytes);
 | |
| 
 | |
|     if (bytesWrittenSuccess == -1) {
 | |
|       Fail("write"_ns, nullptr, PR_GetOSError());
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     // Apply any tmpPath renames.
 | |
|     if (!mTmpPath.IsVoid()) {
 | |
|       if (mBackupTo.IsVoid() && fileExists) {
 | |
|         // We need to delete the old file first, if it exists and we haven't
 | |
|         // already renamed it as a part of backing it up.
 | |
| #if defined(XP_WIN)
 | |
|         if (::DeleteFileW(mPath.get()) == false) {
 | |
|           Fail("delete"_ns, nullptr, ::GetLastError());
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
| #else
 | |
|         if (PR_Delete(path.get()) == PR_FAILURE) {
 | |
|           Fail("delete"_ns, nullptr, PR_GetOSError());
 | |
|           return NS_ERROR_FAILURE;
 | |
|         }
 | |
| #endif  // defined(XP_WIN)
 | |
|       }
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
|       if (::MoveFileW(mTmpPath.get(), mPath.get()) == false) {
 | |
|         Fail("rename"_ns, nullptr, ::GetLastError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
| #else
 | |
|       if (PR_Rename(tmpPath.get(), path.get()) == PR_FAILURE) {
 | |
|         Fail("rename"_ns, nullptr, PR_GetOSError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
| #endif  // defined(XP_WIN)
 | |
|     }
 | |
| 
 | |
|     if (mFlush) {
 | |
|       if (PR_Sync(file) == PR_FAILURE) {
 | |
|         Fail("sync"_ns, nullptr, PR_GetOSError());
 | |
|         return NS_ERROR_FAILURE;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     *aBytesWritten = bytesWrittenSuccess;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   nsresult AfterWriteAtomic(TimeStamp aDispatchDate, int32_t aBytesWritten) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate,
 | |
|                   aBytesWritten);
 | |
|     Succeed(mResult.forget());
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   const nsString mPath;
 | |
|   const UniquePtr<char[], JS::FreePolicy> mBuffer;
 | |
|   const int32_t mBytes;
 | |
|   const nsString mTmpPath;
 | |
|   const nsString mBackupTo;
 | |
|   const bool mFlush;
 | |
|   const bool mNoOverwrite;
 | |
| 
 | |
|  private:
 | |
|   RefPtr<Int32Result> mResult;
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| // The OS.File service
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(NativeOSFileInternalsService,
 | |
|                   nsINativeOSFileInternalsService);
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| NativeOSFileInternalsService::Read(const nsAString& aPath,
 | |
|                                    JS::Handle<JS::Value> aOptions,
 | |
|                                    nsINativeOSFileSuccessCallback* aOnSuccess,
 | |
|                                    nsINativeOSFileErrorCallback* aOnError,
 | |
|                                    JSContext* cx) {
 | |
|   // Extract options
 | |
|   nsCString encoding;
 | |
|   uint64_t bytes = UINT64_MAX;
 | |
| 
 | |
|   if (aOptions.isObject()) {
 | |
|     dom::NativeOSFileReadOptions dict;
 | |
|     if (!dict.Init(cx, aOptions)) {
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|     }
 | |
| 
 | |
|     if (dict.mEncoding.WasPassed()) {
 | |
|       CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
 | |
|     }
 | |
| 
 | |
|     if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
 | |
|       bytes = dict.mBytes.Value().Value();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Prepare the off main thread event and dispatch it
 | |
|   nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
 | |
|       new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
 | |
|           "nsINativeOSFileSuccessCallback", onSuccess));
 | |
|   nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
 | |
|       new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
 | |
|           "nsINativeOSFileErrorCallback", onError));
 | |
| 
 | |
|   RefPtr<AbstractDoEvent> event;
 | |
|   if (encoding.IsEmpty()) {
 | |
|     event = new DoReadToTypedArrayEvent(aPath, bytes, onSuccessHandle,
 | |
|                                         onErrorHandle);
 | |
|   } else {
 | |
|     event = new DoReadToStringEvent(aPath, encoding, bytes, onSuccessHandle,
 | |
|                                     onErrorHandle);
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIEventTarget> target =
 | |
|       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
 | |
| 
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
|   return target->Dispatch(event, NS_DISPATCH_NORMAL);
 | |
| }
 | |
| 
 | |
| // Note: This method steals the contents of `aBuffer`.
 | |
| NS_IMETHODIMP
 | |
| NativeOSFileInternalsService::WriteAtomic(
 | |
|     const nsAString& aPath, JS::Handle<JS::Value> aBuffer,
 | |
|     JS::Handle<JS::Value> aOptions, nsINativeOSFileSuccessCallback* aOnSuccess,
 | |
|     nsINativeOSFileErrorCallback* aOnError, JSContext* cx) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   // Extract typed-array/string into buffer. We also need to store the length
 | |
|   // of the buffer as that may be required if not provided in `aOptions`.
 | |
|   UniquePtr<char[], JS::FreePolicy> buffer;
 | |
|   int32_t bytes;
 | |
| 
 | |
|   // The incoming buffer must be an Object.
 | |
|   if (!aBuffer.isObject()) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   JS::Rooted<JSObject*> bufferObject(cx, nullptr);
 | |
|   if (!JS_ValueToObject(cx, aBuffer, &bufferObject)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   if (!JS::IsArrayBufferObject(bufferObject.get())) {
 | |
|     return NS_ERROR_INVALID_ARG;
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     // Throw for large ArrayBuffers to prevent truncation.
 | |
|     size_t len = JS::GetArrayBufferByteLength(bufferObject.get());
 | |
|     if (len > INT32_MAX) {
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|     }
 | |
|     bytes = len;
 | |
|   }
 | |
|   buffer.reset(
 | |
|       static_cast<char*>(JS::StealArrayBufferContents(cx, bufferObject)));
 | |
| 
 | |
|   if (!buffer) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Extract options.
 | |
|   dom::NativeOSFileWriteAtomicOptions dict;
 | |
| 
 | |
|   if (aOptions.isObject()) {
 | |
|     if (!dict.Init(cx, aOptions)) {
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|     }
 | |
|   } else {
 | |
|     // If an options object is not provided, initializing with a `null`
 | |
|     // value, which will give a set of defaults defined in the WebIDL binding.
 | |
|     if (!dict.Init(cx, JS::NullHandleValue)) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
 | |
|     // We need to check size and cast because NSPR and WebIDL have different
 | |
|     // types.
 | |
|     if (dict.mBytes.Value().Value() > PR_INT32_MAX) {
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|     }
 | |
|     bytes = (int32_t)(dict.mBytes.Value().Value());
 | |
|   }
 | |
| 
 | |
|   // Prepare the off main thread event and dispatch it
 | |
|   nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
 | |
|       new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
 | |
|           "nsINativeOSFileSuccessCallback", onSuccess));
 | |
|   nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
 | |
|   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
 | |
|       new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
 | |
|           "nsINativeOSFileErrorCallback", onError));
 | |
| 
 | |
|   RefPtr<AbstractDoEvent> event = new DoWriteAtomicEvent(
 | |
|       aPath, std::move(buffer), bytes, dict.mTmpPath, dict.mBackupTo,
 | |
|       dict.mFlush, dict.mNoOverwrite, onSuccessHandle, onErrorHandle);
 | |
|   nsresult rv;
 | |
|   nsCOMPtr<nsIEventTarget> target =
 | |
|       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
 | |
| 
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return target->Dispatch(event, NS_DISPATCH_NORMAL);
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
