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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 #include "ErrorList.h"
12 #include "TypedArray.h"
13 #include "js/ArrayBuffer.h"
14 #include "js/ColumnNumber.h" // JS::ColumnNumberZeroOrigin
16 #include "js/Utility.h"
17 #include "js/experimental/TypedData.h"
18 #include "jsfriendapi.h"
19 #include "mozilla/Assertions.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/CheckedInt.h"
22 #include "mozilla/Compression.h"
23 #include "mozilla/Encoding.h"
24 #include "mozilla/EndianUtils.h"
25 #include "mozilla/ErrorNames.h"
26 #include "mozilla/FileUtils.h"
27 #include "mozilla/Maybe.h"
28 #include "mozilla/ResultExtensions.h"
29 #include "mozilla/Services.h"
30 #include "mozilla/Span.h"
31 #include "mozilla/StaticPtr.h"
32 #include "mozilla/TextUtils.h"
33 #include "mozilla/Try.h"
34 #include "mozilla/Unused.h"
35 #include "mozilla/Utf8.h"
36 #include "mozilla/dom/BindingUtils.h"
37 #include "mozilla/dom/IOUtilsBinding.h"
38 #include "mozilla/dom/Promise.h"
39 #include "mozilla/dom/WorkerCommon.h"
40 #include "mozilla/dom/WorkerRef.h"
41 #include "mozilla/ipc/LaunchError.h"
42 #include "PathUtils.h"
45 #include "nsFileStreams.h"
46 #include "nsIDirectoryEnumerator.h"
48 #include "nsIGlobalObject.h"
49 #include "nsIInputStream.h"
50 #include "nsISupports.h"
51 #include "nsLocalFile.h"
52 #include "nsNetUtil.h"
53 #include "nsNSSComponent.h"
54 #include "nsPrintfCString.h"
55 #include "nsReadableUtils.h"
57 #include "nsStringFwd.h"
59 #include "nsThreadManager.h"
60 #include "nsXULAppAPI.h"
65 #include "ScopedNSSTypes.h"
68 #if defined(XP_UNIX) && !defined(ANDROID)
69 # include "nsSystemInfo.h"
73 # include "nsILocalFileWin.h"
74 #elif defined(XP_MACOSX)
75 # include "nsILocalFileMac.h"
79 # include "base/process_util.h"
82 #define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise) \
84 if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
86 (_promise)->MaybeRejectWithOperationError( \
87 FormatErrorMessage(_rv, "Could not parse path (%s)", \
88 NS_ConvertUTF16toUTF8(_path).get())); \
93 static constexpr auto SHUTDOWN_ERROR
=
94 "IOUtils: Shutting down and refusing additional I/O tasks"_ns
;
96 namespace mozilla::dom
{
98 // static helper functions
101 * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
102 * report I/O errors inconsistently. For convenience, this function will attempt
103 * to match a |nsresult| against known results which imply a file cannot be
106 * @see nsLocalFileWin.cpp
107 * @see nsLocalFileUnix.cpp
109 static bool IsFileNotFound(nsresult aResult
) {
110 return aResult
== NS_ERROR_FILE_NOT_FOUND
;
113 * Like |IsFileNotFound|, but checks for known results that suggest a file
114 * is not a directory.
116 static bool IsNotDirectory(nsresult aResult
) {
117 return aResult
== NS_ERROR_FILE_DESTINATION_NOT_DIR
||
118 aResult
== NS_ERROR_FILE_NOT_DIRECTORY
;
122 * Formats an error message and appends the error name to the end.
124 template <typename
... Args
>
125 static nsCString
FormatErrorMessage(nsresult aError
, const char* const aMessage
,
127 nsPrintfCString
msg(aMessage
, aArgs
...);
129 if (const char* errName
= GetStaticErrorName(aError
)) {
130 msg
.AppendPrintf(": %s", errName
);
132 // In the exceptional case where there is no error name, print the literal
133 // integer value of the nsresult as an upper case hex value so it can be
134 // located easily in searchfox.
135 msg
.AppendPrintf(": 0x%" PRIX32
, static_cast<uint32_t>(aError
));
138 return std::move(msg
);
141 static nsCString
FormatErrorMessage(nsresult aError
,
142 const char* const aMessage
) {
143 const char* errName
= GetStaticErrorName(aError
);
145 return nsPrintfCString("%s: %s", aMessage
, errName
);
147 // In the exceptional case where there is no error name, print the literal
148 // integer value of the nsresult as an upper case hex value so it can be
149 // located easily in searchfox.
150 return nsPrintfCString("%s: 0x%" PRIX32
, aMessage
,
151 static_cast<uint32_t>(aError
));
154 [[nodiscard
]] inline bool ToJSValue(
155 JSContext
* aCx
, const IOUtils::InternalFileInfo
& aInternalFileInfo
,
156 JS::MutableHandle
<JS::Value
> aValue
) {
158 info
.mPath
.Construct(aInternalFileInfo
.mPath
);
159 info
.mType
.Construct(aInternalFileInfo
.mType
);
160 info
.mSize
.Construct(aInternalFileInfo
.mSize
);
162 if (aInternalFileInfo
.mCreationTime
.isSome()) {
163 info
.mCreationTime
.Construct(aInternalFileInfo
.mCreationTime
.ref());
165 info
.mLastAccessed
.Construct(aInternalFileInfo
.mLastAccessed
);
166 info
.mLastModified
.Construct(aInternalFileInfo
.mLastModified
);
168 info
.mPermissions
.Construct(aInternalFileInfo
.mPermissions
);
170 return ToJSValue(aCx
, info
, aValue
);
173 template <typename T
>
174 static void ResolveJSPromise(Promise
* aPromise
, T
&& aValue
) {
175 if constexpr (std::is_same_v
<T
, Ok
>) {
176 aPromise
->MaybeResolveWithUndefined();
177 } else if constexpr (std::is_same_v
<T
, nsTArray
<uint8_t>>) {
178 TypedArrayCreator
<Uint8Array
> array(aValue
);
179 aPromise
->MaybeResolve(array
);
181 aPromise
->MaybeResolve(std::forward
<T
>(aValue
));
185 static void RejectJSPromise(Promise
* aPromise
, const IOUtils::IOError
& aError
) {
186 const auto& errMsg
= aError
.Message();
188 switch (aError
.Code()) {
189 case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK
:
190 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
191 case NS_ERROR_FILE_NOT_FOUND
:
192 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
193 case NS_ERROR_FILE_INVALID_PATH
:
194 aPromise
->MaybeRejectWithNotFoundError(errMsg
.refOr("File not found"_ns
));
196 case NS_ERROR_FILE_IS_LOCKED
:
197 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
198 case NS_ERROR_FILE_ACCESS_DENIED
:
199 aPromise
->MaybeRejectWithNotAllowedError(
200 errMsg
.refOr("Access was denied to the target file"_ns
));
202 case NS_ERROR_FILE_TOO_BIG
:
203 aPromise
->MaybeRejectWithNotReadableError(
204 errMsg
.refOr("Target file is too big"_ns
));
206 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
207 aPromise
->MaybeRejectWithNotReadableError(
208 errMsg
.refOr("Target device is full"_ns
));
210 case NS_ERROR_FILE_ALREADY_EXISTS
:
211 aPromise
->MaybeRejectWithNoModificationAllowedError(
212 errMsg
.refOr("Target file already exists"_ns
));
214 case NS_ERROR_FILE_COPY_OR_MOVE_FAILED
:
215 aPromise
->MaybeRejectWithOperationError(
216 errMsg
.refOr("Failed to copy or move the target file"_ns
));
218 case NS_ERROR_FILE_READ_ONLY
:
219 aPromise
->MaybeRejectWithReadOnlyError(
220 errMsg
.refOr("Target file is read only"_ns
));
222 case NS_ERROR_FILE_NOT_DIRECTORY
:
223 [[fallthrough
]]; // to NS_ERROR_FILE_DESTINATION_NOT_DIR
224 case NS_ERROR_FILE_DESTINATION_NOT_DIR
:
225 aPromise
->MaybeRejectWithInvalidAccessError(
226 errMsg
.refOr("Target file is not a directory"_ns
));
228 case NS_ERROR_FILE_IS_DIRECTORY
:
229 aPromise
->MaybeRejectWithInvalidAccessError(
230 errMsg
.refOr("Target file is a directory"_ns
));
232 case NS_ERROR_FILE_UNKNOWN_TYPE
:
233 aPromise
->MaybeRejectWithInvalidAccessError(
234 errMsg
.refOr("Target file is of unknown type"_ns
));
236 case NS_ERROR_FILE_NAME_TOO_LONG
:
237 aPromise
->MaybeRejectWithOperationError(
238 errMsg
.refOr("Target file path is too long"_ns
));
240 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
241 aPromise
->MaybeRejectWithOperationError(
242 errMsg
.refOr("Target file path is not recognized"_ns
));
244 case NS_ERROR_FILE_DIR_NOT_EMPTY
:
245 aPromise
->MaybeRejectWithOperationError(
246 errMsg
.refOr("Target directory is not empty"_ns
));
248 case NS_ERROR_FILE_DEVICE_FAILURE
:
249 [[fallthrough
]]; // to NS_ERROR_FILE_FS_CORRUPTED
250 case NS_ERROR_FILE_FS_CORRUPTED
:
251 aPromise
->MaybeRejectWithNotReadableError(
252 errMsg
.refOr("Target file system may be corrupt or unavailable"_ns
));
254 case NS_ERROR_FILE_CORRUPTED
:
255 aPromise
->MaybeRejectWithNotReadableError(
256 errMsg
.refOr("Target file could not be read and may be corrupt"_ns
));
258 case NS_ERROR_ILLEGAL_INPUT
:
259 [[fallthrough
]]; // NS_ERROR_ILLEGAL_VALUE
260 case NS_ERROR_ILLEGAL_VALUE
:
261 aPromise
->MaybeRejectWithDataError(
262 errMsg
.refOr("Argument is not allowed"_ns
));
264 case NS_ERROR_NOT_AVAILABLE
:
265 aPromise
->MaybeRejectWithNotFoundError(errMsg
.refOr("Unavailable"_ns
));
268 aPromise
->MaybeRejectWithAbortError(errMsg
.refOr("Operation aborted"_ns
));
271 aPromise
->MaybeRejectWithUnknownError(FormatErrorMessage(
272 aError
.Code(), errMsg
.refOr("Unexpected error"_ns
).get()));
276 static void RejectShuttingDown(Promise
* aPromise
) {
277 RejectJSPromise(aPromise
,
278 IOUtils::IOError(NS_ERROR_ABORT
).WithMessage(SHUTDOWN_ERROR
));
281 static bool AssertParentProcessWithCallerLocationImpl(GlobalObject
& aGlobal
,
283 if (MOZ_LIKELY(XRE_IsParentProcess())) {
288 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
289 MOZ_ALWAYS_TRUE(global
);
290 MOZ_ALWAYS_TRUE(jsapi
.Init(global
));
292 JSContext
* cx
= jsapi
.cx();
294 JS::AutoFilename scriptFilename
;
296 JS::ColumnNumberZeroOrigin colNo
;
299 JS::DescribeScriptedCaller(cx
, &scriptFilename
, &lineNo
, &colNo
), false);
301 NS_ENSURE_TRUE(scriptFilename
.get(), false);
303 reason
.AppendPrintf(" Called from %s:%d:%d.", scriptFilename
.get(), lineNo
,
304 colNo
.zeroOriginValue());
308 static void AssertParentProcessWithCallerLocation(GlobalObject
& aGlobal
) {
309 nsCString reason
= "IOUtils can only be used in the parent process."_ns
;
310 if (!AssertParentProcessWithCallerLocationImpl(aGlobal
, reason
)) {
311 MOZ_CRASH_UNSAFE_PRINTF("%s", reason
.get());
315 // IOUtils implementation
317 IOUtils::StateMutex
IOUtils::sState
{"IOUtils::sState"};
320 template <typename Fn
>
321 already_AddRefed
<Promise
> IOUtils::WithPromiseAndState(GlobalObject
& aGlobal
,
324 AssertParentProcessWithCallerLocation(aGlobal
);
326 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
, aError
);
331 if (auto state
= GetState()) {
332 aFn(promise
, state
.ref());
334 RejectShuttingDown(promise
);
336 return promise
.forget();
340 template <typename OkT
, typename Fn
>
341 void IOUtils::DispatchAndResolve(IOUtils::EventQueue
* aQueue
, Promise
* aPromise
,
343 RefPtr
<StrongWorkerRef
> workerRef
;
344 if (!NS_IsMainThread()) {
345 // We need to manually keep the worker alive until the promise returned by
346 // Dispatch() resolves or rejects.
347 workerRef
= StrongWorkerRef::CreateForcibly(GetCurrentThreadWorkerPrivate(),
351 if (RefPtr
<IOPromise
<OkT
>> p
= aQueue
->Dispatch
<OkT
, Fn
>(std::move(aFunc
))) {
353 GetCurrentSerialEventTarget(), __func__
,
354 [workerRef
, promise
= RefPtr(aPromise
)](OkT
&& ok
) {
355 ResolveJSPromise(promise
, std::forward
<OkT
>(ok
));
357 [workerRef
, promise
= RefPtr(aPromise
)](const IOError
& err
) {
358 RejectJSPromise(promise
, err
);
364 already_AddRefed
<Promise
> IOUtils::Read(GlobalObject
& aGlobal
,
365 const nsAString
& aPath
,
366 const ReadOptions
& aOptions
,
367 ErrorResult
& aError
) {
368 return WithPromiseAndState(
369 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
370 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
371 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
373 Maybe
<uint32_t> toRead
= Nothing();
374 if (!aOptions
.mMaxBytes
.IsNull()) {
375 if (aOptions
.mMaxBytes
.Value() == 0) {
376 // Resolve with an empty buffer.
377 nsTArray
<uint8_t> arr(0);
378 promise
->MaybeResolve(TypedArrayCreator
<Uint8Array
>(arr
));
381 toRead
.emplace(aOptions
.mMaxBytes
.Value());
384 DispatchAndResolve
<JsBuffer
>(
385 state
->mEventQueue
, promise
,
386 [file
= std::move(file
), offset
= aOptions
.mOffset
, toRead
,
387 decompress
= aOptions
.mDecompress
]() {
388 return ReadSync(file
, offset
, toRead
, decompress
,
389 BufferKind::Uint8Array
);
395 RefPtr
<SyncReadFile
> IOUtils::OpenFileForSyncReading(GlobalObject
& aGlobal
,
396 const nsAString
& aPath
,
398 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
400 // This API is only exposed to workers, so we should not be on the main
402 MOZ_RELEASE_ASSERT(!NS_IsMainThread());
404 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
405 if (nsresult rv
= PathUtils::InitFileWithPath(file
, aPath
); NS_FAILED(rv
)) {
406 aRv
.ThrowOperationError(FormatErrorMessage(
407 rv
, "Could not parse path (%s)", NS_ConvertUTF16toUTF8(aPath
).get()));
411 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
413 stream
->Init(file
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
415 aRv
.ThrowOperationError(
416 FormatErrorMessage(rv
, "Could not open the file at %s",
417 NS_ConvertUTF16toUTF8(aPath
).get()));
422 if (nsresult rv
= stream
->GetSize(&size
); NS_FAILED(rv
)) {
423 aRv
.ThrowOperationError(FormatErrorMessage(
424 rv
, "Could not get the stream size for the file at %s",
425 NS_ConvertUTF16toUTF8(aPath
).get()));
429 return new SyncReadFile(aGlobal
.GetAsSupports(), std::move(stream
), size
);
433 already_AddRefed
<Promise
> IOUtils::ReadUTF8(GlobalObject
& aGlobal
,
434 const nsAString
& aPath
,
435 const ReadUTF8Options
& aOptions
,
436 ErrorResult
& aError
) {
437 return WithPromiseAndState(
438 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
439 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
440 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
442 DispatchAndResolve
<JsBuffer
>(
443 state
->mEventQueue
, promise
,
444 [file
= std::move(file
), decompress
= aOptions
.mDecompress
]() {
445 return ReadUTF8Sync(file
, decompress
);
451 already_AddRefed
<Promise
> IOUtils::ReadJSON(GlobalObject
& aGlobal
,
452 const nsAString
& aPath
,
453 const ReadUTF8Options
& aOptions
,
454 ErrorResult
& aError
) {
455 return WithPromiseAndState(
456 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
457 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
458 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
460 RefPtr
<StrongWorkerRef
> workerRef
;
461 if (!NS_IsMainThread()) {
462 // We need to manually keep the worker alive until the promise
463 // returned by Dispatch() resolves or rejects.
464 workerRef
= StrongWorkerRef::CreateForcibly(
465 GetCurrentThreadWorkerPrivate(), __func__
);
469 ->template Dispatch
<JsBuffer
>(
470 [file
, decompress
= aOptions
.mDecompress
]() {
471 return ReadUTF8Sync(file
, decompress
);
474 GetCurrentSerialEventTarget(), __func__
,
475 [workerRef
, promise
= RefPtr
{promise
},
476 file
](JsBuffer
&& aBuffer
) {
478 if (NS_WARN_IF(!jsapi
.Init(promise
->GetGlobalObject()))) {
479 promise
->MaybeRejectWithUnknownError(
480 "Could not initialize JS API");
483 JSContext
* cx
= jsapi
.cx();
485 JS::Rooted
<JSString
*> jsonStr(
487 IOUtils::JsBuffer::IntoString(cx
, std::move(aBuffer
)));
489 RejectJSPromise(promise
, IOError(NS_ERROR_OUT_OF_MEMORY
));
493 JS::Rooted
<JS::Value
> val(cx
);
494 if (!JS_ParseJSON(cx
, jsonStr
, &val
)) {
495 JS::Rooted
<JS::Value
> exn(cx
);
496 if (JS_GetPendingException(cx
, &exn
)) {
497 JS_ClearPendingException(cx
);
498 promise
->MaybeReject(exn
);
502 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
504 "ParseJSON threw an uncatchable exception "
505 "while parsing file(%s)",
506 file
->HumanReadablePath().get()));
512 promise
->MaybeResolve(val
);
514 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
515 RejectJSPromise(promise
, aErr
);
521 already_AddRefed
<Promise
> IOUtils::Write(GlobalObject
& aGlobal
,
522 const nsAString
& aPath
,
523 const Uint8Array
& aData
,
524 const WriteOptions
& aOptions
,
525 ErrorResult
& aError
) {
526 return WithPromiseAndState(
527 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
528 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
529 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
531 Maybe
<Buffer
<uint8_t>> buf
= aData
.CreateFromData
<Buffer
<uint8_t>>();
532 if (buf
.isNothing()) {
533 promise
->MaybeRejectWithOperationError(
534 "Out of memory: Could not allocate buffer while writing to file");
538 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
540 RejectJSPromise(promise
, opts
.unwrapErr());
544 DispatchAndResolve
<uint32_t>(
545 state
->mEventQueue
, promise
,
546 [file
= std::move(file
), buf
= buf
.extract(),
547 opts
= opts
.unwrap()]() { return WriteSync(file
, buf
, opts
); });
552 already_AddRefed
<Promise
> IOUtils::WriteUTF8(GlobalObject
& aGlobal
,
553 const nsAString
& aPath
,
554 const nsACString
& aString
,
555 const WriteOptions
& aOptions
,
556 ErrorResult
& aError
) {
557 return WithPromiseAndState(
558 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
559 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
560 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
562 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
564 RejectJSPromise(promise
, opts
.unwrapErr());
568 DispatchAndResolve
<uint32_t>(
569 state
->mEventQueue
, promise
,
570 [file
= std::move(file
), str
= nsCString(aString
),
571 opts
= opts
.unwrap()]() {
572 return WriteSync(file
, AsBytes(Span(str
)), opts
);
577 static bool AppendJsonAsUtf8(const char16_t
* aData
, uint32_t aLen
, void* aStr
) {
578 nsCString
* str
= static_cast<nsCString
*>(aStr
);
579 return AppendUTF16toUTF8(Span
<const char16_t
>(aData
, aLen
), *str
, fallible
);
583 already_AddRefed
<Promise
> IOUtils::WriteJSON(GlobalObject
& aGlobal
,
584 const nsAString
& aPath
,
585 JS::Handle
<JS::Value
> aValue
,
586 const WriteOptions
& aOptions
,
587 ErrorResult
& aError
) {
588 return WithPromiseAndState(
589 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
590 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
591 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
593 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
595 RejectJSPromise(promise
, opts
.unwrapErr());
599 if (opts
.inspect().mMode
== WriteMode::Append
||
600 opts
.inspect().mMode
== WriteMode::AppendOrCreate
) {
601 promise
->MaybeRejectWithNotSupportedError(
602 "IOUtils.writeJSON does not support appending to files."_ns
);
606 JSContext
* cx
= aGlobal
.Context();
607 JS::Rooted
<JS::Value
> rootedValue(cx
, aValue
);
610 if (!JS_Stringify(cx
, &rootedValue
, nullptr, JS::NullHandleValue
,
611 AppendJsonAsUtf8
, &utf8Str
)) {
612 JS::Rooted
<JS::Value
> exn(cx
, JS::UndefinedValue());
613 if (JS_GetPendingException(cx
, &exn
)) {
614 JS_ClearPendingException(cx
);
615 promise
->MaybeReject(exn
);
619 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
620 .WithMessage("Could not serialize object to JSON"));
625 DispatchAndResolve
<uint32_t>(
626 state
->mEventQueue
, promise
,
627 [file
= std::move(file
), utf8Str
= std::move(utf8Str
),
628 opts
= opts
.unwrap()]() {
629 return WriteSync(file
, AsBytes(Span(utf8Str
)), opts
);
635 already_AddRefed
<Promise
> IOUtils::Move(GlobalObject
& aGlobal
,
636 const nsAString
& aSourcePath
,
637 const nsAString
& aDestPath
,
638 const MoveOptions
& aOptions
,
639 ErrorResult
& aError
) {
640 return WithPromiseAndState(
641 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
642 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
643 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
645 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
646 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
648 DispatchAndResolve
<Ok
>(
649 state
->mEventQueue
, promise
,
650 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
651 noOverwrite
= aOptions
.mNoOverwrite
]() {
652 return MoveSync(sourceFile
, destFile
, noOverwrite
);
658 already_AddRefed
<Promise
> IOUtils::Remove(GlobalObject
& aGlobal
,
659 const nsAString
& aPath
,
660 const RemoveOptions
& aOptions
,
661 ErrorResult
& aError
) {
662 return WithPromiseAndState(
663 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
664 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
665 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
667 DispatchAndResolve
<Ok
>(
668 state
->mEventQueue
, promise
,
669 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
,
670 recursive
= aOptions
.mRecursive
,
671 retryReadonly
= aOptions
.mRetryReadonly
]() {
672 return RemoveSync(file
, ignoreAbsent
, recursive
, retryReadonly
);
678 already_AddRefed
<Promise
> IOUtils::MakeDirectory(
679 GlobalObject
& aGlobal
, const nsAString
& aPath
,
680 const MakeDirectoryOptions
& aOptions
, ErrorResult
& aError
) {
681 return WithPromiseAndState(
682 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
683 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
684 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
686 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
687 [file
= std::move(file
),
688 createAncestors
= aOptions
.mCreateAncestors
,
689 ignoreExisting
= aOptions
.mIgnoreExisting
,
690 permissions
= aOptions
.mPermissions
]() {
691 return MakeDirectorySync(file
, createAncestors
,
698 already_AddRefed
<Promise
> IOUtils::Stat(GlobalObject
& aGlobal
,
699 const nsAString
& aPath
,
700 ErrorResult
& aError
) {
701 return WithPromiseAndState(
702 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
703 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
704 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
706 DispatchAndResolve
<InternalFileInfo
>(
707 state
->mEventQueue
, promise
,
708 [file
= std::move(file
)]() { return StatSync(file
); });
713 already_AddRefed
<Promise
> IOUtils::Copy(GlobalObject
& aGlobal
,
714 const nsAString
& aSourcePath
,
715 const nsAString
& aDestPath
,
716 const CopyOptions
& aOptions
,
717 ErrorResult
& aError
) {
718 return WithPromiseAndState(
719 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
720 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
721 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
723 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
724 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
726 DispatchAndResolve
<Ok
>(
727 state
->mEventQueue
, promise
,
728 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
729 noOverwrite
= aOptions
.mNoOverwrite
,
730 recursive
= aOptions
.mRecursive
]() {
731 return CopySync(sourceFile
, destFile
, noOverwrite
, recursive
);
737 already_AddRefed
<Promise
> IOUtils::SetAccessTime(
738 GlobalObject
& aGlobal
, const nsAString
& aPath
,
739 const Optional
<int64_t>& aAccess
, ErrorResult
& aError
) {
740 return SetTime(aGlobal
, aPath
, aAccess
, &nsIFile::SetLastAccessedTime
,
745 already_AddRefed
<Promise
> IOUtils::SetModificationTime(
746 GlobalObject
& aGlobal
, const nsAString
& aPath
,
747 const Optional
<int64_t>& aModification
, ErrorResult
& aError
) {
748 return SetTime(aGlobal
, aPath
, aModification
, &nsIFile::SetLastModifiedTime
,
753 already_AddRefed
<Promise
> IOUtils::SetTime(GlobalObject
& aGlobal
,
754 const nsAString
& aPath
,
755 const Optional
<int64_t>& aNewTime
,
756 IOUtils::SetTimeFn aSetTimeFn
,
757 ErrorResult
& aError
) {
758 return WithPromiseAndState(
759 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
760 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
761 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
763 int64_t newTime
= aNewTime
.WasPassed() ? aNewTime
.Value()
764 : PR_Now() / PR_USEC_PER_MSEC
;
765 DispatchAndResolve
<int64_t>(
766 state
->mEventQueue
, promise
,
767 [file
= std::move(file
), aSetTimeFn
, newTime
]() {
768 return SetTimeSync(file
, aSetTimeFn
, newTime
);
774 already_AddRefed
<Promise
> IOUtils::GetChildren(
775 GlobalObject
& aGlobal
, const nsAString
& aPath
,
776 const GetChildrenOptions
& aOptions
, ErrorResult
& aError
) {
777 return WithPromiseAndState(
778 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
779 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
780 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
782 DispatchAndResolve
<nsTArray
<nsString
>>(
783 state
->mEventQueue
, promise
,
784 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
]() {
785 return GetChildrenSync(file
, ignoreAbsent
);
791 already_AddRefed
<Promise
> IOUtils::SetPermissions(GlobalObject
& aGlobal
,
792 const nsAString
& aPath
,
793 uint32_t aPermissions
,
794 const bool aHonorUmask
,
795 ErrorResult
& aError
) {
796 return WithPromiseAndState(
797 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
798 #if defined(XP_UNIX) && !defined(ANDROID)
800 aPermissions
&= ~nsSystemInfo::gUserUmask
;
804 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
805 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
807 DispatchAndResolve
<Ok
>(
808 state
->mEventQueue
, promise
,
809 [file
= std::move(file
), permissions
= aPermissions
]() {
810 return SetPermissionsSync(file
, permissions
);
816 already_AddRefed
<Promise
> IOUtils::Exists(GlobalObject
& aGlobal
,
817 const nsAString
& aPath
,
818 ErrorResult
& aError
) {
819 return WithPromiseAndState(
820 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
821 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
822 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
824 DispatchAndResolve
<bool>(
825 state
->mEventQueue
, promise
,
826 [file
= std::move(file
)]() { return ExistsSync(file
); });
831 already_AddRefed
<Promise
> IOUtils::CreateUniqueFile(GlobalObject
& aGlobal
,
832 const nsAString
& aParent
,
833 const nsAString
& aPrefix
,
834 const uint32_t aPermissions
,
835 ErrorResult
& aError
) {
836 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::NORMAL_FILE_TYPE
,
837 aPermissions
, aError
);
841 already_AddRefed
<Promise
> IOUtils::CreateUniqueDirectory(
842 GlobalObject
& aGlobal
, const nsAString
& aParent
, const nsAString
& aPrefix
,
843 const uint32_t aPermissions
, ErrorResult
& aError
) {
844 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::DIRECTORY_TYPE
,
845 aPermissions
, aError
);
849 already_AddRefed
<Promise
> IOUtils::CreateUnique(GlobalObject
& aGlobal
,
850 const nsAString
& aParent
,
851 const nsAString
& aPrefix
,
852 const uint32_t aFileType
,
853 const uint32_t aPermissions
,
854 ErrorResult
& aError
) {
855 return WithPromiseAndState(
856 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
857 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
858 REJECT_IF_INIT_PATH_FAILED(file
, aParent
, promise
);
860 if (nsresult rv
= file
->Append(aPrefix
); NS_FAILED(rv
)) {
861 RejectJSPromise(promise
,
862 IOError(rv
).WithMessage(
863 "Could not append prefix `%s' to parent `%s'",
864 NS_ConvertUTF16toUTF8(aPrefix
).get(),
865 file
->HumanReadablePath().get()));
869 DispatchAndResolve
<nsString
>(
870 state
->mEventQueue
, promise
,
871 [file
= std::move(file
), aPermissions
, aFileType
]() {
872 return CreateUniqueSync(file
, aFileType
, aPermissions
);
878 already_AddRefed
<Promise
> IOUtils::ComputeHexDigest(
879 GlobalObject
& aGlobal
, const nsAString
& aPath
,
880 const HashAlgorithm aAlgorithm
, ErrorResult
& aError
) {
881 const bool nssInitialized
= EnsureNSSInitializedChromeOrContent();
883 return WithPromiseAndState(
884 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
885 if (!nssInitialized
) {
886 RejectJSPromise(promise
,
887 IOError(NS_ERROR_UNEXPECTED
)
888 .WithMessage("Could not initialize NSS"));
892 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
893 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
895 DispatchAndResolve
<nsCString
>(state
->mEventQueue
, promise
,
896 [file
= std::move(file
), aAlgorithm
]() {
897 return ComputeHexDigestSync(file
,
906 already_AddRefed
<Promise
> IOUtils::GetWindowsAttributes(GlobalObject
& aGlobal
,
907 const nsAString
& aPath
,
908 ErrorResult
& aError
) {
909 return WithPromiseAndState(
910 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
911 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
912 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
914 RefPtr
<StrongWorkerRef
> workerRef
;
915 if (!NS_IsMainThread()) {
916 // We need to manually keep the worker alive until the promise
917 // returned by Dispatch() resolves or rejects.
918 workerRef
= StrongWorkerRef::CreateForcibly(
919 GetCurrentThreadWorkerPrivate(), __func__
);
923 ->template Dispatch
<uint32_t>([file
= std::move(file
)]() {
924 return GetWindowsAttributesSync(file
);
927 GetCurrentSerialEventTarget(), __func__
,
928 [workerRef
, promise
= RefPtr
{promise
}](const uint32_t aAttrs
) {
929 WindowsFileAttributes attrs
;
931 attrs
.mReadOnly
.Construct(aAttrs
& FILE_ATTRIBUTE_READONLY
);
932 attrs
.mHidden
.Construct(aAttrs
& FILE_ATTRIBUTE_HIDDEN
);
933 attrs
.mSystem
.Construct(aAttrs
& FILE_ATTRIBUTE_SYSTEM
);
935 promise
->MaybeResolve(attrs
);
937 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
938 RejectJSPromise(promise
, aErr
);
944 already_AddRefed
<Promise
> IOUtils::SetWindowsAttributes(
945 GlobalObject
& aGlobal
, const nsAString
& aPath
,
946 const WindowsFileAttributes
& aAttrs
, ErrorResult
& aError
) {
947 return WithPromiseAndState(
948 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
949 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
950 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
952 uint32_t setAttrs
= 0;
953 uint32_t clearAttrs
= 0;
955 if (aAttrs
.mReadOnly
.WasPassed()) {
956 if (aAttrs
.mReadOnly
.Value()) {
957 setAttrs
|= FILE_ATTRIBUTE_READONLY
;
959 clearAttrs
|= FILE_ATTRIBUTE_READONLY
;
963 if (aAttrs
.mHidden
.WasPassed()) {
964 if (aAttrs
.mHidden
.Value()) {
965 setAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
967 clearAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
971 if (aAttrs
.mSystem
.WasPassed()) {
972 if (aAttrs
.mSystem
.Value()) {
973 setAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
975 clearAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
979 DispatchAndResolve
<Ok
>(
980 state
->mEventQueue
, promise
,
981 [file
= std::move(file
), setAttrs
, clearAttrs
]() {
982 return SetWindowsAttributesSync(file
, setAttrs
, clearAttrs
);
987 #elif defined(XP_MACOSX)
990 already_AddRefed
<Promise
> IOUtils::HasMacXAttr(GlobalObject
& aGlobal
,
991 const nsAString
& aPath
,
992 const nsACString
& aAttr
,
993 ErrorResult
& aError
) {
994 return WithPromiseAndState(
995 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
996 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
997 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
999 DispatchAndResolve
<bool>(
1000 state
->mEventQueue
, promise
,
1001 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1002 return HasMacXAttrSync(file
, attr
);
1008 already_AddRefed
<Promise
> IOUtils::GetMacXAttr(GlobalObject
& aGlobal
,
1009 const nsAString
& aPath
,
1010 const nsACString
& aAttr
,
1011 ErrorResult
& aError
) {
1012 return WithPromiseAndState(
1013 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1014 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1015 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1017 DispatchAndResolve
<nsTArray
<uint8_t>>(
1018 state
->mEventQueue
, promise
,
1019 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1020 return GetMacXAttrSync(file
, attr
);
1026 already_AddRefed
<Promise
> IOUtils::SetMacXAttr(GlobalObject
& aGlobal
,
1027 const nsAString
& aPath
,
1028 const nsACString
& aAttr
,
1029 const Uint8Array
& aValue
,
1030 ErrorResult
& aError
) {
1031 return WithPromiseAndState(
1032 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1033 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1034 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1036 nsTArray
<uint8_t> value
;
1038 if (!aValue
.AppendDataTo(value
)) {
1041 IOError(NS_ERROR_OUT_OF_MEMORY
)
1043 "Could not allocate buffer to set extended attribute"));
1047 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
1048 [file
= std::move(file
), attr
= nsCString(aAttr
),
1049 value
= std::move(value
)] {
1050 return SetMacXAttrSync(file
, attr
, value
);
1056 already_AddRefed
<Promise
> IOUtils::DelMacXAttr(GlobalObject
& aGlobal
,
1057 const nsAString
& aPath
,
1058 const nsACString
& aAttr
,
1059 ErrorResult
& aError
) {
1060 return WithPromiseAndState(
1061 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1062 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1063 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1065 DispatchAndResolve
<Ok
>(
1066 state
->mEventQueue
, promise
,
1067 [file
= std::move(file
), attr
= nsCString(aAttr
)] {
1068 return DelMacXAttrSync(file
, attr
);
1076 already_AddRefed
<Promise
> IOUtils::GetFile(
1077 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1078 ErrorResult
& aError
) {
1079 return WithPromiseAndState(
1080 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1081 ErrorResult joinErr
;
1082 nsCOMPtr
<nsIFile
> file
= PathUtils::Join(aComponents
, joinErr
);
1083 if (joinErr
.Failed()) {
1084 promise
->MaybeReject(std::move(joinErr
));
1088 nsCOMPtr
<nsIFile
> parent
;
1089 if (nsresult rv
= file
->GetParent(getter_AddRefs(parent
));
1091 RejectJSPromise(promise
, IOError(rv
).WithMessage(
1092 "Could not get parent directory"));
1097 ->template Dispatch
<Ok
>([parent
= std::move(parent
)]() {
1098 return MakeDirectorySync(parent
, /* aCreateAncestors = */ true,
1099 /* aIgnoreExisting = */ true, 0755);
1102 GetCurrentSerialEventTarget(), __func__
,
1103 [file
= std::move(file
), promise
= RefPtr(promise
)](const Ok
&) {
1104 promise
->MaybeResolve(file
);
1106 [promise
= RefPtr(promise
)](const IOError
& err
) {
1107 RejectJSPromise(promise
, err
);
1113 already_AddRefed
<Promise
> IOUtils::GetDirectory(
1114 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1115 ErrorResult
& aError
) {
1116 return WithPromiseAndState(
1117 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1118 ErrorResult joinErr
;
1119 nsCOMPtr
<nsIFile
> dir
= PathUtils::Join(aComponents
, joinErr
);
1120 if (joinErr
.Failed()) {
1121 promise
->MaybeReject(std::move(joinErr
));
1126 ->template Dispatch
<Ok
>([dir
]() {
1127 return MakeDirectorySync(dir
, /* aCreateAncestors = */ true,
1128 /* aIgnoreExisting = */ true, 0755);
1131 GetCurrentSerialEventTarget(), __func__
,
1132 [dir
, promise
= RefPtr(promise
)](const Ok
&) {
1133 promise
->MaybeResolve(dir
);
1135 [promise
= RefPtr(promise
)](const IOError
& err
) {
1136 RejectJSPromise(promise
, err
);
1142 already_AddRefed
<Promise
> IOUtils::CreateJSPromise(GlobalObject
& aGlobal
,
1143 ErrorResult
& aError
) {
1144 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1145 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
1146 if (aError
.Failed()) {
1149 MOZ_ASSERT(promise
);
1150 return do_AddRef(promise
);
1154 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadSync(
1155 nsIFile
* aFile
, const uint64_t aOffset
, const Maybe
<uint32_t> aMaxBytes
,
1156 const bool aDecompress
, IOUtils::BufferKind aBufferKind
) {
1157 MOZ_ASSERT(!NS_IsMainThread());
1159 if (aMaxBytes
.isSome() && aDecompress
) {
1161 IOError(NS_ERROR_ILLEGAL_INPUT
)
1163 "The `maxBytes` and `decompress` options are not compatible"));
1166 if (aOffset
> static_cast<uint64_t>(INT64_MAX
)) {
1167 return Err(IOError(NS_ERROR_ILLEGAL_INPUT
)
1168 .WithMessage("Requested offset is too large (%" PRIu64
1170 aOffset
, INT64_MAX
));
1173 const int64_t offset
= static_cast<int64_t>(aOffset
);
1175 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
1177 stream
->Init(aFile
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
1179 return Err(IOError(rv
).WithMessage("Could not open the file at %s",
1180 aFile
->HumanReadablePath().get()));
1183 uint32_t bufSize
= 0;
1185 if (aMaxBytes
.isNothing()) {
1186 // Limitation: We cannot read more than the maximum size of a TypedArray
1187 // (UINT32_MAX bytes). Reject if we have been requested to
1188 // perform too large of a read.
1190 int64_t rawStreamSize
= -1;
1191 if (nsresult rv
= stream
->GetSize(&rawStreamSize
); NS_FAILED(rv
)) {
1192 return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED
)
1193 .WithMessage("Could not get info for the file at %s",
1194 aFile
->HumanReadablePath().get()));
1196 MOZ_RELEASE_ASSERT(rawStreamSize
>= 0);
1198 uint64_t streamSize
= static_cast<uint64_t>(rawStreamSize
);
1199 if (aOffset
>= streamSize
) {
1202 if (streamSize
- offset
> static_cast<int64_t>(UINT32_MAX
)) {
1203 return Err(IOError(NS_ERROR_FILE_TOO_BIG
)
1205 "Could not read the file at %s with offset %" PRIu32
1206 " because it is too large(size=%" PRIu64
" bytes)",
1207 aFile
->HumanReadablePath().get(), offset
,
1211 bufSize
= static_cast<uint32_t>(streamSize
- offset
);
1214 bufSize
= aMaxBytes
.value();
1218 if (nsresult rv
= stream
->Seek(PR_SEEK_SET
, offset
); NS_FAILED(rv
)) {
1219 return Err(IOError(rv
).WithMessage(
1220 "Could not seek to position %" PRId64
" in file %s", offset
,
1221 aFile
->HumanReadablePath().get()));
1225 JsBuffer buffer
= JsBuffer::CreateEmpty(aBufferKind
);
1228 auto result
= JsBuffer::Create(aBufferKind
, bufSize
);
1229 if (result
.isErr()) {
1230 return result
.propagateErr();
1232 buffer
= result
.unwrap();
1233 Span
<char> toRead
= buffer
.BeginWriting();
1235 // Read the file from disk.
1236 uint32_t totalRead
= 0;
1237 while (totalRead
!= bufSize
) {
1238 // Read no more than INT32_MAX on each call to stream->Read, otherwise it
1239 // returns an error.
1240 uint32_t bytesToReadThisChunk
=
1241 std::min
<uint32_t>(bufSize
- totalRead
, INT32_MAX
);
1242 uint32_t bytesRead
= 0;
1244 stream
->Read(toRead
.Elements(), bytesToReadThisChunk
, &bytesRead
);
1246 return Err(IOError(rv
).WithMessage(
1247 "Encountered an unexpected error while reading file(%s)",
1248 aFile
->HumanReadablePath().get()));
1250 if (bytesRead
== 0) {
1253 totalRead
+= bytesRead
;
1254 toRead
= toRead
.From(bytesRead
);
1257 buffer
.SetLength(totalRead
);
1260 // Decompress the file contents, if required.
1262 return MozLZ4::Decompress(AsBytes(buffer
.BeginReading()), aBufferKind
);
1265 return std::move(buffer
);
1269 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadUTF8Sync(
1270 nsIFile
* aFile
, bool aDecompress
) {
1271 auto result
= ReadSync(aFile
, 0, Nothing
{}, aDecompress
, BufferKind::String
);
1272 if (result
.isErr()) {
1273 return result
.propagateErr();
1276 JsBuffer buffer
= result
.unwrap();
1277 if (!IsUtf8(buffer
.BeginReading())) {
1279 IOError(NS_ERROR_FILE_CORRUPTED
)
1281 "Could not read file(%s) because it is not UTF-8 encoded",
1282 aFile
->HumanReadablePath().get()));
1289 Result
<uint32_t, IOUtils::IOError
> IOUtils::WriteSync(
1290 nsIFile
* aFile
, const Span
<const uint8_t>& aByteArray
,
1291 const IOUtils::InternalWriteOpts
& aOptions
) {
1292 MOZ_ASSERT(!NS_IsMainThread());
1294 nsIFile
* backupFile
= aOptions
.mBackupFile
;
1295 nsIFile
* tempFile
= aOptions
.mTmpFile
;
1297 bool exists
= false;
1298 MOZ_TRY(aFile
->Exists(&exists
));
1300 if (exists
&& aOptions
.mMode
== WriteMode::Create
) {
1301 return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS
)
1302 .WithMessage("Refusing to overwrite the file at %s\n"
1303 "Specify `mode: \"overwrite\"` to allow "
1304 "overwriting the destination",
1305 aFile
->HumanReadablePath().get()));
1308 // If backupFile was specified, perform the backup as a move.
1309 if (exists
&& backupFile
) {
1310 // We copy `destFile` here to a new `nsIFile` because
1311 // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we
1312 // did not do this, we would end up having `destFile` point to the same
1313 // location as `backupFile`. Then, when we went to write to `destFile`, we
1314 // would end up overwriting `backupFile` and never actually write to the
1315 // file we were supposed to.
1316 nsCOMPtr
<nsIFile
> toMove
;
1317 MOZ_ALWAYS_SUCCEEDS(aFile
->Clone(getter_AddRefs(toMove
)));
1319 bool noOverwrite
= aOptions
.mMode
== WriteMode::Create
;
1321 if (MoveSync(toMove
, backupFile
, noOverwrite
).isErr()) {
1322 return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1323 .WithMessage("Failed to backup the source file(%s) to %s",
1324 aFile
->HumanReadablePath().get(),
1325 backupFile
->HumanReadablePath().get()));
1329 // If tempFile was specified, we will write to there first, then perform a
1330 // move to ensure the file ends up at the final requested destination.
1334 writeFile
= tempFile
;
1339 int32_t flags
= PR_WRONLY
;
1341 switch (aOptions
.mMode
) {
1342 case WriteMode::Overwrite
:
1343 flags
|= PR_TRUNCATE
| PR_CREATE_FILE
;
1346 case WriteMode::Append
:
1350 case WriteMode::AppendOrCreate
:
1351 flags
|= PR_APPEND
| PR_CREATE_FILE
;
1354 case WriteMode::Create
:
1355 flags
|= PR_CREATE_FILE
| PR_EXCL
;
1359 MOZ_CRASH("IOUtils: unknown write mode");
1362 if (aOptions
.mFlush
) {
1366 // Try to perform the write and ensure that the file is closed before
1368 uint32_t totalWritten
= 0;
1370 // Compress the byte array if required.
1371 nsTArray
<uint8_t> compressed
;
1372 Span
<const char> bytes
;
1373 if (aOptions
.mCompress
) {
1374 auto rv
= MozLZ4::Compress(aByteArray
);
1376 return rv
.propagateErr();
1378 compressed
= rv
.unwrap();
1379 bytes
= Span(reinterpret_cast<const char*>(compressed
.Elements()),
1380 compressed
.Length());
1382 bytes
= Span(reinterpret_cast<const char*>(aByteArray
.Elements()),
1383 aByteArray
.Length());
1386 RefPtr
<nsFileOutputStream
> stream
= new nsFileOutputStream();
1387 if (nsresult rv
= stream
->Init(writeFile
, flags
, 0666, 0); NS_FAILED(rv
)) {
1388 // Normalize platform-specific errors for opening a directory to an access
1390 if (rv
== nsresult::NS_ERROR_FILE_IS_DIRECTORY
) {
1391 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
1394 IOError(rv
).WithMessage("Could not open the file at %s for writing",
1395 writeFile
->HumanReadablePath().get()));
1398 // nsFileRandomAccessStream::Write uses PR_Write under the hood, which
1399 // accepts a *int32_t* for the chunk size.
1400 uint32_t chunkSize
= INT32_MAX
;
1401 Span
<const char> pendingBytes
= bytes
;
1403 while (pendingBytes
.Length() > 0) {
1404 if (pendingBytes
.Length() < chunkSize
) {
1405 chunkSize
= pendingBytes
.Length();
1408 uint32_t bytesWritten
= 0;
1410 stream
->Write(pendingBytes
.Elements(), chunkSize
, &bytesWritten
);
1412 return Err(IOError(rv
).WithMessage(
1413 "Could not write chunk (size = %" PRIu32
1414 ") to file %s. The file may be corrupt.",
1415 chunkSize
, writeFile
->HumanReadablePath().get()));
1417 pendingBytes
= pendingBytes
.From(bytesWritten
);
1418 totalWritten
+= bytesWritten
;
1422 // If tempFile was passed, check destFile against writeFile and, if they
1423 // differ, the operation is finished by performing a move.
1425 nsAutoStringN
<256> destPath
;
1426 nsAutoStringN
<256> writePath
;
1428 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(destPath
));
1429 MOZ_ALWAYS_SUCCEEDS(writeFile
->GetPath(writePath
));
1431 // nsIFile::MoveToFollowingLinks will only update the path of the file if
1432 // the move succeeds.
1433 if (destPath
!= writePath
) {
1434 if (aOptions
.mTmpFile
) {
1436 if (nsresult rv
= aFile
->IsDirectory(&isDir
);
1437 NS_FAILED(rv
) && !IsFileNotFound(rv
)) {
1438 return Err(IOError(rv
).WithMessage("Could not stat the file at %s",
1439 aFile
->HumanReadablePath().get()));
1442 // If we attempt to write to a directory *without* a temp file, we get a
1443 // permission error.
1445 // However, if we are writing to a temp file first, when we copy the
1446 // temp file over the destination file, we actually end up copying it
1447 // inside the directory, which is not what we want. In this case, we are
1448 // just going to bail out early.
1451 IOError(NS_ERROR_FILE_ACCESS_DENIED
)
1452 .WithMessage("Could not open the file at %s for writing",
1453 aFile
->HumanReadablePath().get()));
1457 if (MoveSync(writeFile
, aFile
, /* aNoOverwrite = */ false).isErr()) {
1459 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1461 "Could not move temporary file(%s) to destination(%s)",
1462 writeFile
->HumanReadablePath().get(),
1463 aFile
->HumanReadablePath().get()));
1467 return totalWritten
;
1471 Result
<Ok
, IOUtils::IOError
> IOUtils::MoveSync(nsIFile
* aSourceFile
,
1473 bool aNoOverwrite
) {
1474 MOZ_ASSERT(!NS_IsMainThread());
1476 // Ensure the source file exists before continuing. If it doesn't exist,
1477 // subsequent operations can fail in different ways on different platforms.
1478 bool srcExists
= false;
1479 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1482 IOError(NS_ERROR_FILE_NOT_FOUND
)
1484 "Could not move source file(%s) because it does not exist",
1485 aSourceFile
->HumanReadablePath().get()));
1488 return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks
, "move", aSourceFile
,
1489 aDestFile
, aNoOverwrite
);
1493 Result
<Ok
, IOUtils::IOError
> IOUtils::CopySync(nsIFile
* aSourceFile
,
1497 MOZ_ASSERT(!NS_IsMainThread());
1499 // Ensure the source file exists before continuing. If it doesn't exist,
1500 // subsequent operations can fail in different ways on different platforms.
1502 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1505 IOError(NS_ERROR_FILE_NOT_FOUND
)
1507 "Could not copy source file(%s) because it does not exist",
1508 aSourceFile
->HumanReadablePath().get()));
1511 // If source is a directory, fail immediately unless the recursive option is
1513 bool srcIsDir
= false;
1514 MOZ_TRY(aSourceFile
->IsDirectory(&srcIsDir
));
1515 if (srcIsDir
&& !aRecursive
) {
1517 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1519 "Refused to copy source directory(%s) to the destination(%s)\n"
1520 "Specify the `recursive: true` option to allow copying "
1522 aSourceFile
->HumanReadablePath().get(),
1523 aDestFile
->HumanReadablePath().get()));
1526 return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks
, "copy", aSourceFile
,
1527 aDestFile
, aNoOverwrite
);
1531 template <typename CopyOrMoveFn
>
1532 Result
<Ok
, IOUtils::IOError
> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod
,
1533 const char* aMethodName
,
1536 bool aNoOverwrite
) {
1537 MOZ_ASSERT(!NS_IsMainThread());
1539 // Case 1: Destination is an existing directory. Copy/move source into dest.
1540 bool destIsDir
= false;
1541 bool destExists
= true;
1543 nsresult rv
= aDest
->IsDirectory(&destIsDir
);
1544 if (NS_SUCCEEDED(rv
) && destIsDir
) {
1545 rv
= (aSource
->*aMethod
)(aDest
, u
""_ns
);
1546 if (NS_FAILED(rv
)) {
1547 return Err(IOError(rv
).WithMessage(
1548 "Could not %s source file(%s) to destination directory(%s)",
1549 aMethodName
, aSource
->HumanReadablePath().get(),
1550 aDest
->HumanReadablePath().get()));
1555 if (NS_FAILED(rv
)) {
1556 if (!IsFileNotFound(rv
)) {
1557 // It's ok if the dest file doesn't exist. Case 2 handles this below.
1558 // Bail out early for any other kind of error though.
1559 return Err(IOError(rv
));
1564 // Case 2: Destination is a file which may or may not exist.
1565 // Try to copy or rename the source to the destination.
1566 // If the destination exists and the source is not a regular file,
1567 // then this may fail.
1568 if (aNoOverwrite
&& destExists
) {
1570 IOError(NS_ERROR_FILE_ALREADY_EXISTS
)
1572 "Could not %s source file(%s) to destination(%s) because the "
1573 "destination already exists and overwrites are not allowed\n"
1574 "Specify the `noOverwrite: false` option to mitigate this "
1576 aMethodName
, aSource
->HumanReadablePath().get(),
1577 aDest
->HumanReadablePath().get()));
1579 if (destExists
&& !destIsDir
) {
1580 // If the source file is a directory, but the target is a file, abort early.
1581 // Different implementations of |CopyTo| and |MoveTo| seem to handle this
1582 // error case differently (or not at all), so we explicitly handle it here.
1583 bool srcIsDir
= false;
1584 MOZ_TRY(aSource
->IsDirectory(&srcIsDir
));
1586 return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR
)
1587 .WithMessage("Could not %s the source directory(%s) to "
1588 "the destination(%s) because the destination "
1589 "is not a directory",
1591 aSource
->HumanReadablePath().get(),
1592 aDest
->HumanReadablePath().get()));
1596 nsCOMPtr
<nsIFile
> destDir
;
1597 nsAutoString destName
;
1598 MOZ_TRY(aDest
->GetLeafName(destName
));
1599 MOZ_TRY(aDest
->GetParent(getter_AddRefs(destDir
)));
1601 // We know `destName` is a file and therefore must have a parent directory.
1602 MOZ_RELEASE_ASSERT(destDir
);
1604 // NB: if destDir doesn't exist, then |CopyToFollowingLinks| or
1605 // |MoveToFollowingLinks| will create it.
1606 rv
= (aSource
->*aMethod
)(destDir
, destName
);
1607 if (NS_FAILED(rv
)) {
1608 return Err(IOError(rv
).WithMessage(
1609 "Could not %s the source file(%s) to the destination(%s)", aMethodName
,
1610 aSource
->HumanReadablePath().get(), aDest
->HumanReadablePath().get()));
1616 Result
<Ok
, IOUtils::IOError
> IOUtils::RemoveSync(nsIFile
* aFile
,
1619 bool aRetryReadonly
) {
1620 MOZ_ASSERT(!NS_IsMainThread());
1622 // Prevent an unused variable warning.
1623 (void)aRetryReadonly
;
1625 nsresult rv
= aFile
->Remove(aRecursive
);
1626 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1629 if (NS_FAILED(rv
)) {
1631 if (IsFileNotFound(rv
)) {
1632 return Err(err
.WithMessage(
1633 "Could not remove the file at %s because it does not exist.\n"
1634 "Specify the `ignoreAbsent: true` option to mitigate this error",
1635 aFile
->HumanReadablePath().get()));
1637 if (rv
== NS_ERROR_FILE_DIR_NOT_EMPTY
) {
1638 return Err(err
.WithMessage(
1639 "Could not remove the non-empty directory at %s.\n"
1640 "Specify the `recursive: true` option to mitigate this error",
1641 aFile
->HumanReadablePath().get()));
1646 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
&& aRetryReadonly
) {
1647 MOZ_TRY(SetWindowsAttributesSync(aFile
, 0, FILE_ATTRIBUTE_READONLY
));
1648 return RemoveSync(aFile
, aIgnoreAbsent
, aRecursive
,
1649 /* aRetryReadonly = */ false);
1654 return Err(err
.WithMessage("Could not remove the file at %s",
1655 aFile
->HumanReadablePath().get()));
1661 Result
<Ok
, IOUtils::IOError
> IOUtils::MakeDirectorySync(nsIFile
* aFile
,
1662 bool aCreateAncestors
,
1663 bool aIgnoreExisting
,
1665 MOZ_ASSERT(!NS_IsMainThread());
1667 nsCOMPtr
<nsIFile
> parent
;
1668 MOZ_TRY(aFile
->GetParent(getter_AddRefs(parent
)));
1670 // If we don't have a parent directory, we were called with a
1671 // root directory. If the directory doesn't already exist (e.g., asking
1672 // for a drive on Windows that does not exist), we will not be able to
1675 // Calling `nsLocalFile::Create()` on Windows can fail with
1676 // `NS_ERROR_ACCESS_DENIED` trying to create a root directory, but we
1677 // would rather the call succeed, so return early if the directory exists.
1679 // Otherwise, we fall through to `nsiFile::Create()` and let it fail there
1681 bool exists
= false;
1682 MOZ_TRY(aFile
->Exists(&exists
));
1689 aFile
->Create(nsIFile::DIRECTORY_TYPE
, aMode
, !aCreateAncestors
);
1690 if (NS_FAILED(rv
)) {
1691 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1692 // NB: We may report a success only if the target is an existing
1693 // directory. We don't want to silence errors that occur if the target is
1694 // an existing file, since trying to create a directory where a regular
1695 // file exists may be indicative of a logic error.
1697 MOZ_TRY(aFile
->IsDirectory(&isDirectory
));
1699 return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY
)
1700 .WithMessage("Could not create directory because the "
1701 "target file(%s) exists "
1702 "and is not a directory",
1703 aFile
->HumanReadablePath().get()));
1705 // The directory exists.
1706 // The caller may suppress this error.
1707 if (aIgnoreExisting
) {
1710 // Otherwise, forward it.
1711 return Err(IOError(rv
).WithMessage(
1712 "Could not create directory because it already exists at %s\n"
1713 "Specify the `ignoreExisting: true` option to mitigate this "
1715 aFile
->HumanReadablePath().get()));
1717 return Err(IOError(rv
).WithMessage("Could not create directory at %s",
1718 aFile
->HumanReadablePath().get()));
1723 Result
<IOUtils::InternalFileInfo
, IOUtils::IOError
> IOUtils::StatSync(
1725 MOZ_ASSERT(!NS_IsMainThread());
1727 InternalFileInfo info
;
1728 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(info
.mPath
));
1730 bool isRegular
= false;
1731 // IsFile will stat and cache info in the file object. If the file doesn't
1732 // exist, or there is an access error, we'll discover it here.
1733 // Any subsequent errors are unexpected and will just be forwarded.
1734 nsresult rv
= aFile
->IsFile(&isRegular
);
1735 if (NS_FAILED(rv
)) {
1737 if (IsFileNotFound(rv
)) {
1739 err
.WithMessage("Could not stat file(%s) because it does not exist",
1740 aFile
->HumanReadablePath().get()));
1745 // Now we can populate the info object by querying the file.
1746 info
.mType
= FileType::Regular
;
1749 MOZ_TRY(aFile
->IsDirectory(&isDir
));
1750 info
.mType
= isDir
? FileType::Directory
: FileType::Other
;
1754 if (info
.mType
== FileType::Regular
) {
1755 MOZ_TRY(aFile
->GetFileSize(&size
));
1759 PRTime creationTime
= 0;
1760 if (nsresult rv
= aFile
->GetCreationTime(&creationTime
); NS_SUCCEEDED(rv
)) {
1761 info
.mCreationTime
.emplace(static_cast<int64_t>(creationTime
));
1762 } else if (NS_FAILED(rv
) && rv
!= NS_ERROR_NOT_IMPLEMENTED
) {
1763 // This field is only supported on some platforms.
1764 return Err(IOError(rv
));
1767 PRTime lastAccessed
= 0;
1768 MOZ_TRY(aFile
->GetLastAccessedTime(&lastAccessed
));
1769 info
.mLastAccessed
= static_cast<int64_t>(lastAccessed
);
1771 PRTime lastModified
= 0;
1772 MOZ_TRY(aFile
->GetLastModifiedTime(&lastModified
));
1773 info
.mLastModified
= static_cast<int64_t>(lastModified
);
1775 MOZ_TRY(aFile
->GetPermissions(&info
.mPermissions
));
1781 Result
<int64_t, IOUtils::IOError
> IOUtils::SetTimeSync(
1782 nsIFile
* aFile
, IOUtils::SetTimeFn aSetTimeFn
, int64_t aNewTime
) {
1783 MOZ_ASSERT(!NS_IsMainThread());
1785 // nsIFile::SetLastModifiedTime will *not* do what is expected when passed 0
1786 // as an argument. Rather than setting the time to 0, it will recalculate the
1787 // system time and set it to that value instead. We explicit forbid this,
1788 // because this side effect is surprising.
1790 // If it ever becomes possible to set a file time to 0, this check should be
1791 // removed, though this use case seems rare.
1792 if (aNewTime
== 0) {
1794 IOError(NS_ERROR_ILLEGAL_VALUE
)
1796 "Refusing to set the modification time of file(%s) to 0.\n"
1797 "To use the current system time, call `setModificationTime` "
1798 "with no arguments",
1799 aFile
->HumanReadablePath().get()));
1802 nsresult rv
= (aFile
->*aSetTimeFn
)(aNewTime
);
1804 if (NS_FAILED(rv
)) {
1806 if (IsFileNotFound(rv
)) {
1808 err
.WithMessage("Could not set modification time of file(%s) "
1809 "because it does not exist",
1810 aFile
->HumanReadablePath().get()));
1818 Result
<nsTArray
<nsString
>, IOUtils::IOError
> IOUtils::GetChildrenSync(
1819 nsIFile
* aFile
, bool aIgnoreAbsent
) {
1820 MOZ_ASSERT(!NS_IsMainThread());
1822 nsTArray
<nsString
> children
;
1823 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
1824 nsresult rv
= aFile
->GetDirectoryEntries(getter_AddRefs(iter
));
1825 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1828 if (NS_FAILED(rv
)) {
1830 if (IsFileNotFound(rv
)) {
1831 return Err(err
.WithMessage(
1832 "Could not get children of file(%s) because it does not exist",
1833 aFile
->HumanReadablePath().get()));
1835 if (IsNotDirectory(rv
)) {
1836 return Err(err
.WithMessage(
1837 "Could not get children of file(%s) because it is not a directory",
1838 aFile
->HumanReadablePath().get()));
1843 bool hasMoreElements
= false;
1844 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1845 while (hasMoreElements
) {
1846 nsCOMPtr
<nsIFile
> child
;
1847 MOZ_TRY(iter
->GetNextFile(getter_AddRefs(child
)));
1850 MOZ_TRY(child
->GetPath(path
));
1851 children
.AppendElement(path
);
1853 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1860 Result
<Ok
, IOUtils::IOError
> IOUtils::SetPermissionsSync(
1861 nsIFile
* aFile
, const uint32_t aPermissions
) {
1862 MOZ_ASSERT(!NS_IsMainThread());
1864 MOZ_TRY(aFile
->SetPermissions(aPermissions
));
1869 Result
<bool, IOUtils::IOError
> IOUtils::ExistsSync(nsIFile
* aFile
) {
1870 MOZ_ASSERT(!NS_IsMainThread());
1872 bool exists
= false;
1873 MOZ_TRY(aFile
->Exists(&exists
));
1879 Result
<nsString
, IOUtils::IOError
> IOUtils::CreateUniqueSync(
1880 nsIFile
* aFile
, const uint32_t aFileType
, const uint32_t aPermissions
) {
1881 MOZ_ASSERT(!NS_IsMainThread());
1883 if (nsresult rv
= aFile
->CreateUnique(aFileType
, aPermissions
);
1885 return Err(IOError(rv
).WithMessage("Could not create unique path"));
1889 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(path
));
1895 Result
<nsCString
, IOUtils::IOError
> IOUtils::ComputeHexDigestSync(
1896 nsIFile
* aFile
, const HashAlgorithm aAlgorithm
) {
1897 static constexpr size_t BUFFER_SIZE
= 8192;
1900 switch (aAlgorithm
) {
1901 case HashAlgorithm::Sha1
:
1905 case HashAlgorithm::Sha256
:
1906 alg
= SEC_OID_SHA256
;
1909 case HashAlgorithm::Sha384
:
1910 alg
= SEC_OID_SHA384
;
1913 case HashAlgorithm::Sha512
:
1914 alg
= SEC_OID_SHA512
;
1918 MOZ_RELEASE_ASSERT(false, "Unexpected HashAlgorithm");
1922 if (nsresult rv
= digest
.Begin(alg
); NS_FAILED(rv
)) {
1923 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1924 aFile
->HumanReadablePath().get()));
1927 RefPtr
<nsIInputStream
> stream
;
1928 if (nsresult rv
= NS_NewLocalFileInputStream(getter_AddRefs(stream
), aFile
);
1930 return Err(IOError(rv
).WithMessage("Could not open the file at %s",
1931 aFile
->HumanReadablePath().get()));
1934 char buffer
[BUFFER_SIZE
];
1937 if (nsresult rv
= stream
->Read(buffer
, BUFFER_SIZE
, &read
); NS_FAILED(rv
)) {
1938 return Err(IOError(rv
).WithMessage(
1939 "Encountered an unexpected error while reading file(%s)",
1940 aFile
->HumanReadablePath().get()));
1947 digest
.Update(reinterpret_cast<unsigned char*>(buffer
), read
);
1949 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1950 aFile
->HumanReadablePath().get()));
1954 AutoTArray
<uint8_t, SHA512_LENGTH
> rawDigest
;
1955 if (nsresult rv
= digest
.End(rawDigest
); NS_FAILED(rv
)) {
1956 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1957 aFile
->HumanReadablePath().get()));
1960 nsCString hexDigest
;
1961 if (!hexDigest
.SetCapacity(2 * rawDigest
.Length(), fallible
)) {
1962 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
));
1965 const char HEX
[] = "0123456789abcdef";
1966 for (uint8_t b
: rawDigest
) {
1967 hexDigest
.Append(HEX
[(b
>> 4) & 0xF]);
1968 hexDigest
.Append(HEX
[b
& 0xF]);
1976 Result
<uint32_t, IOUtils::IOError
> IOUtils::GetWindowsAttributesSync(
1978 MOZ_ASSERT(!NS_IsMainThread());
1982 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
1985 if (nsresult rv
= file
->GetWindowsFileAttributes(&attrs
); NS_FAILED(rv
)) {
1986 return Err(IOError(rv
).WithMessage(
1987 "Could not get Windows file attributes for the file at `%s'",
1988 aFile
->HumanReadablePath().get()));
1993 Result
<Ok
, IOUtils::IOError
> IOUtils::SetWindowsAttributesSync(
1994 nsIFile
* aFile
, const uint32_t aSetAttrs
, const uint32_t aClearAttrs
) {
1995 MOZ_ASSERT(!NS_IsMainThread());
1997 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
2000 if (nsresult rv
= file
->SetWindowsFileAttributes(aSetAttrs
, aClearAttrs
);
2002 return Err(IOError(rv
).WithMessage(
2003 "Could not set Windows file attributes for the file at `%s'",
2004 aFile
->HumanReadablePath().get()));
2010 #elif defined(XP_MACOSX)
2013 Result
<bool, IOUtils::IOError
> IOUtils::HasMacXAttrSync(
2014 nsIFile
* aFile
, const nsCString
& aAttr
) {
2015 MOZ_ASSERT(!NS_IsMainThread());
2017 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2020 bool hasAttr
= false;
2021 if (nsresult rv
= file
->HasXAttr(aAttr
, &hasAttr
); NS_FAILED(rv
)) {
2022 return Err(IOError(rv
).WithMessage(
2023 "Could not read the extended attribute `%s' from the file `%s'",
2024 aAttr
.get(), aFile
->HumanReadablePath().get()));
2031 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::GetMacXAttrSync(
2032 nsIFile
* aFile
, const nsCString
& aAttr
) {
2033 MOZ_ASSERT(!NS_IsMainThread());
2035 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2038 nsTArray
<uint8_t> value
;
2039 if (nsresult rv
= file
->GetXAttr(aAttr
, value
); NS_FAILED(rv
)) {
2040 auto err
= IOError(rv
);
2042 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2043 return Err(err
.WithMessage(
2044 "The file `%s' does not have an extended attribute `%s'",
2045 aFile
->HumanReadablePath().get(), aAttr
.get()));
2048 return Err(err
.WithMessage(
2049 "Could not read the extended attribute `%s' from the file `%s'",
2050 aAttr
.get(), aFile
->HumanReadablePath().get()));
2057 Result
<Ok
, IOUtils::IOError
> IOUtils::SetMacXAttrSync(
2058 nsIFile
* aFile
, const nsCString
& aAttr
, const nsTArray
<uint8_t>& aValue
) {
2059 MOZ_ASSERT(!NS_IsMainThread());
2061 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2064 if (nsresult rv
= file
->SetXAttr(aAttr
, aValue
); NS_FAILED(rv
)) {
2065 return Err(IOError(rv
).WithMessage(
2066 "Could not set extended attribute `%s' on file `%s'", aAttr
.get(),
2067 aFile
->HumanReadablePath().get()));
2074 Result
<Ok
, IOUtils::IOError
> IOUtils::DelMacXAttrSync(nsIFile
* aFile
,
2075 const nsCString
& aAttr
) {
2076 MOZ_ASSERT(!NS_IsMainThread());
2078 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2081 if (nsresult rv
= file
->DelXAttr(aAttr
); NS_FAILED(rv
)) {
2082 auto err
= IOError(rv
);
2084 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2085 return Err(err
.WithMessage(
2086 "The file `%s' does not have an extended attribute `%s'",
2087 aFile
->HumanReadablePath().get(), aAttr
.get()));
2090 return Err(IOError(rv
).WithMessage(
2091 "Could not delete extended attribute `%s' on file `%s'", aAttr
.get(),
2092 aFile
->HumanReadablePath().get()));
2101 void IOUtils::GetProfileBeforeChange(GlobalObject
& aGlobal
,
2102 JS::MutableHandle
<JS::Value
> aClient
,
2104 return GetShutdownClient(aGlobal
, aClient
, aRv
,
2105 ShutdownPhase::ProfileBeforeChange
);
2109 void IOUtils::GetSendTelemetry(GlobalObject
& aGlobal
,
2110 JS::MutableHandle
<JS::Value
> aClient
,
2112 return GetShutdownClient(aGlobal
, aClient
, aRv
, ShutdownPhase::SendTelemetry
);
2116 * Assert that the given phase has a shutdown client exposed by IOUtils
2118 * There is no shutdown client exposed for XpcomWillShutdown.
2120 static void AssertHasShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2121 MOZ_RELEASE_ASSERT(aPhase
>= IOUtils::ShutdownPhase::ProfileBeforeChange
&&
2122 aPhase
< IOUtils::ShutdownPhase::XpcomWillShutdown
);
2126 void IOUtils::GetShutdownClient(GlobalObject
& aGlobal
,
2127 JS::MutableHandle
<JS::Value
> aClient
,
2129 const IOUtils::ShutdownPhase aPhase
) {
2130 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
2131 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2132 AssertHasShutdownClient(aPhase
);
2134 if (auto state
= GetState()) {
2135 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
!=
2136 ShutdownBlockerStatus::Uninitialized
);
2138 if (state
.ref()->mBlockerStatus
== ShutdownBlockerStatus::Failed
) {
2139 aRv
.ThrowAbortError("IOUtils: could not register shutdown blockers");
2143 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
==
2144 ShutdownBlockerStatus::Initialized
);
2145 auto result
= state
.ref()->mEventQueue
->GetShutdownClient(aPhase
);
2146 if (result
.isErr()) {
2147 aRv
.ThrowAbortError("IOUtils: could not get shutdown client");
2151 RefPtr
<nsIAsyncShutdownClient
> client
= result
.unwrap();
2152 MOZ_RELEASE_ASSERT(client
);
2153 if (nsresult rv
= client
->GetJsclient(aClient
); NS_FAILED(rv
)) {
2154 aRv
.ThrowAbortError("IOUtils: Could not get shutdown jsclient");
2159 aRv
.ThrowAbortError(
2160 "IOUtils: profileBeforeChange phase has already finished");
2164 Maybe
<IOUtils::StateMutex::AutoLock
> IOUtils::GetState() {
2165 auto state
= sState
.Lock();
2166 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2170 if (state
->mQueueStatus
== EventQueueStatus::Uninitialized
) {
2171 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2172 state
->mEventQueue
= new EventQueue();
2173 state
->mQueueStatus
= EventQueueStatus::Initialized
;
2175 MOZ_RELEASE_ASSERT(state
->mBlockerStatus
==
2176 ShutdownBlockerStatus::Uninitialized
);
2179 if (NS_IsMainThread() &&
2180 state
->mBlockerStatus
== ShutdownBlockerStatus::Uninitialized
) {
2181 state
->SetShutdownHooks();
2184 return Some(std::move(state
));
2187 IOUtils::EventQueue::EventQueue() {
2188 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
2189 "IOUtils::EventQueue", getter_AddRefs(mBackgroundEventTarget
)));
2191 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2194 void IOUtils::State::SetShutdownHooks() {
2195 if (mBlockerStatus
!= ShutdownBlockerStatus::Uninitialized
) {
2199 if (NS_WARN_IF(NS_FAILED(mEventQueue
->SetShutdownHooks()))) {
2200 mBlockerStatus
= ShutdownBlockerStatus::Failed
;
2202 mBlockerStatus
= ShutdownBlockerStatus::Initialized
;
2205 if (mBlockerStatus
!= ShutdownBlockerStatus::Initialized
) {
2206 NS_WARNING("IOUtils: could not register shutdown blockers.");
2210 nsresult
IOUtils::EventQueue::SetShutdownHooks() {
2211 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2213 constexpr static auto STACK
= u
"IOUtils::EventQueue::SetShutdownHooks"_ns
;
2214 constexpr static auto FILE = NS_LITERAL_STRING_FROM_CSTRING(__FILE__
);
2216 nsCOMPtr
<nsIAsyncShutdownService
> svc
= services::GetAsyncShutdownService();
2218 return NS_ERROR_NOT_AVAILABLE
;
2221 nsCOMPtr
<nsIAsyncShutdownBlocker
> profileBeforeChangeBlocker
;
2223 // Create a shutdown blocker for the profile-before-change phase.
2225 profileBeforeChangeBlocker
=
2226 new IOUtilsShutdownBlocker(ShutdownPhase::ProfileBeforeChange
);
2228 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2229 MOZ_TRY(svc
->GetProfileBeforeChange(getter_AddRefs(globalClient
)));
2230 MOZ_RELEASE_ASSERT(globalClient
);
2232 MOZ_TRY(globalClient
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
,
2236 // Create the shutdown barrier for profile-before-change so that consumers can
2237 // register shutdown blockers.
2239 // The blocker we just created will wait for all clients registered on this
2240 // barrier to finish.
2242 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2244 // It is okay for this to fail. The created shutdown blocker won't await
2245 // anything and shutdown will proceed.
2246 MOZ_TRY(svc
->MakeBarrier(
2247 u
"IOUtils: waiting for profileBeforeChange IO to complete"_ns
,
2248 getter_AddRefs(barrier
)));
2249 MOZ_RELEASE_ASSERT(barrier
);
2251 mBarriers
[ShutdownPhase::ProfileBeforeChange
] = std::move(barrier
);
2254 // Create a shutdown blocker for the profile-before-change-telemetry phase.
2255 nsCOMPtr
<nsIAsyncShutdownBlocker
> sendTelemetryBlocker
;
2257 sendTelemetryBlocker
=
2258 new IOUtilsShutdownBlocker(ShutdownPhase::SendTelemetry
);
2260 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2261 MOZ_TRY(svc
->GetSendTelemetry(getter_AddRefs(globalClient
)));
2262 MOZ_RELEASE_ASSERT(globalClient
);
2265 globalClient
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
, STACK
));
2268 // Create the shutdown barrier for profile-before-change-telemetry so that
2269 // consumers can register shutdown blockers.
2271 // The blocker we just created will wait for all clients registered on this
2272 // barrier to finish.
2274 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2276 MOZ_TRY(svc
->MakeBarrier(
2277 u
"IOUtils: waiting for sendTelemetry IO to complete"_ns
,
2278 getter_AddRefs(barrier
)));
2279 MOZ_RELEASE_ASSERT(barrier
);
2281 // Add a blocker on the previous shutdown phase.
2282 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2283 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2286 client
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
, STACK
));
2288 mBarriers
[ShutdownPhase::SendTelemetry
] = std::move(barrier
);
2291 // Create a shutdown blocker for the xpcom-will-shutdown phase.
2293 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2294 MOZ_TRY(svc
->GetXpcomWillShutdown(getter_AddRefs(globalClient
)));
2295 MOZ_RELEASE_ASSERT(globalClient
);
2297 nsCOMPtr
<nsIAsyncShutdownBlocker
> blocker
=
2298 new IOUtilsShutdownBlocker(ShutdownPhase::XpcomWillShutdown
);
2299 MOZ_TRY(globalClient
->AddBlocker(
2300 blocker
, FILE, __LINE__
, u
"IOUtils::EventQueue::SetShutdownHooks"_ns
));
2303 // Create a shutdown barrier for the xpcom-will-shutdown phase.
2305 // The blocker we just created will wait for all clients registered on this
2306 // barrier to finish.
2308 // The only client registered on this barrier should be a blocker for the
2309 // previous phase. This is to ensure that all shutdown IO happens when
2310 // shutdown phases do not happen (e.g., in xpcshell tests where
2311 // profile-before-change does not occur).
2313 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2315 MOZ_TRY(svc
->MakeBarrier(
2316 u
"IOUtils: waiting for xpcomWillShutdown IO to complete"_ns
,
2317 getter_AddRefs(barrier
)));
2318 MOZ_RELEASE_ASSERT(barrier
);
2320 // Add a blocker on the previous shutdown phase.
2321 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2322 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2324 client
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
,
2325 u
"IOUtils::EventQueue::SetShutdownHooks"_ns
);
2327 mBarriers
[ShutdownPhase::XpcomWillShutdown
] = std::move(barrier
);
2333 template <typename OkT
, typename Fn
>
2334 RefPtr
<IOUtils::IOPromise
<OkT
>> IOUtils::EventQueue::Dispatch(Fn aFunc
) {
2335 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2338 MakeRefPtr
<typename
IOUtils::IOPromise
<OkT
>::Private
>(__func__
);
2339 mBackgroundEventTarget
->Dispatch(
2340 NS_NewRunnableFunction("IOUtils::EventQueue::Dispatch",
2341 [promise
, func
= std::move(aFunc
)] {
2342 Result
<OkT
, IOError
> result
= func();
2343 if (result
.isErr()) {
2344 promise
->Reject(result
.unwrapErr(), __func__
);
2346 promise
->Resolve(result
.unwrap(), __func__
);
2349 NS_DISPATCH_EVENT_MAY_BLOCK
);
2353 Result
<already_AddRefed
<nsIAsyncShutdownBarrier
>, nsresult
>
2354 IOUtils::EventQueue::GetShutdownBarrier(const IOUtils::ShutdownPhase aPhase
) {
2355 if (!mBarriers
[aPhase
]) {
2356 return Err(NS_ERROR_NOT_AVAILABLE
);
2359 return do_AddRef(mBarriers
[aPhase
]);
2362 Result
<already_AddRefed
<nsIAsyncShutdownClient
>, nsresult
>
2363 IOUtils::EventQueue::GetShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2364 AssertHasShutdownClient(aPhase
);
2366 if (!mBarriers
[aPhase
]) {
2367 return Err(NS_ERROR_NOT_AVAILABLE
);
2370 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2371 MOZ_TRY(mBarriers
[aPhase
]->GetClient(getter_AddRefs(client
)));
2373 return do_AddRef(client
);
2377 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::MozLZ4::Compress(
2378 Span
<const uint8_t> aUncompressed
) {
2379 nsTArray
<uint8_t> result
;
2380 size_t worstCaseSize
=
2381 Compression::LZ4::maxCompressedSize(aUncompressed
.Length()) + HEADER_SIZE
;
2382 if (!result
.SetCapacity(worstCaseSize
, fallible
)) {
2383 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
2384 .WithMessage("Could not allocate buffer to compress data"));
2386 result
.AppendElements(Span(MAGIC_NUMBER
.data(), MAGIC_NUMBER
.size()));
2387 std::array
<uint8_t, sizeof(uint32_t)> contentSizeBytes
{};
2388 LittleEndian::writeUint32(contentSizeBytes
.data(), aUncompressed
.Length());
2389 result
.AppendElements(Span(contentSizeBytes
.data(), contentSizeBytes
.size()));
2391 if (aUncompressed
.Length() == 0) {
2392 // Don't try to compress an empty buffer.
2393 // Just return the correctly formed header.
2394 result
.SetLength(HEADER_SIZE
);
2398 size_t compressed
= Compression::LZ4::compress(
2399 reinterpret_cast<const char*>(aUncompressed
.Elements()),
2400 aUncompressed
.Length(),
2401 reinterpret_cast<char*>(result
.Elements()) + HEADER_SIZE
);
2404 IOError(NS_ERROR_UNEXPECTED
).WithMessage("Could not compress data"));
2406 result
.SetLength(HEADER_SIZE
+ compressed
);
2411 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::MozLZ4::Decompress(
2412 Span
<const uint8_t> aFileContents
, IOUtils::BufferKind aBufferKind
) {
2413 if (aFileContents
.LengthBytes() < HEADER_SIZE
) {
2415 IOError(NS_ERROR_FILE_CORRUPTED
)
2417 "Could not decompress file because the buffer is too short"));
2419 auto header
= aFileContents
.To(HEADER_SIZE
);
2420 if (!std::equal(std::begin(MAGIC_NUMBER
), std::end(MAGIC_NUMBER
),
2421 std::begin(header
))) {
2424 for (; i
< header
.Length() - 1; ++i
) {
2425 magicStr
.AppendPrintf("%02X ", header
.at(i
));
2427 magicStr
.AppendPrintf("%02X", header
.at(i
));
2429 return Err(IOError(NS_ERROR_FILE_CORRUPTED
)
2430 .WithMessage("Could not decompress file because it has an "
2431 "invalid LZ4 header (wrong magic number: '%s')",
2434 size_t numBytes
= sizeof(uint32_t);
2435 Span
<const uint8_t> sizeBytes
= header
.Last(numBytes
);
2436 uint32_t expectedDecompressedSize
=
2437 LittleEndian::readUint32(sizeBytes
.data());
2438 if (expectedDecompressedSize
== 0) {
2439 return JsBuffer::CreateEmpty(aBufferKind
);
2441 auto contents
= aFileContents
.From(HEADER_SIZE
);
2442 auto result
= JsBuffer::Create(aBufferKind
, expectedDecompressedSize
);
2443 if (result
.isErr()) {
2444 return result
.propagateErr();
2447 JsBuffer decompressed
= result
.unwrap();
2448 size_t actualSize
= 0;
2449 if (!Compression::LZ4::decompress(
2450 reinterpret_cast<const char*>(contents
.Elements()), contents
.Length(),
2451 reinterpret_cast<char*>(decompressed
.Elements()),
2452 expectedDecompressedSize
, &actualSize
)) {
2454 IOError(NS_ERROR_FILE_CORRUPTED
)
2456 "Could not decompress file contents, the file may be corrupt"));
2458 decompressed
.SetLength(actualSize
);
2459 return decompressed
;
2462 NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker
, nsIAsyncShutdownBlocker
,
2463 nsIAsyncShutdownCompletionCallback
);
2465 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetName(nsAString
& aName
) {
2466 aName
= u
"IOUtils Blocker ("_ns
;
2467 aName
.Append(PHASE_NAMES
[mPhase
]);
2473 NS_IMETHODIMP
IOUtilsShutdownBlocker::BlockShutdown(
2474 nsIAsyncShutdownClient
* aBarrierClient
) {
2475 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2476 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2478 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2480 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2483 auto state
= IOUtils::sState
.Lock();
2484 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2485 // If the previous blockers have already run, then the event queue is
2486 // already torn down and we have nothing to do.
2488 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2489 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2491 Unused
<< NS_WARN_IF(NS_FAILED(aBarrierClient
->RemoveBlocker(this)));
2492 mParentClient
= nullptr;
2497 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
2499 mParentClient
= aBarrierClient
;
2501 barrier
= state
->mEventQueue
->GetShutdownBarrier(mPhase
).unwrapOr(nullptr);
2504 // We cannot barrier->Wait() while holding the mutex because it will lead to
2506 if (!barrier
|| NS_WARN_IF(NS_FAILED(barrier
->Wait(this)))) {
2507 // If we don't have a barrier, we still need to flush the IOUtils event
2508 // queue and disable task submission.
2510 // Likewise, if waiting on the barrier failed, we are going to make our best
2511 // attempt to clean up.
2518 NS_IMETHODIMP
IOUtilsShutdownBlocker::Done() {
2519 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2520 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2522 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2524 bool didFlush
= false;
2527 auto state
= IOUtils::sState
.Lock();
2529 if (state
->mEventQueue
) {
2530 MOZ_RELEASE_ASSERT(state
->mQueueStatus
== EventQueueStatus::Initialized
);
2532 // This method is called once we have served all shutdown clients. Now we
2533 // flush the remaining IO queue. This ensures any straggling IO that was
2534 // not part of the shutdown blocker finishes before we move to the next
2536 state
->mEventQueue
->Dispatch
<Ok
>([]() { return Ok
{}; })
2537 ->Then(GetMainThreadSerialEventTarget(), __func__
,
2538 [self
= RefPtr(this)]() { self
->OnFlush(); });
2540 // And if we're the last shutdown phase to allow IO, disable the event
2541 // queue to disallow further IO requests.
2542 if (mPhase
>= LAST_IO_PHASE
) {
2543 state
->mQueueStatus
= EventQueueStatus::Shutdown
;
2550 // If we have already shut down the event loop, then call OnFlush to stop
2551 // blocking our parent shutdown client.
2553 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2560 void IOUtilsShutdownBlocker::OnFlush() {
2561 if (mParentClient
) {
2562 (void)NS_WARN_IF(NS_FAILED(mParentClient
->RemoveBlocker(this)));
2563 mParentClient
= nullptr;
2565 // If we are past the last shutdown phase that allows IO,
2566 // we can shutdown the event queue here because no additional IO requests
2567 // will be allowed (see |Done()|).
2568 if (mPhase
>= LAST_IO_PHASE
) {
2569 auto state
= IOUtils::sState
.Lock();
2570 if (state
->mEventQueue
) {
2571 state
->mEventQueue
= nullptr;
2577 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetState(nsIPropertyBag
** aState
) {
2581 Result
<IOUtils::InternalWriteOpts
, IOUtils::IOError
>
2582 IOUtils::InternalWriteOpts::FromBinding(const WriteOptions
& aOptions
) {
2583 InternalWriteOpts opts
;
2584 opts
.mFlush
= aOptions
.mFlush
;
2585 opts
.mMode
= aOptions
.mMode
;
2587 if (aOptions
.mBackupFile
.WasPassed()) {
2588 opts
.mBackupFile
= new nsLocalFile();
2589 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mBackupFile
,
2590 aOptions
.mBackupFile
.Value());
2592 return Err(IOUtils::IOError(rv
).WithMessage(
2593 "Could not parse path of backupFile (%s)",
2594 NS_ConvertUTF16toUTF8(aOptions
.mBackupFile
.Value()).get()));
2598 if (aOptions
.mTmpPath
.WasPassed()) {
2599 opts
.mTmpFile
= new nsLocalFile();
2600 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mTmpFile
,
2601 aOptions
.mTmpPath
.Value());
2603 return Err(IOUtils::IOError(rv
).WithMessage(
2604 "Could not parse path of temp file (%s)",
2605 NS_ConvertUTF16toUTF8(aOptions
.mTmpPath
.Value()).get()));
2609 opts
.mCompress
= aOptions
.mCompress
;
2614 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::JsBuffer::Create(
2615 IOUtils::BufferKind aBufferKind
, size_t aCapacity
) {
2616 JsBuffer
buffer(aBufferKind
, aCapacity
);
2617 if (aCapacity
!= 0 && !buffer
.mBuffer
) {
2618 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
2619 .WithMessage("Could not allocate buffer"));
2625 IOUtils::JsBuffer
IOUtils::JsBuffer::CreateEmpty(
2626 IOUtils::BufferKind aBufferKind
) {
2627 JsBuffer
buffer(aBufferKind
, 0);
2628 MOZ_RELEASE_ASSERT(buffer
.mBuffer
== nullptr);
2632 IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind
, size_t aCapacity
)
2633 : mBufferKind(aBufferKind
), mCapacity(aCapacity
), mLength(0) {
2635 if (aBufferKind
== BufferKind::String
) {
2636 mBuffer
= JS::UniqueChars(
2637 js_pod_arena_malloc
<char>(js::StringBufferArena
, mCapacity
));
2639 MOZ_RELEASE_ASSERT(aBufferKind
== BufferKind::Uint8Array
);
2640 mBuffer
= JS::UniqueChars(
2641 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, mCapacity
));
2646 IOUtils::JsBuffer::JsBuffer(IOUtils::JsBuffer
&& aOther
) noexcept
2647 : mBufferKind(aOther
.mBufferKind
),
2648 mCapacity(aOther
.mCapacity
),
2649 mLength(aOther
.mLength
),
2650 mBuffer(std::move(aOther
.mBuffer
)) {
2651 aOther
.mCapacity
= 0;
2655 IOUtils::JsBuffer
& IOUtils::JsBuffer::operator=(
2656 IOUtils::JsBuffer
&& aOther
) noexcept
{
2657 mBufferKind
= aOther
.mBufferKind
;
2658 mCapacity
= aOther
.mCapacity
;
2659 mLength
= aOther
.mLength
;
2660 mBuffer
= std::move(aOther
.mBuffer
);
2662 // Invalidate aOther.
2663 aOther
.mCapacity
= 0;
2670 JSString
* IOUtils::JsBuffer::IntoString(JSContext
* aCx
, JsBuffer aBuffer
) {
2671 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::String
);
2673 if (!aBuffer
.mCapacity
) {
2674 return JS_GetEmptyString(aCx
);
2677 if (IsAscii(aBuffer
.BeginReading())) {
2678 // If the string is just plain ASCII, then we can hand the buffer off to
2679 // JavaScript as a Latin1 string (since ASCII is a subset of Latin1).
2680 JS::UniqueLatin1Chars
asLatin1(
2681 reinterpret_cast<JS::Latin1Char
*>(aBuffer
.mBuffer
.release()));
2682 return JS_NewLatin1String(aCx
, std::move(asLatin1
), aBuffer
.mLength
);
2685 const char* ptr
= aBuffer
.mBuffer
.get();
2686 size_t length
= aBuffer
.mLength
;
2688 // Strip off a leading UTF-8 byte order marker (BOM) if found.
2689 if (length
>= 3 && Substring(ptr
, 3) == "\xEF\xBB\xBF"_ns
) {
2694 // If the string is encodable as Latin1, we need to deflate the string to a
2695 // Latin1 string to account for UTF-8 characters that are encoded as more than
2698 // Otherwise, the string contains characters outside Latin1 so we have to
2699 // inflate to UTF-16.
2700 return JS_NewStringCopyUTF8N(aCx
, JS::UTF8Chars(ptr
, length
));
2704 JSObject
* IOUtils::JsBuffer::IntoUint8Array(JSContext
* aCx
, JsBuffer aBuffer
) {
2705 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::Uint8Array
);
2707 if (!aBuffer
.mCapacity
) {
2708 return JS_NewUint8Array(aCx
, 0);
2711 MOZ_RELEASE_ASSERT(aBuffer
.mBuffer
);
2712 JS::Rooted
<JSObject
*> arrayBuffer(
2713 aCx
, JS::NewArrayBufferWithContents(aCx
, aBuffer
.mLength
,
2714 std::move(aBuffer
.mBuffer
)));
2717 // aBuffer will be destructed at end of scope, but its destructor does not
2718 // take into account |mCapacity| or |mLength|, so it is OK for them to be
2719 // non-zero here with a null |mBuffer|.
2723 return JS_NewUint8ArrayWithBuffer(aCx
, arrayBuffer
, 0, aBuffer
.mLength
);
2726 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, IOUtils::JsBuffer
&& aBuffer
,
2727 JS::MutableHandle
<JS::Value
> aValue
) {
2728 if (aBuffer
.mBufferKind
== IOUtils::BufferKind::String
) {
2729 JSString
* str
= IOUtils::JsBuffer::IntoString(aCx
, std::move(aBuffer
));
2734 aValue
.setString(str
);
2738 JSObject
* array
= IOUtils::JsBuffer::IntoUint8Array(aCx
, std::move(aBuffer
));
2743 aValue
.setObject(*array
);
2749 NS_IMPL_CYCLE_COLLECTING_ADDREF(SyncReadFile
)
2750 NS_IMPL_CYCLE_COLLECTING_RELEASE(SyncReadFile
)
2752 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SyncReadFile
)
2753 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
2754 NS_INTERFACE_MAP_ENTRY(nsISupports
)
2755 NS_INTERFACE_MAP_END
2757 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SyncReadFile
, mParent
)
2759 SyncReadFile::SyncReadFile(nsISupports
* aParent
,
2760 RefPtr
<nsFileRandomAccessStream
>&& aStream
,
2762 : mParent(aParent
), mStream(std::move(aStream
)), mSize(aSize
) {
2763 MOZ_RELEASE_ASSERT(mSize
>= 0);
2766 SyncReadFile::~SyncReadFile() = default;
2768 JSObject
* SyncReadFile::WrapObject(JSContext
* aCx
,
2769 JS::Handle
<JSObject
*> aGivenProto
) {
2770 return SyncReadFile_Binding::Wrap(aCx
, this, aGivenProto
);
2773 void SyncReadFile::ReadBytesInto(const Uint8Array
& aDestArray
,
2774 const int64_t aOffset
, ErrorResult
& aRv
) {
2776 return aRv
.ThrowOperationError("SyncReadFile is closed");
2779 aDestArray
.ProcessFixedData([&](const Span
<uint8_t>& aData
) {
2780 auto rangeEnd
= CheckedInt64(aOffset
) + aData
.Length();
2781 if (!rangeEnd
.isValid()) {
2782 return aRv
.ThrowOperationError("Requested range overflows i64");
2785 if (rangeEnd
.value() > mSize
) {
2786 return aRv
.ThrowOperationError(
2787 "Requested range overflows SyncReadFile size");
2790 size_t readLen
{aData
.Length()};
2795 if (nsresult rv
= mStream
->Seek(PR_SEEK_SET
, aOffset
); NS_FAILED(rv
)) {
2796 return aRv
.ThrowOperationError(
2797 FormatErrorMessage(rv
, "Could not seek to position %lld", aOffset
));
2800 Span
<char> toRead
= AsWritableChars(aData
);
2802 size_t totalRead
= 0;
2803 while (totalRead
!= readLen
) {
2804 // Read no more than INT32_MAX on each call to mStream->Read,
2805 // otherwise it returns an error.
2806 uint32_t bytesToReadThisChunk
=
2807 std::min(readLen
- totalRead
, size_t(INT32_MAX
));
2809 uint32_t bytesRead
= 0;
2810 if (nsresult rv
= mStream
->Read(toRead
.Elements(), bytesToReadThisChunk
,
2813 return aRv
.ThrowOperationError(FormatErrorMessage(
2814 rv
, "Encountered an unexpected error while reading file stream"));
2816 if (bytesRead
== 0) {
2817 return aRv
.ThrowOperationError(
2818 "Reading stopped before the entire array was filled");
2820 totalRead
+= bytesRead
;
2821 toRead
= toRead
.From(bytesRead
);
2826 void SyncReadFile::Close() { mStream
= nullptr; }
2831 static nsCString
FromUnixString(const IOUtils::UnixString
& aString
) {
2832 if (aString
.IsUTF8String()) {
2833 return aString
.GetAsUTF8String();
2835 if (aString
.IsUint8Array()) {
2837 Unused
<< aString
.GetAsUint8Array().AppendDataTo(data
);
2840 MOZ_CRASH("unreachable");
2846 uint32_t IOUtils::LaunchProcess(GlobalObject
& aGlobal
,
2847 const Sequence
<UnixString
>& aArgv
,
2848 const LaunchOptions
& aOptions
,
2850 // The binding is worker-only, so should always be off-main-thread.
2851 MOZ_ASSERT(!NS_IsMainThread());
2853 // This generally won't work in child processes due to sandboxing.
2854 AssertParentProcessWithCallerLocation(aGlobal
);
2856 std::vector
<std::string
> argv
;
2857 base::LaunchOptions options
;
2859 for (const auto& arg
: aArgv
) {
2860 argv
.push_back(FromUnixString(arg
).get());
2863 size_t envLen
= aOptions
.mEnvironment
.Length();
2864 base::EnvironmentArray
envp(new char*[envLen
+ 1]);
2865 for (size_t i
= 0; i
< envLen
; ++i
) {
2866 // EnvironmentArray is a UniquePtr instance which will `free`
2868 envp
[i
] = strdup(FromUnixString(aOptions
.mEnvironment
[i
]).get());
2870 envp
[envLen
] = nullptr;
2871 options
.full_env
= std::move(envp
);
2873 if (aOptions
.mWorkdir
.WasPassed()) {
2874 options
.workdir
= FromUnixString(aOptions
.mWorkdir
.Value()).get();
2877 if (aOptions
.mFdMap
.WasPassed()) {
2878 for (const auto& fdItem
: aOptions
.mFdMap
.Value()) {
2879 options
.fds_to_remap
.push_back({fdItem
.mSrc
, fdItem
.mDst
});
2884 options
.disclaim
= aOptions
.mDisclaim
;
2887 base::ProcessHandle pid
;
2888 static_assert(sizeof(pid
) <= sizeof(uint32_t),
2889 "WebIDL long should be large enough for a pid");
2890 Result
<Ok
, mozilla::ipc::LaunchError
> err
=
2891 base::LaunchApp(argv
, options
, &pid
);
2893 aRv
.Throw(NS_ERROR_FAILURE
);
2897 MOZ_ASSERT(pid
>= 0);
2898 return static_cast<uint32_t>(pid
);
2902 } // namespace mozilla::dom
2904 #undef REJECT_IF_INIT_PATH_FAILED