Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/osfile/NativeOSFileInternals.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/**
8
 * Native implementation of some OS.File operations.
9
 */
10
11
#include "NativeOSFileInternals.h"
12
13
#include "nsString.h"
14
#include "nsNetCID.h"
15
#include "nsThreadUtils.h"
16
#include "nsXPCOMCID.h"
17
#include "nsCycleCollectionParticipant.h"
18
#include "nsServiceManagerUtils.h"
19
#include "nsProxyRelease.h"
20
21
#include "nsINativeOSFileInternals.h"
22
#include "mozilla/dom/NativeOSFileInternalsBinding.h"
23
24
#include "mozilla/Encoding.h"
25
#include "nsIEventTarget.h"
26
27
#include "mozilla/DebugOnly.h"
28
#include "mozilla/Scoped.h"
29
#include "mozilla/HoldDropJSObjects.h"
30
#include "mozilla/TimeStamp.h"
31
#include "mozilla/UniquePtr.h"
32
33
#include "prio.h"
34
#include "prerror.h"
35
#include "private/pprio.h"
36
37
#include "jsapi.h"
38
#include "jsfriendapi.h"
39
#include "js/Conversions.h"
40
#include "js/Utility.h"
41
#include "xpcpublic.h"
42
43
#include <algorithm>
44
#if defined(XP_UNIX)
45
#include <unistd.h>
46
#include <errno.h>
47
#include <fcntl.h>
48
#include <sys/stat.h>
49
#include <sys/uio.h>
50
#endif // defined (XP_UNIX)
51
52
#if defined(XP_WIN)
53
#include <windows.h>
54
#endif // defined (XP_WIN)
55
56
namespace mozilla {
57
58
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close)
59
60
namespace {
61
62
// Utilities for safely manipulating ArrayBuffer contents even in the
63
// absence of a JSContext.
64
65
/**
66
 * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
67
 * this instead of a void* buffer, as this lets us transfer data across threads
68
 * and into JavaScript without copy.
69
 */
70
struct ArrayBufferContents {
71
  /**
72
   * The data of the ArrayBuffer. This is the pointer manipulated to
73
   * read/write the contents of the buffer.
74
   */
75
  uint8_t* data;
76
  /**
77
   * The number of bytes in the ArrayBuffer.
78
   */
79
  size_t nbytes;
80
};
81
82
/**
83
 * RAII for ArrayBufferContents.
84
 */
85
struct ScopedArrayBufferContentsTraits {
86
  typedef ArrayBufferContents type;
87
0
  const static type empty() {
88
0
    type result = {0, 0};
89
0
    return result;
90
0
  }
91
0
  static void release(type ptr) {
92
0
    js_free(ptr.data);
93
0
    ptr.data = nullptr;
94
0
    ptr.nbytes = 0;
95
0
  }
96
};
97
98
struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> {
99
  explicit ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM):
100
    Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
101
0
  { }
102
103
0
  ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
104
0
    Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
105
0
    return *this;
106
0
  }
107
108
  /**
109
   * Request memory for this ArrayBufferContent. This memory may later
110
   * be used to create an ArrayBuffer object (possibly on another
111
   * thread) without copy.
112
   *
113
   * @return true In case of success, false otherwise.
114
   */
115
0
  bool Allocate(uint32_t length) {
116
0
    dispose();
117
0
    ArrayBufferContents& value = rwget();
118
0
    void *ptr = js_calloc(1, length);
119
0
    if (ptr) {
120
0
      value.data = (uint8_t *) ptr;
121
0
      value.nbytes = length;
122
0
      return true;
123
0
    }
124
0
    return false;
125
0
  }
126
private:
127
  explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) = delete;
128
  ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) = delete;
129
};
130
131
///////// Cross-platform issues
132
133
// Platform specific constants. As OS.File always uses OS-level
134
// errors, we need to map a few high-level errors to OS-level
135
// constants.
136
#if defined(XP_UNIX)
137
0
#define OS_ERROR_FILE_EXISTS EEXIST
138
0
#define OS_ERROR_NOMEM ENOMEM
139
0
#define OS_ERROR_INVAL EINVAL
140
0
#define OS_ERROR_TOO_LARGE EFBIG
141
0
#define OS_ERROR_RACE EIO
142
#elif defined(XP_WIN)
143
#define OS_ERROR_FILE_EXISTS ERROR_ALREADY_EXISTS
144
#define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
145
#define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
146
#define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
147
#define OS_ERROR_RACE ERROR_SHARING_VIOLATION
148
#else
149
#error "We do not have platform-specific constants for this platform"
150
#endif
151
152
///////// Results of OS.File operations
153
154
/**
155
 * Base class for results passed to the callbacks.
156
 *
157
 * This base class implements caching of JS values returned to the client.
158
 * We make use of this caching in derived classes e.g. to avoid accidents
159
 * when we transfer data allocated on another thread into JS. Note that
160
 * this caching can lead to cycles (e.g. if a client adds a back-reference
161
 * in the JS value), so we implement all Cycle Collector primitives in
162
 * AbstractResult.
163
 */
