toolkit/components/osfile/NativeOSFileInternals.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:5d614c3230f3
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 "nsString.h"
10 #include "nsNetCID.h"
11 #include "nsThreadUtils.h"
12 #include "nsXPCOMCID.h"
13 #include "nsCycleCollectionParticipant.h"
14 #include "nsServiceManagerUtils.h"
15 #include "nsProxyRelease.h"
16
17 #include "nsINativeOSFileInternals.h"
18 #include "NativeOSFileInternals.h"
19 #include "mozilla/dom/NativeOSFileInternalsBinding.h"
20
21 #include "nsIUnicodeDecoder.h"
22 #include "nsIEventTarget.h"
23
24 #include "mozilla/dom/EncodingUtils.h"
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 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 const static type empty() {
84 type result = {0, 0};
85 return result;
86 }
87 const static void release(type ptr) {
88 js_free(ptr.data);
89 ptr.data = nullptr;
90 ptr.nbytes = 0;
91 }
92 };
93
94 struct ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> {
95 ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM):
96 Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
97 { }
98 ScopedArrayBufferContents(const ArrayBufferContents& v
99 MOZ_GUARD_OBJECT_NOTIFIER_PARAM):
100 Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
101 { }
102 ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
103 Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
104 return *this;
105 }
106
107 /**
108 * Request memory for this ArrayBufferContent. This memory may later
109 * be used to create an ArrayBuffer object (possibly on another
110 * thread) without copy.
111 *
112 * @return true In case of success, false otherwise.
113 */
114 bool Allocate(uint32_t length) {
115 dispose();
116 ArrayBufferContents& value = rwget();
117 void *ptr = JS_AllocateArrayBufferContents(/*no context available*/nullptr, length);
118 if (ptr) {
119 value.data = (uint8_t *) ptr;
120 value.nbytes = length;
121 return true;
122 }
123 return false;
124 }
125 private:
126 explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE;
127 ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE;
128 };
129
130 ///////// Cross-platform issues
131
132 // Platform specific constants. As OS.File always uses OS-level
133 // errors, we need to map a few high-level errors to OS-level
134 // constants.
135 #if defined(XP_UNIX)
136 #define OS_ERROR_NOMEM ENOMEM
137 #define OS_ERROR_INVAL EINVAL
138 #define OS_ERROR_TOO_LARGE EFBIG
139 #define OS_ERROR_RACE EIO
140 #elif defined(XP_WIN)
141 #define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
142 #define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
143 #define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
144 #define OS_ERROR_RACE ERROR_SHARING_VIOLATION
145 #else
146 #error "We do not have platform-specific constants for this platform"
147 #endif
148
149 ///////// Results of OS.File operations
150
151 /**
152 * Base class for results passed to the callbacks.
153 *
154 * This base class implements caching of JS values returned to the client.
155 * We make use of this caching in derived classes e.g. to avoid accidents
156 * when we transfer data allocated on another thread into JS. Note that
157 * this caching can lead to cycles (e.g. if a client adds a back-reference
158 * in the JS value), so we implement all Cycle Collector primitives in
159 * AbstractResult.
160 */
161 class AbstractResult: public nsINativeOSFileResult {
162 public:
163 NS_DECL_NSINATIVEOSFILERESULT
164 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
165 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
166
167 /**
168 * Construct the result object. Must be called on the main thread
169 * as the AbstractResult is cycle-collected.
170 *
171 * @param aStartDate The instant at which the operation was
172 * requested. Used to collect Telemetry statistics.
173 */
174 AbstractResult(TimeStamp aStartDate)
175 : mStartDate(aStartDate)
176 {
177 MOZ_ASSERT(NS_IsMainThread());
178 mozilla::HoldJSObjects(this);
179 }
180 virtual ~AbstractResult() {
181 MOZ_ASSERT(NS_IsMainThread());
182 DropJSData();
183 mozilla::DropJSObjects(this);
184 }
185
186 /**
187 * Setup the AbstractResult once data is available.
188 *
189 * @param aDispatchDate The instant at which the IO thread received
190 * the operation request. Used to collect Telemetry statistics.
191 * @param aExecutionDuration The duration of the operation on the
192 * IO thread.
193 */
194 void Init(TimeStamp aDispatchDate,
195 TimeDuration aExecutionDuration) {
196 MOZ_ASSERT(!NS_IsMainThread());
197
198 mDispatchDuration = (aDispatchDate - mStartDate);
199 mExecutionDuration = aExecutionDuration;
200 }
201
202 /**
203 * Drop any data that could lead to a cycle.
204 */
205 void DropJSData() {
206 mCachedResult = JS::UndefinedValue();
207 }
208
209 protected:
210 virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0;
211
212 private:
213 TimeStamp mStartDate;
214 TimeDuration mDispatchDuration;
215 TimeDuration mExecutionDuration;
216 JS::Heap<JS::Value> mCachedResult;
217 };
218
219 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
220 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
221
222 NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
223
224 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
225 NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
226 NS_INTERFACE_MAP_ENTRY(nsISupports)
227 NS_INTERFACE_MAP_END
228
229 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
230 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult)
231 NS_IMPL_CYCLE_COLLECTION_TRACE_END
232
233 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
234 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
235 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
236
237 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
238 tmp->DropJSData();
239 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
240
241 NS_IMETHODIMP
242 AbstractResult::GetDispatchDurationMS(double *aDispatchDuration)
243 {
244 *aDispatchDuration = mDispatchDuration.ToMilliseconds();
245 return NS_OK;
246 }
247
248 NS_IMETHODIMP
249 AbstractResult::GetExecutionDurationMS(double *aExecutionDuration)
250 {
251 *aExecutionDuration = mExecutionDuration.ToMilliseconds();
252 return NS_OK;
253 }
254
255 NS_IMETHODIMP
256 AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult)
257 {
258 if (mCachedResult.isUndefined()) {
259 nsresult rv = GetCacheableResult(cx, aResult);
260 if (NS_FAILED(rv)) {
261 return rv;
262 }
263 mCachedResult = aResult;
264 return NS_OK;
265 }
266 aResult.set(mCachedResult);
267 return NS_OK;
268 }
269
270 /**
271 * Return a result as a string.
272 *
273 * In this implementation, attribute |result| is a string. Strings are
274 * passed to JS without copy.
275 */
276 class StringResult MOZ_FINAL : public AbstractResult
277 {
278 public:
279 StringResult(TimeStamp aStartDate)
280 : AbstractResult(aStartDate)
281 {
282 }
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,
292 TimeDuration aExecutionDuration,
293 nsString& aContents) {
294 AbstractResult::Init(aDispatchDate, aExecutionDuration);
295 mContents = aContents;
296 }
297
298 protected:
299 nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE;
300
301 private:
302 nsString mContents;
303 };
304
305 nsresult
306 StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
307 {
308 MOZ_ASSERT(NS_IsMainThread());
309 MOZ_ASSERT(mContents.get());
310
311 // Convert mContents to a js string without copy. Note that this
312 // may have the side-effect of stealing the contents of the string
313 // from XPCOM and into JS.
314 if (!xpc::StringToJsval(cx, mContents, aResult)) {
315 return NS_ERROR_FAILURE;
316 }
317 return NS_OK;
318 }
319
320
321 /**
322 * Return a result as a Uint8Array.
323 *
324 * In this implementation, attribute |result| is a Uint8Array. The array
325 * is passed to JS without memory copy.
326 */
327 class TypedArrayResult MOZ_FINAL : public AbstractResult
328 {
329 public:
330 TypedArrayResult(TimeStamp aStartDate)
331 : AbstractResult(aStartDate)
332 {
333 }
334
335 /**
336 * @param aContents The contents to pass to JS. Calling this method.
337 * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
338 * Do not reuse this value anywhere else.
339 */
340 void Init(TimeStamp aDispatchDate,
341 TimeDuration aExecutionDuration,
342 ArrayBufferContents aContents) {
343 AbstractResult::Init(aDispatchDate, aExecutionDuration);
344 mContents = aContents;
345 }
346
347 protected:
348 nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE;
349 private:
350 ScopedArrayBufferContents mContents;
351 };
352
353 nsresult
354 TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
355 {
356 MOZ_ASSERT(NS_IsMainThread());
357 // We cannot simply construct a typed array using contents.data as
358 // this would allow us to have several otherwise unrelated
359 // ArrayBuffers with the same underlying C buffer. As this would be
360 // very unsafe, we need to cache the result once we have it.
361
362 const ArrayBufferContents& contents = mContents.get();
363 MOZ_ASSERT(contents.data);
364
365 JS::Rooted<JSObject*>
366 arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
367 if (!arrayBuffer) {
368 return NS_ERROR_OUT_OF_MEMORY;
369 }
370
371 JS::Rooted<JSObject*>
372 result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer,
373 0, contents.nbytes));
374 if (!result) {
375 return NS_ERROR_OUT_OF_MEMORY;
376 }
377 // The memory of contents has been allocated on a thread that
378 // doesn't have a JSRuntime, hence without a context. Now that we
379 // have a context, attach the memory to where it belongs.
380 JS_updateMallocCounter(cx, contents.nbytes);
381 mContents.forget();
382
383 aResult.setObject(*result);
384 return NS_OK;
385 }
386
387 //////// Callback events
388
389 /**
390 * An event used to notify asynchronously of an error.
391 */
392 class ErrorEvent MOZ_FINAL : public nsRunnable {
393 public:
394 /**
395 * @param aOnSuccess The success callback.
396 * @param aOnError The error callback.
397 * @param aDiscardedResult The discarded result.
398 * @param aOperation The name of the operation, used for error reporting.
399 * @param aOSError The OS error of the operation, as returned by errno/
400 * GetLastError().
401 *
402 * Note that we pass both the success callback and the error
403 * callback, as well as the discarded result to ensure that they are
404 * all released on the main thread, rather than on the IO thread
405 * (which would hopefully segfault). Also, we pass the callbacks as
406 * alread_AddRefed to ensure that we do not manipulate main-thread
407 * only refcounters off the main thread.
408 */
409 ErrorEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess,
410 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError,
411 already_AddRefed<AbstractResult>& aDiscardedResult,
412 const nsACString& aOperation,
413 int32_t aOSError)
414 : mOnSuccess(aOnSuccess)
415 , mOnError(aOnError)
416 , mDiscardedResult(aDiscardedResult)
417 , mOSError(aOSError)
418 , mOperation(aOperation)
419 {
420 MOZ_ASSERT(!NS_IsMainThread());
421 }
422
423 NS_METHOD Run() {
424 MOZ_ASSERT(NS_IsMainThread());
425 (void)mOnError->Complete(mOperation, mOSError);
426
427 // Ensure that the callbacks are released on the main thread.
428 mOnSuccess = nullptr;
429 mOnError = nullptr;
430 mDiscardedResult = nullptr;
431
432 return NS_OK;
433 }
434 private:
435 // The callbacks. Maintained as nsRefPtr as they are generally
436 // xpconnect values, which cannot be manipulated with nsCOMPtr off
437 // the main thread. We store both the success callback and the
438 // error callback to ensure that they are safely released on the
439 // main thread.
440 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
441 nsRefPtr<nsINativeOSFileErrorCallback> mOnError;
442 nsRefPtr<AbstractResult> mDiscardedResult;
443 int32_t mOSError;
444 nsCString mOperation;
445 };
446
447 /**
448 * An event used to notify of a success.
449 */
450 class SuccessEvent MOZ_FINAL : public nsRunnable {
451 public:
452 /**
453 * @param aOnSuccess The success callback.
454 * @param aOnError The error callback.
455 *
456 * Note that we pass both the success callback and the error
457 * callback to ensure that they are both released on the main
458 * thread, rather than on the IO thread (which would hopefully
459 * segfault). Also, we pass them as alread_AddRefed to ensure that
460 * we do not manipulate xpconnect refcounters off the main thread
461 * (which is illegal).
462 */
463 SuccessEvent(already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess,
464 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError,
465 already_AddRefed<nsINativeOSFileResult>& aResult)
466 : mOnSuccess(aOnSuccess)
467 , mOnError(aOnError)
468 , mResult(aResult)
469 {
470 MOZ_ASSERT(!NS_IsMainThread());
471 }
472
473 NS_METHOD Run() {
474 MOZ_ASSERT(NS_IsMainThread());
475 (void)mOnSuccess->Complete(mResult);
476
477 // Ensure that the callbacks are released on the main thread.
478 mOnSuccess = nullptr;
479 mOnError = nullptr;
480 mResult = nullptr;
481
482 return NS_OK;
483 }
484 private:
485 // The callbacks. Maintained as nsRefPtr 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 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
491 nsRefPtr<nsINativeOSFileErrorCallback> mOnError;
492 nsRefPtr<nsINativeOSFileResult> mResult;
493 };
494
495
496 //////// Action events
497
498 /**
499 * Base class shared by actions.
500 */
501 class AbstractDoEvent: public nsRunnable {
502 public:
503 AbstractDoEvent(already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess,
504 already_AddRefed<nsINativeOSFileErrorCallback>& aOnError)
505 : mOnSuccess(aOnSuccess)
506 , mOnError(aOnError)
507 #if defined(DEBUG)
508 , mResolved(false)
509 #endif // defined(DEBUG)
510 {
511 MOZ_ASSERT(NS_IsMainThread());
512 }
513
514 /**
515 * Fail, asynchronously.
516 */
517 void Fail(const nsACString& aOperation,
518 already_AddRefed<AbstractResult>&& aDiscardedResult,
519 int32_t aOSError = 0) {
520 Resolve();
521 nsRefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess.forget(),
522 mOnError.forget(),
523 aDiscardedResult,
524 aOperation,
525 aOSError);
526 nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
527 if (NS_FAILED(rv)) {
528 // Last ditch attempt to release on the main thread - some of
529 // the members of event are not thread-safe, so letting the
530 // pointer go out of scope would cause a crash.
531 nsCOMPtr<nsIThread> main = do_GetMainThread();
532 NS_ProxyRelease(main, event);
533 }
534 }
535
536 /**
537 * Succeed, asynchronously.
538 */
539 void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
540 Resolve();
541 nsRefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess.forget(),
542 mOnError.forget(),
543 aResult);
544 nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
545 if (NS_FAILED(rv)) {
546 // Last ditch attempt to release on the main thread - some of
547 // the members of event are not thread-safe, so letting the
548 // pointer go out of scope would cause a crash.
549 nsCOMPtr<nsIThread> main = do_GetMainThread();
550 NS_ProxyRelease(main, event);
551 }
552
553 }
554
555 private:
556
557 /**
558 * Mark the event as complete, for debugging purposes.
559 */
560 void Resolve() {
561 #if defined(DEBUG)
562 MOZ_ASSERT(!mResolved);
563 mResolved = true;
564 #endif // defined(DEBUG)
565 }
566
567 private:
568 nsRefPtr<nsINativeOSFileSuccessCallback> mOnSuccess;
569 nsRefPtr<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 class AbstractReadEvent: public AbstractDoEvent {
583 public:
584 /**
585 * @param aPath The path of the file.
586 */
587 AbstractReadEvent(const nsAString& aPath,
588 const uint64_t aBytes,
589 already_AddRefed<nsINativeOSFileSuccessCallback>& aOnSuccess,
590 already_AddRefed<nsINativeOSFileErrorCallback>& aOnError)
591 : AbstractDoEvent(aOnSuccess, aOnError)
592 , mPath(aPath)
593 , mBytes(aBytes)
594 {
595 MOZ_ASSERT(NS_IsMainThread());
596 }
597
598 NS_METHOD Run() MOZ_OVERRIDE {
599 MOZ_ASSERT(!NS_IsMainThread());
600 TimeStamp dispatchDate = TimeStamp::Now();
601
602 nsresult rv = BeforeRead();
603 if (NS_FAILED(rv)) {
604 // Error reporting is handled by BeforeRead();
605 return NS_OK;
606 }
607
608 ScopedArrayBufferContents buffer;
609 rv = Read(buffer);
610 if (NS_FAILED(rv)) {
611 // Error reporting is handled by Read();
612 return NS_OK;
613 }
614
615 AfterRead(dispatchDate, buffer);
616 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 nsresult Read(ScopedArrayBufferContents& aBuffer)
628 {
629 MOZ_ASSERT(!NS_IsMainThread());
630
631 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 NS_ConvertUTF16toUTF8 path(mPath);
661 file = PR_OpenFile(path.get(), PR_RDONLY, 0);
662 if (!file) {
663 Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
664 return NS_ERROR_FAILURE;
665 }
666
667 #endif // defined(XP_XIN)
668
669 PRFileInfo64 stat;
670 if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
671 Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError());
672 return NS_ERROR_FAILURE;
673 }
674
675 uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
676 if (bytes > UINT32_MAX) {
677 Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL);
678 return NS_ERROR_FAILURE;
679 }
680
681 if (!aBuffer.Allocate(bytes)) {
682 Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM);
683 return NS_ERROR_FAILURE;
684 }
685
686 uint64_t total_read = 0;
687 int32_t just_read = 0;
688 char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
689 do {
690 just_read = PR_Read(file, dest_chars + total_read,
691 std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
692 if (just_read == -1) {
693 Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError());
694 return NS_ERROR_FAILURE;
695 }
696 total_read += just_read;
697 } while (just_read != 0 && total_read < bytes);
698 if (total_read != bytes) {
699 // We seem to have a race condition here.
700 Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE);
701 return NS_ERROR_FAILURE;
702 }
703
704 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 nsresult BeforeRead() {
716 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 MOZ_FINAL : public AbstractReadEvent {
735 public:
736 DoReadToTypedArrayEvent(const nsAString& aPath,
737 const uint32_t aBytes,
738 already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess,
739 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError)
740 : AbstractReadEvent(aPath, aBytes,
741 aOnSuccess, aOnError)
742 , mResult(new TypedArrayResult(TimeStamp::Now()))
743 { }
744
745 ~DoReadToTypedArrayEvent() {
746 // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
747 // mResult, which is main-thread only data
748 if (!mResult) {
749 return;
750 }
751 nsCOMPtr<nsIThread> main = do_GetMainThread();
752 (void)NS_ProxyRelease(main, mResult);
753 }
754
755 protected:
756 void AfterRead(TimeStamp aDispatchDate,
757 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE {
758 MOZ_ASSERT(!NS_IsMainThread());
759 mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget());
760 Succeed(mResult.forget());
761 }
762
763 private:
764 nsRefPtr<TypedArrayResult> mResult;
765 };
766
767 /**
768 * An implementation of a Read event that provides the data
769 * as a JavaScript string.
770 */
771 class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent {
772 public:
773 DoReadToStringEvent(const nsAString& aPath,
774 const nsACString& aEncoding,
775 const uint32_t aBytes,
776 already_AddRefed<nsINativeOSFileSuccessCallback>&& aOnSuccess,
777 already_AddRefed<nsINativeOSFileErrorCallback>&& aOnError)
778 : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError)
779 , mEncoding(aEncoding)
780 , mResult(new StringResult(TimeStamp::Now()))
781 { }
782
783 ~DoReadToStringEvent() {
784 // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
785 // mResult, which is main-thread only data
786 if (!mResult) {
787 return;
788 }
789 nsCOMPtr<nsIThread> main = do_GetMainThread();
790 (void)NS_ProxyRelease(main, mResult);
791 }
792
793 protected:
794 nsresult BeforeRead() MOZ_OVERRIDE {
795 // Obtain the decoder. We do this before reading to avoid doing
796 // any unnecessary I/O in case the name of the encoding is incorrect.
797 MOZ_ASSERT(!NS_IsMainThread());
798 nsAutoCString encodingName;
799 if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) {
800 Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL);
801 return NS_ERROR_FAILURE;
802 }
803 mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName);
804 if (!mDecoder) {
805 Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL);
806 return NS_ERROR_FAILURE;
807 }
808
809 return NS_OK;
810 }
811
812 void AfterRead(TimeStamp aDispatchDate,
813 ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE {
814 MOZ_ASSERT(!NS_IsMainThread());
815
816 int32_t maxChars;
817 const char* sourceChars = reinterpret_cast<const char*>(aBuffer.get().data);
818 int32_t sourceBytes = aBuffer.get().nbytes;
819 if (sourceBytes < 0) {
820 Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
821 return;
822 }
823
824 nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars);
825 if (NS_FAILED(rv)) {
826 Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL);
827 return;
828 }
829
830 if (maxChars < 0) {
831 Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
832 return;
833 }
834
835 nsString resultString;
836 resultString.SetLength(maxChars);
837 if (resultString.Length() != (nsString::size_type)maxChars) {
838 Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
839 return;
840 }
841
842
843 rv = mDecoder->Convert(sourceChars, &sourceBytes,
844 resultString.BeginWriting(), &maxChars);
845 MOZ_ASSERT(NS_SUCCEEDED(rv));
846 resultString.SetLength(maxChars);
847
848 mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString);
849 Succeed(mResult.forget());
850 }
851
852 private:
853 nsCString mEncoding;
854 nsCOMPtr<nsIUnicodeDecoder> mDecoder;
855 nsRefPtr<StringResult> mResult;
856 };
857
858 } // osfile
859
860 // The OS.File service
861
862 NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
863
864 NS_IMETHODIMP
865 NativeOSFileInternalsService::Read(const nsAString& aPath,
866 JS::HandleValue aOptions,
867 nsINativeOSFileSuccessCallback *aOnSuccess,
868 nsINativeOSFileErrorCallback *aOnError,
869 JSContext* cx)
870 {
871 // Extract options
872 nsCString encoding;
873 uint64_t bytes = UINT64_MAX;
874
875 if (aOptions.isObject()) {
876 dom::NativeOSFileReadOptions dict;
877 if (!dict.Init(cx, aOptions)) {
878 return NS_ERROR_INVALID_ARG;
879 }
880
881 if (dict.mEncoding.WasPassed()) {
882 CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
883 }
884
885 if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
886 bytes = dict.mBytes.Value().Value();
887 }
888 }
889
890 // Prepare the off main thread event and dispatch it
891 nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
892 nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
893
894 nsRefPtr<AbstractDoEvent> event;
895 if (encoding.IsEmpty()) {
896 event = new DoReadToTypedArrayEvent(aPath, bytes,
897 onSuccess.forget(),
898 onError.forget());
899 } else {
900 event = new DoReadToStringEvent(aPath, encoding, bytes,
901 onSuccess.forget(),
902 onError.forget());
903 }
904
905 nsresult rv;
906 nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
907
908 if (NS_FAILED(rv)) {
909 return rv;
910 }
911 return target->Dispatch(event, NS_DISPATCH_NORMAL);
912 }
913
914 } // namespace mozilla
915

mercurial