Source code

Revision control

Other Tools

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