164
class AbstractResult: public nsINativeOSFileResult {
165
public:
166
  NS_DECL_NSINATIVEOSFILERESULT
167
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
168
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
169
170
  /**
171
   * Construct the result object. Must be called on the main thread
172
   * as the AbstractResult is cycle-collected.
173
   *
174
   * @param aStartDate The instant at which the operation was
175
   * requested.  Used to collect Telemetry statistics.
176
   */
177
  explicit AbstractResult(TimeStamp aStartDate)
178
    : mStartDate(aStartDate)
179
0
  {
180
0
    MOZ_ASSERT(NS_IsMainThread());
181
0
    mozilla::HoldJSObjects(this);
182
0
  }
183
184
  /**
185
   * Setup the AbstractResult once data is available.
186
   *
187
   * @param aDispatchDate The instant at which the IO thread received
188
   * the operation request. Used to collect Telemetry statistics.
189
   * @param aExecutionDuration The duration of the operation on the
190
   * IO thread.
191
   */
192
  void Init(TimeStamp aDispatchDate,
193
0
            TimeDuration aExecutionDuration) {
194
0
    MOZ_ASSERT(!NS_IsMainThread());
195
0
196
0
    mDispatchDuration = (aDispatchDate - mStartDate);
197
0
    mExecutionDuration = aExecutionDuration;
198
0
  }
199
200
  /**
201
   * Drop any data that could lead to a cycle.
202
   */
203
0
  void DropJSData() {
204
0
    mCachedResult = JS::UndefinedValue();
205
0
  }
206
207
protected:
208
0
  virtual ~AbstractResult() {
209
0
    MOZ_ASSERT(NS_IsMainThread());
210
0
    DropJSData();
211
0
    mozilla::DropJSObjects(this);
212
0
  }
213
214
  virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0;
215
216
private:
217
  TimeStamp mStartDate;
218
  TimeDuration mDispatchDuration;
219
  TimeDuration mExecutionDuration;
220
  JS::Heap<JS::Value> mCachedResult;
221
};
222
223
NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
224
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
225
226
NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
227
228
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
229
0
  NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
230
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
231
0
NS_INTERFACE_MAP_END
232
233
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
234
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedResult)
235
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
236
237
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
238
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
239
240
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
241
0
  tmp->DropJSData();
242
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
243
244
NS_IMETHODIMP
245
AbstractResult::GetDispatchDurationMS(double *aDispatchDuration)
246
0
{
247
0
  *aDispatchDuration = mDispatchDuration.ToMilliseconds();
248
0
  return NS_OK;
249
0
}
250
251
NS_IMETHODIMP
252
AbstractResult::GetExecutionDurationMS(double *aExecutionDuration)
253
0
{
254
0
  *aExecutionDuration = mExecutionDuration.ToMilliseconds();
255
0
  return NS_OK;
256
0
}
257
258
NS_IMETHODIMP
259
AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult)
260
0
{
261
0
  if (mCachedResult.isUndefined()) {
262
0
    nsresult rv = GetCacheableResult(cx, aResult);
263
0
    if (NS_FAILED(rv)) {
264
0
      return rv;
265
0
    }
266
0
    mCachedResult = aResult;
267
0
    return NS_OK;
268
0
  }
269
0
  aResult.set(mCachedResult);
270
0
  return NS_OK;
271
0
}
272
273
/**
274
 * Return a result as a string.
275
 *
276
 * In this implementation, attribute |result| is a string. Strings are
277
 * passed to JS without copy.
278
 */
279
class StringResult final : public AbstractResult
280
{
281
public:
282
  explicit StringResult(TimeStamp aStartDate)
283
    : AbstractResult(aStartDate)
284
0
  {
285
0
  }
286
287
  /**
288
   * Initialize the object once the contents of the result as available.
289
   *
290
   * @param aContents The string to pass to JavaScript. Ownership of the
291
   * string and its contents is passed to StringResult. The string must
292
   * be valid UTF-16.
293
   */
294
  void Init(TimeStamp aDispatchDate,
295
            TimeDuration aExecutionDuration,
296
0
            nsString& aContents) {
297
0
    AbstractResult::Init(aDispatchDate, aExecutionDuration);
298
0
    mContents = aContents;
299
0
  }
300
301
protected:
302
  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
303
304
private:
305
  nsString mContents;
306
};
307
308
nsresult
309
StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
310
0
{
311
0
  MOZ_ASSERT(NS_IsMainThread());
312
0
  MOZ_ASSERT(mContents.get());
313
0
314
0
  // Convert mContents to a js string without copy. Note that this
315
0
  // may have the side-effect of stealing the contents of the string
316
0
  // from XPCOM and into JS.
317
0
  if (!xpc::StringToJsval(cx, mContents, aResult)) {
318
0
    return NS_ERROR_FAILURE;
319
0
  }
320
0
  return NS_OK;
321
0
}
322
323
324
/**
325
 * Return a result as a Uint8Array.
326
 *
327
 * In this implementation, attribute |result| is a Uint8Array. The array
328
 * is passed to JS without memory copy.
329
 */
330
class TypedArrayResult final : public AbstractResult
331
{
332
public:
333
  explicit TypedArrayResult(TimeStamp aStartDate)
334
    : AbstractResult(aStartDate)
335
0
  {
336
0
  }
337
338
  /**
339
   * @param aContents The contents to pass to JS. Calling this method.
340
   * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
341
   * Do not reuse this value anywhere else.
342
   */
343
  void Init(TimeStamp aDispatchDate,
344
            TimeDuration aExecutionDuration,
345
0
            ArrayBufferContents aContents) {
346
0
    AbstractResult::Init(aDispatchDate, aExecutionDuration);
347
0
    mContents = aContents;
348
0
  }
349
350
protected:
351
  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
352
private:
353
  ScopedArrayBufferContents mContents;
354
};
355
356
nsresult
357
TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
358
0
{
359
0
  MOZ_ASSERT(NS_IsMainThread());
360
0
  // We cannot simply construct a typed array using contents.data as
361
0
  // this would allow us to have several otherwise unrelated
362
0
  // ArrayBuffers with the same underlying C buffer. As this would be
363
0
  // very unsafe, we need to cache the result once we have it.
364
0
365
0
  const ArrayBufferContents& contents = mContents.get();
366
0
  MOZ_ASSERT(contents.data);
367
0
368
0
  JS::Rooted<JSObject*>
369
0
    arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
370
0
  if (!arrayBuffer) {
371
0
    return NS_ERROR_OUT_OF_MEMORY;
372
0
  }
373
0
374
0
  JS::Rooted<JSObject*>
375
0
    result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer,
376
0
                                          0, contents.nbytes));
377
0
  if (!result) {
378
0
    return NS_ERROR_OUT_OF_MEMORY;
379
0
  }
380
0
  // The memory of contents has been allocated on a thread that
381
0
  // doesn't have a JSRuntime, hence without a context. Now that we
