LCOV - code coverage report
Current view: top level - toolkit/components/osfile - NativeOSFileInternals.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 221 296 74.7 %
Date: 2017-07-14 16:53:18 Functions: 56 70 80.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* This Source Code Form is subject to the terms of the Mozilla Public
       2             :  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
       3             :  * You can obtain one at http://mozilla.org/MPL/2.0/. */
       4             : 
       5             : /**
       6             :  * Native implementation of some OS.File operations.
       7             :  */
       8             : 
       9             : #include "NativeOSFileInternals.h"
      10             : 
      11             : #include "nsString.h"
      12             : #include "nsNetCID.h"
      13             : #include "nsThreadUtils.h"
      14             : #include "nsXPCOMCID.h"
      15             : #include "nsCycleCollectionParticipant.h"
      16             : #include "nsServiceManagerUtils.h"
      17             : #include "nsProxyRelease.h"
      18             : 
      19             : #include "nsINativeOSFileInternals.h"
      20             : #include "mozilla/dom/NativeOSFileInternalsBinding.h"
      21             : 
      22             : #include "mozilla/Encoding.h"
      23             : #include "nsIEventTarget.h"
      24             : 
      25             : #include "mozilla/DebugOnly.h"
      26             : #include "mozilla/Scoped.h"
      27             : #include "mozilla/HoldDropJSObjects.h"
      28             : #include "mozilla/TimeStamp.h"
      29             : 
      30             : #include "prio.h"
      31             : #include "prerror.h"
      32             : #include "private/pprio.h"
      33             : 
      34             : #include "jsapi.h"
      35             : #include "jsfriendapi.h"
      36             : #include "js/Utility.h"
      37             : #include "xpcpublic.h"
      38             : 
      39             : #include <algorithm>
      40             : #if defined(XP_UNIX)
      41             : #include <unistd.h>
      42             : #include <errno.h>
      43             : #include <fcntl.h>
      44             : #include <sys/stat.h>
      45             : #include <sys/uio.h>
      46             : #endif // defined (XP_UNIX)
      47             : 
      48             : #if defined(XP_WIN)
      49             : #include <windows.h>
      50             : #endif // defined (XP_WIN)
      51             : 
      52             : namespace mozilla {
      53             : 
      54           0 : MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close)
      55             : 
      56             : namespace {
      57             : 
      58             : // Utilities for safely manipulating ArrayBuffer contents even in the
      59             : // absence of a JSContext.
      60             : 
      61             : /**
      62             :  * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
      63             :  * this instead of a void* buffer, as this lets us transfer data across threads
      64             :  * and into JavaScript without copy.
      65             :  */
      66             : struct ArrayBufferContents {
      67             :   /**
      68             :    * The data of the ArrayBuffer. This is the pointer manipulated to
      69             :    * read/write the contents of the buffer.
      70             :    */
      71             :   uint8_t* data;
      72             :   /**
      73             :    * The number of bytes in the ArrayBuffer.
      74             :    */
      75             :   size_t nbytes;
      76             : };
      77             : 
      78             : /**
      79             :  * RAII for ArrayBufferContents.
      80             :  */
      81             : struct ScopedArrayBufferContentsTraits {
      82             :   typedef ArrayBufferContents type;
      83           6 :   const static type empty() {
      84           6 :     type result = {0, 0};
      85           6 :     return result;
      86             :   }
      87           6 :   static void release(type ptr) {
      88           6 :     js_free(ptr.data);
      89           6 :     ptr.data = nullptr;
      90           6 :     ptr.nbytes = 0;
      91           6 :   }
      92             : };
      93             : 
      94           4 : struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> {
      95           4 :   explicit ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM):
      96           4 :     Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
      97           4 :   { }
      98             : 
      99           0 :   ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
     100           0 :     Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
     101           0 :     return *this;
     102             :   }
     103             : 
     104             :   /**
     105             :    * Request memory for this ArrayBufferContent. This memory may later
     106             :    * be used to create an ArrayBuffer object (possibly on another
     107             :    * thread) without copy.
     108             :    *
     109             :    * @return true In case of success, false otherwise.
     110             :    */
     111           2 :   bool Allocate(uint32_t length) {
     112           2 :     dispose();
     113           2 :     ArrayBufferContents& value = rwget();
     114           2 :     void *ptr = js_calloc(1, length);
     115           2 :     if (ptr) {
     116           2 :       value.data = (uint8_t *) ptr;
     117           2 :       value.nbytes = length;
     118           2 :       return true;
     119             :     }
     120           0 :     return false;
     121             :   }
     122             : private:
     123             :   explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) = delete;
     124             :   ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) = delete;
     125             : };
     126             : 
     127             : ///////// Cross-platform issues
     128             : 
     129             : // Platform specific constants. As OS.File always uses OS-level
     130             : // errors, we need to map a few high-level errors to OS-level
     131             : // constants.
     132             : #if defined(XP_UNIX)
     133             : #define OS_ERROR_NOMEM ENOMEM
     134             : #define OS_ERROR_INVAL EINVAL
     135             : #define OS_ERROR_TOO_LARGE EFBIG
     136             : #define OS_ERROR_RACE EIO
     137             : #elif defined(XP_WIN)
     138             : #define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
     139             : #define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
     140             : #define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
     141             : #define OS_ERROR_RACE ERROR_SHARING_VIOLATION
     142             : #else
     143             : #error "We do not have platform-specific constants for this platform"
     144             : #endif
     145             : 
     146             : ///////// Results of OS.File operations
     147             : 
     148             : /**
     149             :  * Base class for results passed to the callbacks.
     150             :  *
     151             :  * This base class implements caching of JS values returned to the client.
     152             :  * We make use of this caching in derived classes e.g. to avoid accidents
     153             :  * when we transfer data allocated on another thread into JS. Note that
     154             :  * this caching can lead to cycles (e.g. if a client adds a back-reference
     155             :  * in the JS value), so we implement all Cycle Collector primitives in
     156             :  * AbstractResult.
     157             :  */
     158             : class AbstractResult: public nsINativeOSFileResult {
     159             : public:
     160             :   NS_DECL_NSINATIVEOSFILERESULT
     161             :   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     162          71 :   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
     163             : 
     164             :   /**
     165             :    * Construct the result object. Must be called on the main thread
     166             :    * as the AbstractResult is cycle-collected.
     167             :    *
     168             :    * @param aStartDate The instant at which the operation was
     169             :    * requested.  Used to collect Telemetry statistics.
     170             :    */
     171           3 :   explicit AbstractResult(TimeStamp aStartDate)
     172           3 :     : mStartDate(aStartDate)
     173             :   {
     174           3 :     MOZ_ASSERT(NS_IsMainThread());
     175           3 :     mozilla::HoldJSObjects(this);
     176           3 :   }
     177             : 
     178             :   /**
     179             :    * Setup the AbstractResult once data is available.
     180             :    *
     181             :    * @param aDispatchDate The instant at which the IO thread received
     182             :    * the operation request. Used to collect Telemetry statistics.
     183             :    * @param aExecutionDuration The duration of the operation on the
     184             :    * IO thread.
     185             :    */
     186           2 :   void Init(TimeStamp aDispatchDate,
     187             :             TimeDuration aExecutionDuration) {
     188           2 :     MOZ_ASSERT(!NS_IsMainThread());
     189             : 
     190           2 :     mDispatchDuration = (aDispatchDate - mStartDate);
     191           2 :     mExecutionDuration = aExecutionDuration;
     192           2 :   }
     193             : 
     194             :   /**
     195             :    * Drop any data that could lead to a cycle.
     196             :    */
     197           1 :   void DropJSData() {
     198           1 :     mCachedResult = JS::UndefinedValue();
     199           1 :   }
     200             : 
     201             : protected:
     202           2 :   virtual ~AbstractResult() {
     203           1 :     MOZ_ASSERT(NS_IsMainThread());
     204           1 :     DropJSData();
     205           1 :     mozilla::DropJSObjects(this);
     206           1 :   }
     207             : 
     208             :   virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0;
     209             : 
     210             : private:
     211             :   TimeStamp mStartDate;
     212             :   TimeDuration mDispatchDuration;
     213             :   TimeDuration mExecutionDuration;
     214             :   JS::Heap<JS::Value> mCachedResult;
     215             : };
     216             : 
     217          17 : NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
     218          12 : NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
     219             : 
     220             : NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
     221             : 
     222          53 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
     223          28 :   NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
     224          24 :   NS_INTERFACE_MAP_ENTRY(nsISupports)
     225          18 : NS_INTERFACE_MAP_END
     226             : 
     227           5 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
     228           5 :   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedResult)
     229           5 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
     230             : 
     231           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
     232           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     233             : 
     234           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
     235           0 :   tmp->DropJSData();
     236           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     237             : 
     238             : NS_IMETHODIMP
     239           0 : AbstractResult::GetDispatchDurationMS(double *aDispatchDuration)
     240             : {
     241           0 :   *aDispatchDuration = mDispatchDuration.ToMilliseconds();
     242           0 :   return NS_OK;
     243             : }
     244             : 
     245             : NS_IMETHODIMP
     246           0 : AbstractResult::GetExecutionDurationMS(double *aExecutionDuration)
     247             : {
     248           0 :   *aExecutionDuration = mExecutionDuration.ToMilliseconds();
     249           0 :   return NS_OK;
     250             : }
     251             : 
     252             : NS_IMETHODIMP
     253           2 : AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult)
     254             : {
     255           2 :   if (mCachedResult.isUndefined()) {
     256           2 :     nsresult rv = GetCacheableResult(cx, aResult);
     257           2 :     if (NS_FAILED(rv)) {
     258           0 :       return rv;
     259             :     }
     260           2 :     mCachedResult = aResult;
     261           2 :     return NS_OK;
     262             :   }
     263           0 :   aResult.set(mCachedResult);
     264           0 :   return NS_OK;
     265             : }
     266             : 
     267             : /**
     268             :  * Return a result as a string.
     269             :  *
     270             :  * In this implementation, attribute |result| is a string. Strings are
     271             :  * passed to JS without copy.
     272             :  */
     273           0 : class StringResult final : public AbstractResult
     274             : {
     275             : public:
     276           2 :   explicit StringResult(TimeStamp aStartDate)
     277           2 :     : AbstractResult(aStartDate)
     278             :   {
     279           2 :   }
     280             : 
     281             :   /**
     282             :    * Initialize the object once the contents of the result as available.
     283             :    *
     284             :    * @param aContents The string to pass to JavaScript. Ownership of the
     285             :    * string and its contents is passed to StringResult. The string must
     286             :    * be valid UTF-16.
     287             :    */
     288           2 :   void Init(TimeStamp aDispatchDate,
     289             :             TimeDuration aExecutionDuration,
     290             :             nsString& aContents) {
     291           2 :     AbstractResult::Init(aDispatchDate, aExecutionDuration);
     292           2 :     mContents = aContents;
     293           2 :   }
     294             : 
     295             : protected:
     296             :   nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
     297             : 
     298             : private:
     299             :   nsString mContents;
     300             : };
     301             : 
     302             : nsresult
     303           2 : StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
     304             : {
     305           2 :   MOZ_ASSERT(NS_IsMainThread());
     306           2 :   MOZ_ASSERT(mContents.get());
     307             : 
     308             :   // Convert mContents to a js string without copy. Note that this
     309             :   // may have the side-effect of stealing the contents of the string
     310             :   // from XPCOM and into JS.
     311           2 :   if (!xpc::StringToJsval(cx, mContents, aResult)) {
     312           0 :     return NS_ERROR_FAILURE;
     313             :   }
     314           2 :   return NS_OK;
     315             : }
     316             : 
     317             : 
     318             : /**
     319             :  * Return a result as a Uint8Array.
     320             :  *
     321             :  * In this implementation, attribute |result| is a Uint8Array. The array
     322             :  * is passed to JS without memory copy.
     323             :  */
     324           3 : class TypedArrayResult final : public AbstractResult
     325             : {
     326             : public:
     327           1 :   explicit TypedArrayResult(TimeStamp aStartDate)
     328           1 :     : AbstractResult(aStartDate)
     329             :   {
     330           1 :   }
     331             : 
     332             :   /**
     333             :    * @param aContents The contents to pass to JS. Calling this method.
     334             :    * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
     335             :    * Do not reuse this value anywhere else.
     336             :    */
     337           0 :   void Init(TimeStamp aDispatchDate,
     338             :             TimeDuration aExecutionDuration,
     339             :             ArrayBufferContents aContents) {
     340           0 :     AbstractResult::Init(aDispatchDate, aExecutionDuration);
     341           0 :     mContents = aContents;
     342           0 :   }
     343             : 
     344             : protected:
     345             :   nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
     346             : private:
     347             :   ScopedArrayBufferContents mContents;
     348             : };
     349             : 
     350             : nsresult
     351           0 : TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
     352             : {
     353           0 :   MOZ_ASSERT(NS_IsMainThread());
     354             :   // We cannot simply construct a typed array using contents.data as
     355             :   // this would allow us to have several otherwise unrelated
     356             :   // ArrayBuffers with the same underlying C buffer. As this would be
     357             :   // very unsafe, we need to cache the result once we have it.
     358             : 
     359           0 :   const ArrayBufferContents& contents = mContents.get();
     360           0 :   MOZ_ASSERT(contents.data);
     361             : 
     362             :   JS::Rooted<JSObject*>
     363           0 :     arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
     364           0 :   if (!arrayBuffer) {
     365           0 :     return NS_ERROR_OUT_OF_MEMORY;
     366             :   }
     367             : 
     368             :   JS::Rooted<JSObject*>
     369           0 :     result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer,
     370           0 :                                           0, contents.nbytes));
     371           0 :   if (!result) {
     372           0 :     return NS_ERROR_OUT_OF_MEMORY;
     373             :   }
     374             :   // The memory of contents has been allocated on a thread that
     375             :   // doesn't have a JSRuntime, hence without a context. Now that we
     376             :   // have a context, attach the memory to where it belongs.
     377           0 :   JS_updateMallocCounter(cx, contents.nbytes);
     378           0 :   mContents.forget();
     379             : 
     380           0 :   aResult.setObject(*result);
     381           0 :   return NS_OK;
     382             : }
     383             : 
     384             : //////// Callback events
     385             : 
     386             : /**
     387             :  * An event used to notify asynchronously of an error.
     388             :  */
     389           3 : class ErrorEvent final : public Runnable {
     390             : public:
     391             :   /**
     392             :    * @param aOnSuccess The success callback.
     393             :    * @param aOnError The error callback.
     394             :    * @param aDiscardedResult The discarded result.
     395             :    * @param aOperation The name of the operation, used for error reporting.
     396             :    * @param aOSError The OS error of the operation, as returned by errno/
     397             :    * GetLastError().
     398             :    *
     399             :    * Note that we pass both the success callback and the error
     400             :    * callback, as well as the discarded result to ensure that they are
     401             :    * all released on the main thread, rather than on the IO thread
     402             :    * (which would hopefully segfault). Also, we pass the callbacks as
     403             :    * alread_AddRefed to ensure that we do not manipulate main-thread
     404             :    * only refcounters off the main thread.
     405             :    */
     406           1 :   ErrorEvent(nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     407             :              nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
     408             :              already_AddRefed<AbstractResult>& aDiscardedResult,
     409             :              const nsACString& aOperation,
     410             :              int32_t aOSError)
     411           1 :     : Runnable("ErrorEvent")
     412             :     , mOnSuccess(aOnSuccess)
     413             :     , mOnError(aOnError)
     414             :     , mDiscardedResult(aDiscardedResult)
     415             :     , mOSError(aOSError)
     416           1 :     , mOperation(aOperation)
     417             :   {
     418           1 :     MOZ_ASSERT(!NS_IsMainThread());
     419           1 :     }
     420             : 
     421           1 :   NS_IMETHOD Run() override {
     422           1 :     MOZ_ASSERT(NS_IsMainThread());
     423           1 :     (void)mOnError->Complete(mOperation, mOSError);
     424             : 
     425             :     // Ensure that the callbacks are released on the main thread.
     426           1 :     mOnSuccess = nullptr;
     427           1 :     mOnError = nullptr;
     428           1 :     mDiscardedResult = nullptr;
     429             : 
     430           1 :     return NS_OK;
     431             :   }
     432             :  private:
     433             :   // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
     434             :   // xpconnect values, which cannot be manipulated with nsCOMPtr off
     435             :   // the main thread. We store both the success callback and the
     436             :   // error callback to ensure that they are safely released on the
     437             :   // main thread.
     438             :   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
     439             :   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
     440             :   RefPtr<AbstractResult> mDiscardedResult;
     441             :   int32_t mOSError;
     442             :   nsCString mOperation;
     443             : };
     444             : 
     445             : /**
     446             :  * An event used to notify of a success.
     447             :  */
     448           6 : class SuccessEvent final : public Runnable {
     449             : public:
     450             :   /**
     451             :    * @param aOnSuccess The success callback.
     452             :    * @param aOnError The error callback.
     453             :    *
     454             :    * Note that we pass both the success callback and the error
     455             :    * callback to ensure that they are both released on the main
     456             :    * thread, rather than on the IO thread (which would hopefully
     457             :    * segfault). Also, we pass them as alread_AddRefed to ensure that
     458             :    * we do not manipulate xpconnect refcounters off the main thread
     459             :    * (which is illegal).
     460             :    */
     461           2 :   SuccessEvent(
     462             :     nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     463             :     nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
     464             :     already_AddRefed<nsINativeOSFileResult>& aResult)
     465           2 :     : Runnable("SuccessEvent")
     466             :     , mOnSuccess(aOnSuccess)
     467             :     , mOnError(aOnError)
     468           2 :     , mResult(aResult)
     469             :   {
     470           2 :     MOZ_ASSERT(!NS_IsMainThread());
     471           2 :     }
     472             : 
     473           2 :   NS_IMETHOD Run() override {
     474           2 :     MOZ_ASSERT(NS_IsMainThread());
     475           2 :     (void)mOnSuccess->Complete(mResult);
     476             : 
     477             :     // Ensure that the callbacks are released on the main thread.
     478           2 :     mOnSuccess = nullptr;
     479           2 :     mOnError = nullptr;
     480           2 :     mResult = nullptr;
     481             : 
     482           2 :     return NS_OK;
     483             :   }
     484             :  private:
     485             :   // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
     486             :   // xpconnect values, which cannot be manipulated with nsCOMPtr off
     487             :   // the main thread. We store both the success callback and the
     488             :   // error callback to ensure that they are safely released on the
     489             :   // main thread.
     490             :   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
     491             :   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
     492             :   RefPtr<nsINativeOSFileResult> mResult;
     493             : };
     494             : 
     495             : 
     496             : //////// Action events
     497             : 
     498             : /**
     499             :  * Base class shared by actions.
     500             :  */
     501           3 : class AbstractDoEvent: public Runnable {
     502             : public:
     503           3 :   AbstractDoEvent(
     504             :     nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     505             :     nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
     506           3 :     : Runnable("AbstractDoEvent")
     507             :     , mOnSuccess(aOnSuccess)
     508             :     , mOnError(aOnError)
     509             : #if defined(DEBUG)
     510           3 :     , mResolved(false)
     511             : #endif // defined(DEBUG)
     512             :   {
     513           3 :     MOZ_ASSERT(NS_IsMainThread());
     514           3 :   }
     515             : 
     516             :   /**
     517             :    * Fail, asynchronously.
     518             :    */
     519           1 :   void Fail(const nsACString& aOperation,
     520             :             already_AddRefed<AbstractResult>&& aDiscardedResult,
     521             :             int32_t aOSError = 0) {
     522           1 :     Resolve();
     523             :     RefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess,
     524             :                                                 mOnError,
     525             :                                                 aDiscardedResult,
     526             :                                                 aOperation,
     527           2 :                                                 aOSError);
     528           1 :     nsresult rv = NS_DispatchToMainThread(event);
     529           1 :     if (NS_FAILED(rv)) {
     530             :       // Last ditch attempt to release on the main thread - some of
     531             :       // the members of event are not thread-safe, so letting the
     532             :       // pointer go out of scope would cause a crash.
     533           0 :       NS_ReleaseOnMainThread("AbstractDoEvent::ErrorEvent", event.forget());
     534             :     }
     535           1 :   }
     536             : 
     537             :   /**
     538             :    * Succeed, asynchronously.
     539             :    */
     540           2 :   void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
     541           2 :     Resolve();
     542             :     RefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess,
     543             :                                                     mOnError,
     544           4 :                                                     aResult);
     545           2 :     nsresult rv = NS_DispatchToMainThread(event);
     546           2 :     if (NS_FAILED(rv)) {
     547             :       // Last ditch attempt to release on the main thread - some of
     548             :       // the members of event are not thread-safe, so letting the
     549             :       // pointer go out of scope would cause a crash.
     550           0 :       NS_ReleaseOnMainThread("AbstractDoEvent::SuccessEvent", event.forget());
     551             :     }
     552             : 
     553           2 :   }
     554             : 
     555             : private:
     556             : 
     557             :   /**
     558             :    * Mark the event as complete, for debugging purposes.
     559             :    */
     560           3 :   void Resolve() {
     561             : #if defined(DEBUG)
     562           3 :     MOZ_ASSERT(!mResolved);
     563           3 :     mResolved = true;
     564             : #endif // defined(DEBUG)
     565           3 :   }
     566             : 
     567             : private:
     568             :   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
     569             :   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
     570             : #if defined(DEBUG)
     571             :   // |true| once the action is complete
     572             :   bool mResolved;
     573             : #endif // defined(DEBUG)
     574             : };
     575             : 
     576             : /**
     577             :  * An abstract event implementing reading from a file.
     578             :  *
     579             :  * Concrete subclasses are responsible for handling the
     580             :  * data obtained from the file and possibly post-processing it.
     581             :  */
     582           3 : class AbstractReadEvent: public AbstractDoEvent {
     583             : public:
     584             :   /**
     585             :    * @param aPath The path of the file.
     586             :    */
     587           3 :   AbstractReadEvent(const nsAString& aPath,
     588             :                     const uint64_t aBytes,
     589             :                     nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     590             :                     nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
     591           3 :     : AbstractDoEvent(aOnSuccess, aOnError)
     592             :     , mPath(aPath)
     593           3 :     , mBytes(aBytes)
     594             :   {
     595           3 :     MOZ_ASSERT(NS_IsMainThread());
     596           3 :   }
     597             : 
     598           3 :   NS_IMETHOD Run() override {
     599           3 :     MOZ_ASSERT(!NS_IsMainThread());
     600           3 :     TimeStamp dispatchDate = TimeStamp::Now();
     601             : 
     602           3 :     nsresult rv = BeforeRead();
     603           3 :     if (NS_FAILED(rv)) {
     604             :       // Error reporting is handled by BeforeRead();
     605           0 :       return NS_OK;
     606             :     }
     607             : 
     608           6 :     ScopedArrayBufferContents buffer;
     609           3 :     rv = Read(buffer);
     610           3 :     if (NS_FAILED(rv)) {
     611             :       // Error reporting is handled by Read();
     612           1 :       return NS_OK;
     613             :     }
     614             : 
     615           2 :     AfterRead(dispatchDate, buffer);
     616           2 :     return NS_OK;
     617             :   }
     618             : 
     619             :  private:
     620             :   /**
     621             :    * Read synchronously.
     622             :    *
     623             :    * Must be called off the main thread.
     624             :    *
     625             :    * @param aBuffer The destination buffer.
     626             :    */
     627           3 :   nsresult Read(ScopedArrayBufferContents& aBuffer)
     628             :   {
     629           3 :     MOZ_ASSERT(!NS_IsMainThread());
     630             : 
     631           6 :     ScopedPRFileDesc file;
     632             : #if defined(XP_WIN)
     633             :     // On Windows, we can't use PR_OpenFile because it doesn't
     634             :     // handle UTF-16 encoding, which is pretty bad. In addition,
     635             :     // PR_OpenFile opens files without sharing, which is not the
     636             :     // general semantics of OS.File.
     637             :     HANDLE handle =
     638             :       ::CreateFileW(mPath.get(),
     639             :                     GENERIC_READ,
     640             :                     FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
     641             :                     /*Security attributes*/nullptr,
     642             :                     OPEN_EXISTING,
     643             :                     FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
     644             :                     /*Template file*/ nullptr);
     645             : 
     646             :     if (handle == INVALID_HANDLE_VALUE) {
     647             :       Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
     648             :       return NS_ERROR_FAILURE;
     649             :     }
     650             : 
     651             :     file = PR_ImportFile((PROsfd)handle);
     652             :     if (!file) {
     653             :       // |file| is closed by PR_ImportFile
     654             :       Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
     655             :       return NS_ERROR_FAILURE;
     656             :     }
     657             : 
     658             : #else
     659             :     // On other platforms, PR_OpenFile will do.
     660           6 :     NS_ConvertUTF16toUTF8 path(mPath);
     661           3 :     file = PR_OpenFile(path.get(), PR_RDONLY, 0);
     662           3 :     if (!file) {
     663           1 :       Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
     664           1 :       return NS_ERROR_FAILURE;
     665             :     }
     666             : 
     667             : #endif // defined(XP_XIN)
     668             : 
     669             :     PRFileInfo64 stat;
     670           2 :     if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
     671           0 :       Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError());
     672           0 :       return NS_ERROR_FAILURE;
     673             :     }
     674             : 
     675           2 :     uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
     676           2 :     if (bytes > UINT32_MAX) {
     677           0 :       Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL);
     678           0 :       return NS_ERROR_FAILURE;
     679             :     }
     680             : 
     681           2 :     if (!aBuffer.Allocate(bytes)) {
     682           0 :       Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM);
     683           0 :       return NS_ERROR_FAILURE;
     684             :     }
     685             : 
     686           2 :     uint64_t total_read = 0;
     687           2 :     int32_t just_read = 0;
     688           2 :     char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
     689           0 :     do {
     690           4 :       just_read = PR_Read(file, dest_chars + total_read,
     691           6 :                           std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
     692           2 :       if (just_read == -1) {
     693           0 :         Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError());
     694           0 :         return NS_ERROR_FAILURE;
     695             :       }
     696           2 :       total_read += just_read;
     697           2 :     } while (just_read != 0 && total_read < bytes);
     698           2 :     if (total_read != bytes) {
     699             :       // We seem to have a race condition here.
     700           0 :       Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE);
     701           0 :       return NS_ERROR_FAILURE;
     702             :     }
     703             : 
     704           2 :     return NS_OK;
     705             :   }
     706             : 
     707             : protected:
     708             :   /**
     709             :    * Any steps that need to be taken before reading.
     710             :    *
     711             :    * In case of error, this method should call Fail() and return
     712             :    * a failure code.
     713             :    */
     714             :   virtual
     715           1 :   nsresult BeforeRead() {
     716           1 :     return NS_OK;
     717             :   }
     718             : 
     719             :   /**
     720             :    * Proceed after reading.
     721             :    */
     722             :   virtual
     723             :   void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0;
     724             : 
     725             :  protected:
     726             :   const nsString mPath;
     727             :   const uint64_t mBytes;
     728             : };
     729             : 
     730             : /**
     731             :  * An implementation of a Read event that provides the data
     732             :  * as a TypedArray.
     733             :  */
     734             : class DoReadToTypedArrayEvent final : public AbstractReadEvent {
     735             : public:
     736           1 :   DoReadToTypedArrayEvent(const nsAString& aPath,
     737             :                           const uint32_t aBytes,
     738             :                           nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     739             :                           nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
     740           1 :     : AbstractReadEvent(aPath, aBytes,
     741             :                         aOnSuccess, aOnError)
     742           2 :     , mResult(new TypedArrayResult(TimeStamp::Now()))
     743           1 :   { }
     744             : 
     745           3 :   ~DoReadToTypedArrayEvent() override {
     746             :     // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
     747             :     // mResult, which is main-thread only data
     748           1 :     if (!mResult) {
     749           0 :       return;
     750             :     }
     751           1 :     NS_ReleaseOnMainThread("DoReadToTypedArrayEvent::mResult", mResult.forget());
     752           3 :   }
     753             : 
     754             : protected:
     755           0 :   void AfterRead(TimeStamp aDispatchDate,
     756             :                  ScopedArrayBufferContents& aBuffer) override {
     757           0 :     MOZ_ASSERT(!NS_IsMainThread());
     758           0 :     mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget());
     759           0 :     Succeed(mResult.forget());
     760           0 :   }
     761             : 
     762             :  private:
     763             :   RefPtr<TypedArrayResult> mResult;
     764             : };
     765             : 
     766             : /**
     767             :  * An implementation of a Read event that provides the data
     768             :  * as a JavaScript string.
     769             :  */
     770             : class DoReadToStringEvent final : public AbstractReadEvent {
     771             : public:
     772           2 :   DoReadToStringEvent(const nsAString& aPath,
     773             :                       const nsACString& aEncoding,
     774             :                       const uint32_t aBytes,
     775             :                       nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
     776             :                       nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
     777           2 :     : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError)
     778             :     , mEncoding(aEncoding)
     779           4 :     , mResult(new StringResult(TimeStamp::Now()))
     780           2 :   { }
     781             : 
     782           6 :   ~DoReadToStringEvent() override {
     783             :     // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
     784             :     // mResult, which is main-thread only data
     785           2 :     if (!mResult) {
     786           2 :       return;
     787             :     }
     788           0 :     NS_ReleaseOnMainThread("DoReadToStringEvent::mResult", mResult.forget());
     789           6 :   }
     790             : 
     791             : protected:
     792           2 :   nsresult BeforeRead() override {
     793             :     // Obtain the decoder. We do this before reading to avoid doing
     794             :     // any unnecessary I/O in case the name of the encoding is incorrect.
     795           2 :     MOZ_ASSERT(!NS_IsMainThread());
     796           2 :     const Encoding* encoding = Encoding::ForLabel(mEncoding);
     797           2 :     if (!encoding) {
     798           0 :       Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL);
     799           0 :       return NS_ERROR_FAILURE;
     800             :     }
     801           2 :     mDecoder = encoding->NewDecoderWithBOMRemoval();
     802           2 :     if (!mDecoder) {
     803           0 :       Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL);
     804           0 :       return NS_ERROR_FAILURE;
     805             :     }
     806             : 
     807           2 :     return NS_OK;
     808             :   }
     809             : 
     810           2 :   void AfterRead(TimeStamp aDispatchDate,
     811             :                  ScopedArrayBufferContents& aBuffer) override {
     812           2 :     MOZ_ASSERT(!NS_IsMainThread());
     813             : 
     814           2 :     auto src = MakeSpan(aBuffer.get().data, aBuffer.get().nbytes);
     815             : 
     816           2 :     CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(src.Length());
     817           4 :     if (!needed.isValid() ||
     818           2 :         needed.value() > MaxValue<nsAString::size_type>::value) {
     819           0 :       Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
     820           0 :       return;
     821             :     }
     822             : 
     823           4 :     nsString resultString;
     824           2 :     bool ok = resultString.SetLength(needed.value(), fallible);
     825           2 :     if (!ok) {
     826           0 :       Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
     827           0 :       return;
     828             :     }
     829             : 
     830             :     // Yoric said on IRC that this method is normally called for the entire file,
     831             :     // but that's not guaranteed. Retaining the bug that EOF in conversion isn't
     832             :     // handled anywhere.
     833             :     uint32_t result;
     834             :     size_t read;
     835             :     size_t written;
     836             :     bool hadErrors;
     837           4 :     Tie(result, read, written, hadErrors) =
     838           6 :       mDecoder->DecodeToUTF16(src, resultString, false);
     839           2 :     MOZ_ASSERT(result == kInputEmpty);
     840           2 :     MOZ_ASSERT(read == src.Length());
     841           2 :     MOZ_ASSERT(written <= needed.value());
     842             :     Unused << hadErrors;
     843           2 :     ok = resultString.SetLength(written, fallible);
     844           2 :     if (!ok) {
     845           0 :       Fail(
     846           0 :         NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
     847           0 :       return;
     848             :     }
     849             : 
     850           2 :     mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString);
     851           2 :     Succeed(mResult.forget());
     852             :   }
     853             : 
     854             :  private:
     855             :   nsCString mEncoding;
     856             :   mozilla::UniquePtr<mozilla::Decoder> mDecoder;
     857             :   RefPtr<StringResult> mResult;
     858             : };
     859             : 
     860             : } // namespace
     861             : 
     862             : // The OS.File service
     863             : 
     864          45 : NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
     865             : 
     866             : NS_IMETHODIMP
     867           3 : NativeOSFileInternalsService::Read(const nsAString& aPath,
     868             :                                    JS::HandleValue aOptions,
     869             :                                    nsINativeOSFileSuccessCallback *aOnSuccess,
     870             :                                    nsINativeOSFileErrorCallback *aOnError,
     871             :                                    JSContext* cx)
     872             : {
     873             :   // Extract options
     874           6 :   nsCString encoding;
     875           3 :   uint64_t bytes = UINT64_MAX;
     876             : 
     877           3 :   if (aOptions.isObject()) {
     878           6 :     dom::NativeOSFileReadOptions dict;
     879           3 :     if (!dict.Init(cx, aOptions)) {
     880           0 :       return NS_ERROR_INVALID_ARG;
     881             :     }
     882             : 
     883           3 :     if (dict.mEncoding.WasPassed()) {
     884           2 :       CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
     885             :     }
     886             : 
     887           3 :     if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
     888           0 :       bytes = dict.mBytes.Value().Value();
     889             :     }
     890             :   }
     891             : 
     892             :   // Prepare the off main thread event and dispatch it
     893           6 :   nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
     894             :   nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
     895             :     new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
     896           9 :       "nsINativeOSFileSuccessCallback", onSuccess));
     897           6 :   nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
     898             :   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
     899             :     new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
     900           9 :       "nsINativeOSFileErrorCallback", onError));
     901             : 
     902           6 :   RefPtr<AbstractDoEvent> event;
     903           3 :   if (encoding.IsEmpty()) {
     904             :     event = new DoReadToTypedArrayEvent(aPath, bytes,
     905             :                                         onSuccessHandle,
     906           1 :                                         onErrorHandle);
     907             :   } else {
     908             :     event = new DoReadToStringEvent(aPath, encoding, bytes,
     909             :                                     onSuccessHandle,
     910           2 :                                     onErrorHandle);
     911             :   }
     912             : 
     913             :   nsresult rv;
     914           6 :   nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
     915             : 
     916           3 :   if (NS_FAILED(rv)) {
     917           0 :     return rv;
     918             :   }
     919           3 :   return target->Dispatch(event, NS_DISPATCH_NORMAL);
     920             : }
     921             : 
     922             : } // namespace mozilla

Generated by: LCOV version 1.13