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