382
0
  // have a context, attach the memory to where it belongs.
383
0
  JS_updateMallocCounter(cx, contents.nbytes);
384
0
  mContents.forget();
385
0
386
0
  aResult.setObject(*result);
387
0
  return NS_OK;
388
0
}
389
390
/**
391
 * Return a result as an int32_t.
392
 *
393
 * In this implementation, attribute |result| is an int32_t.
394
 */
395
class Int32Result final: public AbstractResult
396
{
397
public:
398
  explicit Int32Result(TimeStamp aStartDate)
399
    : AbstractResult(aStartDate)
400
    , mContents(0)
401
0
  {
402
0
  }
403
404
  /**
405
   * Initialize the object once the contents of the result are available.
406
   *
407
   * @param aContents The contents to pass to JS. This is an int32_t.
408
   */
409
  void Init(TimeStamp aDispatchDate,
410
            TimeDuration aExecutionDuration,
411
0
            int32_t aContents) {
412
0
    AbstractResult::Init(aDispatchDate, aExecutionDuration);
413
0
    mContents = aContents;
414
0
  }
415
416
protected:
417
  nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
418
private:
419
  int32_t mContents;
420
};
421
422
nsresult
423
Int32Result::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
424
0
{
425
0
  MOZ_ASSERT(NS_IsMainThread());
426
0
  aResult.set(JS::NumberValue(mContents));
427
0
  return NS_OK;
428
0
}
429
430
//////// Callback events
431
432
/**
433
 * An event used to notify asynchronously of an error.
434
 */
435
class OSFileErrorEvent final : public Runnable {
436
public:
437
  /**
438
   * @param aOnSuccess The success callback.
439
   * @param aOnError The error callback.
440
   * @param aDiscardedResult The discarded result.
441
   * @param aOperation The name of the operation, used for error reporting.
442
   * @param aOSError The OS error of the operation, as returned by errno/
443
   * GetLastError().
444
   *
445
   * Note that we pass both the success callback and the error
446
   * callback, as well as the discarded result to ensure that they are
447
   * all released on the main thread, rather than on the IO thread
448
   * (which would hopefully segfault). Also, we pass the callbacks as
449
   * alread_AddRefed to ensure that we do not manipulate main-thread
450
   * only refcounters off the main thread.
451
   */
452
  OSFileErrorEvent(nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
453
                   nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
454
                   already_AddRefed<AbstractResult>& aDiscardedResult,
455
                   const nsACString& aOperation,
456
                   int32_t aOSError)
457
    : Runnable("OSFileErrorEvent")
458
    , mOnSuccess(aOnSuccess)
459
    , mOnError(aOnError)
460
    , mDiscardedResult(aDiscardedResult)
461
    , mOSError(aOSError)
462
    , mOperation(aOperation)
463
0
  {
464
0
    MOZ_ASSERT(!NS_IsMainThread());
465
0
    }
466
467
0
  NS_IMETHOD Run() override {
468
0
    MOZ_ASSERT(NS_IsMainThread());
469
0
    (void)mOnError->Complete(mOperation, mOSError);
470
0
471
0
    // Ensure that the callbacks are released on the main thread.
472
0
    mOnSuccess = nullptr;
473
0
    mOnError = nullptr;
474
0
    mDiscardedResult = nullptr;
475
0
476
0
    return NS_OK;
477
0
  }
478
 private:
479
  // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
480
  // xpconnect values, which cannot be manipulated with nsCOMPtr off
481
  // the main thread. We store both the success callback and the
482
  // error callback to ensure that they are safely released on the
483
  // main thread.
484
  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
485
  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
486
  RefPtr<AbstractResult> mDiscardedResult;
487
  int32_t mOSError;
488
  nsCString mOperation;
489
};
490
491
/**
492
 * An event used to notify of a success.
493
 */
494
class SuccessEvent final : public Runnable {
495
public:
496
  /**
497
   * @param aOnSuccess The success callback.
498
   * @param aOnError The error callback.
499
   *
500
   * Note that we pass both the success callback and the error
501
   * callback to ensure that they are both released on the main
502
   * thread, rather than on the IO thread (which would hopefully
503
   * segfault). Also, we pass them as alread_AddRefed to ensure that
504
   * we do not manipulate xpconnect refcounters off the main thread
505
   * (which is illegal).
506
   */
507
  SuccessEvent(
508
    nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
509
    nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
510
    already_AddRefed<nsINativeOSFileResult>& aResult)
511
    : Runnable("SuccessEvent")
512
    , mOnSuccess(aOnSuccess)
513
    , mOnError(aOnError)
514
    , mResult(aResult)
515
0
  {
516
0
    MOZ_ASSERT(!NS_IsMainThread());
517
0
    }
518
519
0
  NS_IMETHOD Run() override {
520
0
    MOZ_ASSERT(NS_IsMainThread());
521
0
    (void)mOnSuccess->Complete(mResult);
522
0
523
0
    // Ensure that the callbacks are released on the main thread.
524
0
    mOnSuccess = nullptr;
525
0
    mOnError = nullptr;
526
0
    mResult = nullptr;
527
0
528
0
    return NS_OK;
529
0
  }
530
 private:
531
  // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
532
  // xpconnect values, which cannot be manipulated with nsCOMPtr off
533
  // the main thread. We store both the success callback and the
534
  // error callback to ensure that they are safely released on the
535
  // main thread.
536
  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
537
  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
538
  RefPtr<nsINativeOSFileResult> mResult;
539
};
540
541
542
//////// Action events
543
544
/**
545
 * Base class shared by actions.
546
 */
