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
|