547
class AbstractDoEvent: public Runnable {
548
public:
549
  AbstractDoEvent(
550
    nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
551
    nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
552
    : Runnable("AbstractDoEvent")
553
    , mOnSuccess(aOnSuccess)
554
    , mOnError(aOnError)
555
#if defined(DEBUG)
556
    , mResolved(false)
557
#endif // defined(DEBUG)
558
0
  {
559
0
    MOZ_ASSERT(NS_IsMainThread());
560
0
  }
561
562
  /**
563
   * Fail, asynchronously.
564
   */
565
  void Fail(const nsACString& aOperation,
566
            already_AddRefed<AbstractResult>&& aDiscardedResult,
567
0
            int32_t aOSError = 0) {
568
0
    Resolve();
569
0
570
0
    RefPtr<OSFileErrorEvent> event = new OSFileErrorEvent(mOnSuccess,
571
0
                                                          mOnError,
572
0
                                                          aDiscardedResult,
573
0
                                                          aOperation,
574
0
                                                          aOSError);
575
0
    nsresult rv = NS_DispatchToMainThread(event);
576
0
    if (NS_FAILED(rv)) {
577
0
      // Last ditch attempt to release on the main thread - some of
578
0
      // the members of event are not thread-safe, so letting the
579
0
      // pointer go out of scope would cause a crash.
580
0
      NS_ReleaseOnMainThreadSystemGroup("AbstractDoEvent::OSFileErrorEvent",
581
0
                                        event.forget());
582
0
    }
583
0
  }
584
585
  /**
586
   * Succeed, asynchronously.
587
   */
588
0
  void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
589
0
    Resolve();
590
0
    RefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess,
591
0
                                                  mOnError,
592
0
                                                  aResult);
593
0
    nsresult rv = NS_DispatchToMainThread(event);
594
0
    if (NS_FAILED(rv)) {
595
0
      // Last ditch attempt to release on the main thread - some of
596
0
      // the members of event are not thread-safe, so letting the
597
0
      // pointer go out of scope would cause a crash.
598
0
      NS_ReleaseOnMainThreadSystemGroup("AbstractDoEvent::SuccessEvent",
599
0
                                        event.forget());
600
0
    }
601
0
602
0
  }
603
604
private:
605
606
  /**
607
   * Mark the event as complete, for debugging purposes.
608
   */
609
0
  void Resolve() {
610
#if defined(DEBUG)
611
    MOZ_ASSERT(!mResolved);
612
    mResolved = true;
613
#endif // defined(DEBUG)
614
  }
615
616
private:
617
  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
618
  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
619
#if defined(DEBUG)
620
  // |true| once the action is complete
621
  bool mResolved;
622
#endif // defined(DEBUG)
623
};
624
625
/**
626
 * An abstract event implementing reading from a file.
627
 *
628
 * Concrete subclasses are responsible for handling the
629
 * data obtained from the file and possibly post-processing it.
630
 */
631
class AbstractReadEvent: public AbstractDoEvent {
632
public:
633
  /**
634
   * @param aPath The path of the file.
635
   */
636
  AbstractReadEvent(const nsAString& aPath,
637
                    const uint64_t aBytes,
638
                    nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
639
                    nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
640
    : AbstractDoEvent(aOnSuccess, aOnError)
641
    , mPath(aPath)
642
    , mBytes(aBytes)
643
0
  {
644
0
    MOZ_ASSERT(NS_IsMainThread());
645
0
  }
646
647
0
  NS_IMETHOD Run() override {
648
0
    MOZ_ASSERT(!NS_IsMainThread());
649
0
    TimeStamp dispatchDate = TimeStamp::Now();
650
0
651
0
    nsresult rv = BeforeRead();
652
0
    if (NS_FAILED(rv)) {
653
0
      // Error reporting is handled by BeforeRead();
654
0
      return NS_OK;
655
0
    }
656
0
657
0
    ScopedArrayBufferContents buffer;
658
0
    rv = Read(buffer);
659
0
    if (NS_FAILED(rv)) {
660
0
      // Error reporting is handled by Read();
661
0
      return NS_OK;
662
0
    }
663
0
664
0
    AfterRead(dispatchDate, buffer);
665
0
    return NS_OK;
666
0
  }
667
668
 private:
669
  /**
670
   * Read synchronously.
671
   *
672
   * Must be called off the main thread.
673
   *
674
   * @param aBuffer The destination buffer.
675
   */
676
  nsresult Read(ScopedArrayBufferContents& aBuffer)
677
0
  {
678
0
    MOZ_ASSERT(!NS_IsMainThread());
679
0
680
0
    ScopedPRFileDesc file;
681
#if defined(XP_WIN)
682
    // On Windows, we can't use PR_OpenFile because it doesn't
683
    // handle UTF-16 encoding, which is pretty bad. In addition,
684
    // PR_OpenFile opens files without sharing, which is not the
685
    // general semantics of OS.File.
686
    HANDLE handle =
687
      ::CreateFileW(mPath.get(),
688
                    GENERIC_READ,
689
                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
690
                    /*Security attributes*/nullptr,
691
                    OPEN_EXISTING,
692
                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
693
                    /*Template file*/ nullptr);
694
695
    if (handle == INVALID_HANDLE_VALUE) {
696
      Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
697
      return NS_ERROR_FAILURE;
698
    }
699
700
    file = PR_ImportFile((PROsfd)handle);
701
    if (!file) {
702
      // |file| is closed by PR_ImportFile
703
      Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
704
      return NS_ERROR_FAILURE;
705
    }
706
707
#else
708
    // On other platforms, PR_OpenFile will do.
709
0
    NS_ConvertUTF16toUTF8 path(mPath);
710
0
    file = PR_OpenFile(path.get(), PR_RDONLY, 0);
711
0
    if (!file) {
712
0
      Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
713
0
      return NS_ERROR_FAILURE;
714
0
    }
715
0
716
0
#endif // defined(XP_XIN)
717
0
718
0
    PRFileInfo64 stat;
719
0
    if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
720
0
      Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError());
721
0
      return NS_ERROR_FAILURE;
722
0
    }
723
0
724
0
    uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
725
0
    if (bytes > UINT32_MAX) {
726
0
      Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL);
727
0
      return NS_ERROR_FAILURE;
728
0
    }
729
0
730
0
    if (!aBuffer.Allocate(bytes)) {
731
0
      Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM);
732
0
      return NS_ERROR_FAILURE;
733
0
    }
734
0
735
0
    uint64_t total_read = 0;
736
0
    int32_t just_read = 0;
737
0
    char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
738
0
    do {
739
0
      just_read = PR_Read(file, dest_chars + total_read,
740
0
                          std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
741
0
      if (just_read == -1) {
742
0
        Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError());
743
0
        return NS_ERROR_FAILURE;
744
0
      }
745
0
      total_read += just_read;
746
0
    } while (just_read != 0 && total_read < bytes);
747
0
    if (total_read != bytes) {
748
0
      // We seem to have a race condition here.
749
0
      Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE);
750
0
      return NS_ERROR_FAILURE;
751
0
    }
752
0
753
0
    return NS_OK;
754
0
  }
755
756
protected:
757
  /**
758
   * Any steps that need to be taken before reading.
759
   *
760
   * In case of error, this method should call Fail() and return
761
   * a failure code.
762
   */
763
  virtual
764
0
  nsresult BeforeRead() {
765
0
    return NS_OK;
766
0
  }
767
768
  /**
769
   * Proceed after reading.
770
   */
771
  virtual
772
  void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0;
773
774
 protected:
775
  const nsString mPath;
776
  const uint64_t mBytes;
777
};
778
779
/**
780
 * An implementation of a Read event that provides the data
781
 * as a TypedArray.
782
 */
783
class DoReadToTypedArrayEvent final : public AbstractReadEvent {
784
public:
785
  DoReadToTypedArrayEvent(const nsAString& aPath,
786
                          const uint32_t aBytes,
787
                          nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
788
                          nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
789
    : AbstractReadEvent(aPath, aBytes,
790
                        aOnSuccess, aOnError)
791
    , mResult(new TypedArrayResult(TimeStamp::Now()))
792
0
  { }
793
794
0
  ~DoReadToTypedArrayEvent() override {
795
0
    // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
796
0
    // mResult, which is main-thread only data
797
0
    if (!mResult) {
798
0
      return;
799
0
    }
800
0
    NS_ReleaseOnMainThreadSystemGroup("DoReadToTypedArrayEvent::mResult",
801
0
                                      mResult.forget());
802
0
  }
803
804
protected:
805
  void AfterRead(TimeStamp aDispatchDate,
806
0
                 ScopedArrayBufferContents& aBuffer) override {
807
0
    MOZ_ASSERT(!NS_IsMainThread());
808
0
    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget());
809
0
    Succeed(mResult.forget());
810
0
  }
811
812
 private:
813
  RefPtr<TypedArrayResult> mResult;
814
};
815
816
/**
817
 * An implementation of a Read event that provides the data
818
 * as a JavaScript string.
819
 */
820
class DoReadToStringEvent final : public AbstractReadEvent {
821
public:
822
  DoReadToStringEvent(const nsAString& aPath,
823
                      const nsACString& aEncoding,
824
                      const uint32_t aBytes,
825
                      nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
826
                      nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
827
    : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError)
828
    , mEncoding(aEncoding)
829
    , mResult(new StringResult(TimeStamp::Now()))
830
0
  { }
831
832
0
  ~DoReadToStringEvent() override {
833
0
    // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
834
0
    // mResult, which is main-thread only data
835
0
    if (!mResult) {
836
0
      return;
837
0
    }
838
0
    NS_ReleaseOnMainThreadSystemGroup("DoReadToStringEvent::mResult",
839
0
                                      mResult.forget());
840
0
  }
841
842
protected:
843
0
  nsresult BeforeRead() override {
844
0
    // Obtain the decoder. We do this before reading to avoid doing
845
0
    // any unnecessary I/O in case the name of the encoding is incorrect.
846
0
    MOZ_ASSERT(!NS_IsMainThread());
847
0
    const Encoding* encoding = Encoding::ForLabel(mEncoding);
848
0
    if (!encoding) {
849
0
      Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL);
850
0
      return NS_ERROR_FAILURE;
851
0
    }
852
0
    mDecoder = encoding->NewDecoderWithBOMRemoval();
853
0
    if (!mDecoder) {
854
0
      Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL);
855
0
      return NS_ERROR_FAILURE;
856
0
    }
857
0
858
0
    return NS_OK;
859
0
  }
860
861
  void AfterRead(TimeStamp aDispatchDate,
862
0
                 ScopedArrayBufferContents& aBuffer) override {
863
0
    MOZ_ASSERT(!NS_IsMainThread());
864
0
865
0
    auto src = MakeSpan(aBuffer.get().data, aBuffer.get().nbytes);
866
0
867
0
    CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(src.Length());
868
0
    if (!needed.isValid() ||
869
0
        needed.value() > MaxValue<nsAString::size_type>::value) {
870
0
      Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
871
0
      return;
872
0
    }
873
0
874
0
    nsString resultString;
875
0
    bool ok = resultString.SetLength(needed.value(), fallible);
876
0
    if (!ok) {
877
0
      Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
878
0
      return;
879
0
    }
880
0
881
0
    // Yoric said on IRC that this method is normally called for the entire file,
882
0
    // but that's not guaranteed. Retaining the bug that EOF in conversion isn't
883
0
    // handled anywhere.
884
0
    uint32_t result;
885
0
    size_t read;
886
0
    size_t written;
887
0
    bool hadErrors;
888
0
    Tie(result, read, written, hadErrors) =
889
0
      mDecoder->DecodeToUTF16(src, resultString, false);
890
0
    MOZ_ASSERT(result == kInputEmpty);
891
0
    MOZ_ASSERT(read == src.Length());
892
0
    MOZ_ASSERT(written <= needed.value());
893
0
    Unused << hadErrors;
894
0
    ok = resultString.SetLength(written, fallible);
895
0
    if (!ok) {
896
0
      Fail(
897
0
        NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
898
0
      return;
899
0
    }
900
0
901
0
    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString);
902
0
    Succeed(mResult.forget());
903
0
  }
904
905
 private:
906
  nsCString mEncoding;
907
  mozilla::UniquePtr<mozilla::Decoder> mDecoder;
908
  RefPtr<StringResult> mResult;
909
};
910
911
/**
912
 * An event implenting writing atomically to a file.
913
 */
914
class DoWriteAtomicEvent: public AbstractDoEvent {
915
public:
916
  /**
917
   * @param aPath The path of the file.
918
   */
919
  DoWriteAtomicEvent(const nsAString& aPath,
920
                     UniquePtr<char> aBuffer,
921
                     const uint64_t aBytes,
922
                     const nsAString& aTmpPath,
923
                     const nsAString& aBackupTo,
924
                     const bool aFlush,
925
                     const bool aNoOverwrite,
926
                     nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
927
                     nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
928
    : AbstractDoEvent(aOnSuccess, aOnError)
929
    , mPath(aPath)
930
    , mBuffer(std::move(aBuffer))
931
    , mBytes(aBytes)
932
    , mTmpPath(aTmpPath)
933
    , mBackupTo(aBackupTo)
934
    , mFlush(aFlush)
935
    , mNoOverwrite(aNoOverwrite)
936
    , mResult(new Int32Result(TimeStamp::Now()))
937
0
  {
938
0
    MOZ_ASSERT(NS_IsMainThread());
939
0
  }
940
941
0
  ~DoWriteAtomicEvent() override {
942
0
    // If Run() has bailed out, we may need to cleanup
943
0
    // mResult, which is main-thread only data
944
0
    if (!mResult) {
945
0
      return;
946
0
    }
947
0
    NS_ReleaseOnMainThreadSystemGroup("DoWriteAtomicEvent::mResult",
948
0
                                      mResult.forget());
949
0
  }
950
951
0
  NS_IMETHODIMP Run() override {
952
0
    MOZ_ASSERT(!NS_IsMainThread());
953
0
    TimeStamp dispatchDate = TimeStamp::Now();
954
0
    int32_t bytesWritten;
955
0
956
0
    nsresult rv = WriteAtomic(&bytesWritten);
957
0
    if (NS_FAILED(rv)) {
958
0
      return NS_OK;
959
0
    }
960
0
961
0
    AfterWriteAtomic(dispatchDate, bytesWritten);
962
0
    return NS_OK;
963
0
  }
964
965
private:
966
  /**
967
   * Write atomically to a file.
968
   * Must be called off the main thread.
969
   * @param aBytesWritten will contain the total bytes written.
970
   * This does not support compression in this implementation.
971
   */
972
  nsresult WriteAtomic(int32_t* aBytesWritten)
973
0
  {
974
0
    MOZ_ASSERT(!NS_IsMainThread());
975
0
976
0
    // Note: In Windows, many NSPR File I/O functions which act on pathnames
977
0
    // do not handle UTF-16 encoding. Thus, we use the following functions
978
0
    // to overcome this.
979
0
    // PR_Access : GetFileAttributesW
980
0
    // PR_Delete : DeleteFileW
981
0
    // PR_OpenFile : CreateFileW followed by PR_ImportFile
982
0
    // PR_Rename : MoveFileW
983
0
984
0
    ScopedPRFileDesc file;
985
0
    NS_ConvertUTF16toUTF8 path(mPath);
986
0
    NS_ConvertUTF16toUTF8 tmpPath(mTmpPath);
987
0
    NS_ConvertUTF16toUTF8 backupTo(mBackupTo);
988
0
    bool fileExists = false;
989
0
990
0
    if (!mTmpPath.IsVoid() || !mBackupTo.IsVoid() || mNoOverwrite) {
991
0
      // fileExists needs to be computed in the case of tmpPath, since
992
0
      // the rename behaves differently depending on whether the
993
0
      // file already exists. It's also computed for backupTo since the
994
0
      // backup can be skipped if the file does not exist in the first place.
995
#if defined(XP_WIN)
996
      fileExists = ::GetFileAttributesW(mPath.get()) != INVALID_FILE_ATTRIBUTES;
997
#else
998
      fileExists = PR_Access(path.get(), PR_ACCESS_EXISTS) == PR_SUCCESS;
999
0
#endif // defined(XP_WIN)
1000
0
    }
1001
0
1002
0
    // Check noOverwrite.
1003
0
    if (mNoOverwrite && fileExists) {
1004
0
      Fail(NS_LITERAL_CSTRING("noOverwrite"), nullptr, OS_ERROR_FILE_EXISTS);
1005
0
      return NS_ERROR_FAILURE;
1006
0
    }
1007
0
1008
0
    // Backup the original file if it exists.
1009
0
    if (!mBackupTo.IsVoid() && fileExists) {
1010
#if defined(XP_WIN)
1011
      if (::GetFileAttributesW(mBackupTo.get()) != INVALID_FILE_ATTRIBUTES) {
1012
        // The file specified by mBackupTo exists, so we need to delete it first.
1013
        if (::DeleteFileW(mBackupTo.get()) == false) {
1014
          Fail(NS_LITERAL_CSTRING("delete"), nullptr, ::GetLastError());
1015
          return NS_ERROR_FAILURE;
1016
        }
1017
      }
1018
1019
      if (::MoveFileW(mPath.get(), mBackupTo.get()) == false) {
1020
        Fail(NS_LITERAL_CSTRING("rename"), nullptr, ::GetLastError());
1021
        return NS_ERROR_FAILURE;
1022
      }
1023
#else
1024
0
      if (PR_Access(backupTo.get(), PR_ACCESS_EXISTS) == PR_SUCCESS) {
1025
0
        // The file specified by mBackupTo exists, so we need to delete it first.
1026
0
        if (PR_Delete(backupTo.get()) == PR_FAILURE) {
1027
0
          Fail(NS_LITERAL_CSTRING("delete"), nullptr, PR_GetOSError());
1028
0
          return NS_ERROR_FAILURE;
1029
0
         }
1030
0
       }
1031
0
1032
0
       if (PR_Rename(path.get(), backupTo.get()) == PR_FAILURE) {
1033
0
        Fail(NS_LITERAL_CSTRING("rename"), nullptr, PR_GetOSError());
1034
0
        return NS_ERROR_FAILURE;
1035
0
      }
1036
0
#endif // defined(XP_WIN)
1037
0
    }
1038
0
1039
#if defined(XP_WIN)
1040
    // In addition to not handling UTF-16 encoding in file paths,
1041
    // PR_OpenFile opens files without sharing, which is not the
1042
    // general semantics of OS.File.
1043
    HANDLE handle;
1044
    // if we're dealing with a tmpFile, we need to write there.
1045
    if (!mTmpPath.IsVoid()) {
1046
      handle =
1047
        ::CreateFileW(mTmpPath.get(),
1048
                      GENERIC_WRITE,
1049
                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1050
                      /*Security attributes*/nullptr,
1051
                      // CREATE_ALWAYS is used since since we need to create the temporary file,
1052
                      // which we don't care about overwriting.
1053
                      CREATE_ALWAYS,
1054
                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
1055
                      /*Template file*/ nullptr);
1056
    } else {
1057
      handle =
1058
        ::CreateFileW(mPath.get(),
1059
                      GENERIC_WRITE,
1060
                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1061
                      /*Security attributes*/nullptr,
1062
                      // CREATE_ALWAYS is used since since have already checked the noOverwrite
1063
                      // condition, and thus can overwrite safely.
1064
                      CREATE_ALWAYS,
1065
                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
1066
                      /*Template file*/ nullptr);
1067
    }
1068
1069
    if (handle == INVALID_HANDLE_VALUE) {
1070
      Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
1071
      return NS_ERROR_FAILURE;
1072
    }
1073
1074
    file = PR_ImportFile((PROsfd)handle);
1075
    if (!file) {
1076
      // |file| is closed by PR_ImportFile
1077
      Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
1078
      return NS_ERROR_FAILURE;
1079
    }
1080
1081
#else
1082
    // if we're dealing with a tmpFile, we need to write there.
1083
0
    if (!mTmpPath.IsVoid()) {
1084
0
      file = PR_OpenFile(tmpPath.get(),
1085
0
                         PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1086
0
                         PR_IRUSR | PR_IWUSR);
1087
0
    } else {
1088
0
      file = PR_OpenFile(path.get(),
1089
0
                         PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1090
0
                         PR_IRUSR | PR_IWUSR);
1091
0
    }
1092
0
1093
0
    if (!file) {
1094
0
      Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
1095
0
      return NS_ERROR_FAILURE;
1096
0
    }
1097
0
#endif // defined(XP_WIN)
1098
0
1099
0
    int32_t bytesWrittenSuccess = PR_Write(file, (void* )(mBuffer.get()), mBytes);
1100
0
1101
0
    if (bytesWrittenSuccess == -1) {
1102
0
      Fail(NS_LITERAL_CSTRING("write"), nullptr, PR_GetOSError());
1103
0
      return NS_ERROR_FAILURE;
1104
0
    }
1105
0
1106
0
    // Apply any tmpPath renames.
1107
0
    if (!mTmpPath.IsVoid()) {
1108
0
      if (mBackupTo.IsVoid() && fileExists) {
1109
0
        // We need to delete the old file first, if it exists and we haven't already
1110
0
        // renamed it as a part of backing it up.
1111
#if defined(XP_WIN)
1112
        if (::DeleteFileW(mPath.get()) == false) {
1113
          Fail(NS_LITERAL_CSTRING("delete"), nullptr, ::GetLastError());
1114
          return NS_ERROR_FAILURE;
1115
        }
1116
#else
1117
0
        if (PR_Delete(path.get()) == PR_FAILURE) {
1118
0
          Fail(NS_LITERAL_CSTRING("delete"), nullptr, PR_GetOSError());
1119
0
          return NS_ERROR_FAILURE;
1120
0
        }
1121
0
#endif // defined(XP_WIN)
1122
0
      }
1123
0
1124
#if defined(XP_WIN)
1125
      if (::MoveFileW(mTmpPath.get(), mPath.get()) == false) {
1126
        Fail(NS_LITERAL_CSTRING("rename"), nullptr, ::GetLastError());
1127
        return NS_ERROR_FAILURE;
1128
      }
1129
#else
1130
0
      if(PR_Rename(tmpPath.get(), path.get()) == PR_FAILURE) {
1131
0
        Fail(NS_LITERAL_CSTRING("rename"), nullptr, PR_GetOSError());
1132
0
        return NS_ERROR_FAILURE;
1133
0
      }
1134
0
#endif // defined(XP_WIN)
1135
0
    }
1136
0
1137
0
    if (mFlush) {
1138
0
      if (PR_Sync(file) == PR_FAILURE) {
1139
0
        Fail(NS_LITERAL_CSTRING("sync"), nullptr, PR_GetOSError());
1140
0
        return NS_ERROR_FAILURE;
1141
0
      }
1142
0
    }
1143
0
1144
0
    *aBytesWritten = bytesWrittenSuccess;
1145
0
    return NS_OK;
1146
0
  }
1147
1148
protected:
1149
0
  nsresult AfterWriteAtomic(TimeStamp aDispatchDate, int32_t aBytesWritten) {
1150
0
    MOZ_ASSERT(!NS_IsMainThread());
1151
0
    mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBytesWritten);
1152
0
    Succeed(mResult.forget());
1153
0
    return NS_OK;
1154
0
  }
1155
1156
  const nsString mPath;
1157
  const UniquePtr<char> mBuffer;
1158
  const int32_t mBytes;
1159
  const nsString mTmpPath;
1160
  const nsString mBackupTo;
1161
  const bool mFlush;
1162
  const bool mNoOverwrite;
1163
1164
private:
1165
  RefPtr<Int32Result> mResult;
1166
};
1167
1168
} // namespace
1169
1170
// The OS.File service
1171
1172
NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
1173
1174
NS_IMETHODIMP
1175
NativeOSFileInternalsService::Read(const nsAString& aPath,
1176
                                   JS::HandleValue aOptions,
1177
                                   nsINativeOSFileSuccessCallback *aOnSuccess,
1178
                                   nsINativeOSFileErrorCallback *aOnError,
1179
                                   JSContext* cx)
1180
0
{
1181
0
  // Extract options
1182
0
  nsCString encoding;
1183
0
  uint64_t bytes = UINT64_MAX;
1184
0
1185
0
  if (aOptions.isObject()) {
1186
0
    dom::NativeOSFileReadOptions dict;
1187
0
    if (!dict.Init(cx, aOptions)) {
1188
0
      return NS_ERROR_INVALID_ARG;
1189
0
    }
1190
0
1191
0
    if (dict.mEncoding.WasPassed()) {
1192
0
      CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
1193
0
    }
1194
0
1195
0
    if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
1196
0
      bytes = dict.mBytes.Value().Value();
1197
0
    }
1198
0
  }
1199
0
1200
0
  // Prepare the off main thread event and dispatch it
1201
0
  nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
1202
0
  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
1203
0
    new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
1204
0
      "nsINativeOSFileSuccessCallback", onSuccess));
1205
0
  nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
1206
0
  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
1207
0
    new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
1208
0
      "nsINativeOSFileErrorCallback", onError));
1209
0
1210
0
  RefPtr<AbstractDoEvent> event;
1211
0
  if (encoding.IsEmpty()) {
1212
0
    event = new DoReadToTypedArrayEvent(aPath, bytes,
1213
0
                                        onSuccessHandle,
1214
0
                                        onErrorHandle);
1215
0
  } else {
1216
0
    event = new DoReadToStringEvent(aPath, encoding, bytes,
1217
0
                                    onSuccessHandle,
1218
0
                                    onErrorHandle);
1219
0
  }
1220
0
1221
0
  nsresult rv;
1222
0
  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
1223
0
1224
0
  if (NS_FAILED(rv)) {
1225
0
    return rv;
1226
0
  }
1227
0
  return target->Dispatch(event, NS_DISPATCH_NORMAL);
1228
0
}
1229
1230
// Note: This method steals the contents of `aBuffer`.
1231
NS_IMETHODIMP
1232
NativeOSFileInternalsService::WriteAtomic(const nsAString& aPath,
1233
                                          JS::HandleValue aBuffer,
1234
                                          JS::HandleValue aOptions,
1235
                                          nsINativeOSFileSuccessCallback *aOnSuccess,
1236
                                          nsINativeOSFileErrorCallback *aOnError,
1237
                                          JSContext* cx)
1238
0
{
1239
0
  MOZ_ASSERT(NS_IsMainThread());
1240
0
  // Extract typed-array/string into buffer. We also need to store the length
1241
0
  // of the buffer as that may be required if not provided in `aOptions`.
1242
0
  UniquePtr<char> buffer;
1243
0
  int32_t bytes;
1244
0
1245
0
  // The incoming buffer must be an Object.
1246
0
  if (!aBuffer.isObject()) {
1247
0
    return NS_ERROR_INVALID_ARG;
1248
0
  }
1249
0
1250
0
  JS::RootedObject bufferObject(cx, nullptr);
1251
0
  if (!JS_ValueToObject(cx, aBuffer, &bufferObject)) {
1252
0
    return NS_ERROR_FAILURE;
1253
0
  }
1254
0
  if (!JS_IsArrayBufferObject(bufferObject.get())) {
1255
0
    return NS_ERROR_INVALID_ARG;
1256
0
  }
1257
0
1258
0
  bytes = JS_GetArrayBufferByteLength(bufferObject.get());
1259
0
  buffer.reset(static_cast<char*>(
1260
0
                 JS_StealArrayBufferContents(cx, bufferObject)));
1261
0
1262
0
  if (!buffer) {
1263
0
    return NS_ERROR_FAILURE;
1264
0
  }
1265
0
1266
0
  // Extract options.
1267
0
  dom::NativeOSFileWriteAtomicOptions dict;
1268
0
1269
0
  if (aOptions.isObject()) {
1270
0
    if (!dict.Init(cx, aOptions)) {
1271
0
      return NS_ERROR_INVALID_ARG;
1272
0
    }
1273
0
  } else {
1274
0
    // If an options object is not provided, initializing with a `null`
1275
0
    // value, which will give a set of defaults defined in the WebIDL binding.
1276
0
    if (!dict.Init(cx, JS::NullHandleValue)) {
1277
0
      return NS_ERROR_FAILURE;
1278
0
    }
1279
0
  }
1280
0
1281
0
  if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
1282
0
    // We need to check size and cast because NSPR and WebIDL have different types.
1283
0
    if (dict.mBytes.Value().Value() > PR_INT32_MAX) {
1284
0
      return NS_ERROR_INVALID_ARG;
1285
0
    }
1286
0
    bytes = (int32_t) (dict.mBytes.Value().Value());
1287
0
  }
1288
0
1289
0
  // Prepare the off main thread event and dispatch it
1290
0
  nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
1291
0
  nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
1292
0
    new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(
1293
0
      "nsINativeOSFileSuccessCallback", onSuccess));
1294
0
  nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
1295
0
  nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
1296
0
    new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(
1297
0
      "nsINativeOSFileErrorCallback", onError));
1298
0
1299
0
  RefPtr<AbstractDoEvent> event = new DoWriteAtomicEvent(aPath,
1300
0
                                                         std::move(buffer),
1301
0
                                                         bytes,
1302
0
                                                         dict.mTmpPath,
1303
0
                                                         dict.mBackupTo,
1304
0
                                                         dict.mFlush,
1305
0
                                                         dict.mNoOverwrite,
1306
0
                                                         onSuccessHandle,
1307
0
                                                         onErrorHandle);
1308
0
  nsresult rv;
1309
0
  nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
1310
0
1311
0
  if (NS_FAILED(rv)) {
1312
0
    return rv;
1313
0
  }
1314
0
1315
0
  return target->Dispatch(event, NS_DISPATCH_NORMAL);
1316
0
}
1317
1318
} // namespace mozilla