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 "js/ArrayBuffer.h"
14 #include "js/Utility.h"
15 #include "js/experimental/TypedData.h"
16 #include "jsfriendapi.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/CheckedInt.h"
20 #include "mozilla/Compression.h"
21 #include "mozilla/Encoding.h"
22 #include "mozilla/EndianUtils.h"
23 #include "mozilla/ErrorNames.h"
24 #include "mozilla/FileUtils.h"
25 #include "mozilla/Maybe.h"
26 #include "mozilla/ResultExtensions.h"
27 #include "mozilla/Services.h"
28 #include "mozilla/Span.h"
29 #include "mozilla/StaticPtr.h"
30 #include "mozilla/TextUtils.h"
31 #include "mozilla/Unused.h"
32 #include "mozilla/Utf8.h"
33 #include "mozilla/dom/BindingUtils.h"
34 #include "mozilla/dom/IOUtilsBinding.h"
35 #include "mozilla/dom/Promise.h"
36 #include "mozilla/dom/WorkerCommon.h"
37 #include "mozilla/dom/WorkerRef.h"
38 #include "PathUtils.h"
41 #include "nsFileStreams.h"
42 #include "nsIDirectoryEnumerator.h"
44 #include "nsIGlobalObject.h"
45 #include "nsIInputStream.h"
46 #include "nsISupports.h"
47 #include "nsLocalFile.h"
48 #include "nsNetUtil.h"
49 #include "nsNSSComponent.h"
50 #include "nsPrintfCString.h"
51 #include "nsReadableUtils.h"
53 #include "nsStringFwd.h"
55 #include "nsThreadManager.h"
56 #include "nsXULAppAPI.h"
61 #include "ScopedNSSTypes.h"
64 #if defined(XP_UNIX) && !defined(ANDROID)
65 # include "nsSystemInfo.h"
69 # include "nsILocalFileWin.h"
70 #elif defined(XP_MACOSX)
71 # include "nsILocalFileMac.h"
75 # include "base/process_util.h"
78 #define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise) \
80 if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
82 (_promise)->MaybeRejectWithOperationError( \
83 FormatErrorMessage(_rv, "Could not parse path (%s)", \
84 NS_ConvertUTF16toUTF8(_path).get())); \
89 static constexpr auto SHUTDOWN_ERROR
=
90 "IOUtils: Shutting down and refusing additional I/O tasks"_ns
;
92 namespace mozilla::dom
{
94 // static helper functions
97 * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
98 * report I/O errors inconsistently. For convenience, this function will attempt
99 * to match a |nsresult| against known results which imply a file cannot be
102 * @see nsLocalFileWin.cpp
103 * @see nsLocalFileUnix.cpp
105 static bool IsFileNotFound(nsresult aResult
) {
106 return aResult
== NS_ERROR_FILE_NOT_FOUND
;
109 * Like |IsFileNotFound|, but checks for known results that suggest a file
110 * is not a directory.
112 static bool IsNotDirectory(nsresult aResult
) {
113 return aResult
== NS_ERROR_FILE_DESTINATION_NOT_DIR
||
114 aResult
== NS_ERROR_FILE_NOT_DIRECTORY
;
118 * Formats an error message and appends the error name to the end.
120 template <typename
... Args
>
121 static nsCString
FormatErrorMessage(nsresult aError
, const char* const aMessage
,
123 nsPrintfCString
msg(aMessage
, aArgs
...);
125 if (const char* errName
= GetStaticErrorName(aError
)) {
126 msg
.AppendPrintf(": %s", errName
);
128 // In the exceptional case where there is no error name, print the literal
129 // integer value of the nsresult as an upper case hex value so it can be
130 // located easily in searchfox.
131 msg
.AppendPrintf(": 0x%" PRIX32
, static_cast<uint32_t>(aError
));
134 return std::move(msg
);
137 static nsCString
FormatErrorMessage(nsresult aError
,
138 const char* const aMessage
) {
139 const char* errName
= GetStaticErrorName(aError
);
141 return nsPrintfCString("%s: %s", aMessage
, errName
);
143 // In the exceptional case where there is no error name, print the literal
144 // integer value of the nsresult as an upper case hex value so it can be
145 // located easily in searchfox.
146 return nsPrintfCString("%s: 0x%" PRIX32
, aMessage
,
147 static_cast<uint32_t>(aError
));
150 [[nodiscard
]] inline bool ToJSValue(
151 JSContext
* aCx
, const IOUtils::InternalFileInfo
& aInternalFileInfo
,
152 JS::MutableHandle
<JS::Value
> aValue
) {
154 info
.mPath
.Construct(aInternalFileInfo
.mPath
);
155 info
.mType
.Construct(aInternalFileInfo
.mType
);
156 info
.mSize
.Construct(aInternalFileInfo
.mSize
);
158 if (aInternalFileInfo
.mCreationTime
.isSome()) {
159 info
.mCreationTime
.Construct(aInternalFileInfo
.mCreationTime
.ref());
161 info
.mLastAccessed
.Construct(aInternalFileInfo
.mLastAccessed
);
162 info
.mLastModified
.Construct(aInternalFileInfo
.mLastModified
);
164 info
.mPermissions
.Construct(aInternalFileInfo
.mPermissions
);
166 return ToJSValue(aCx
, info
, aValue
);
169 template <typename T
>
170 static void ResolveJSPromise(Promise
* aPromise
, T
&& aValue
) {
171 if constexpr (std::is_same_v
<T
, Ok
>) {
172 aPromise
->MaybeResolveWithUndefined();
173 } else if constexpr (std::is_same_v
<T
, nsTArray
<uint8_t>>) {
174 TypedArrayCreator
<Uint8Array
> array(aValue
);
175 aPromise
->MaybeResolve(array
);
177 aPromise
->MaybeResolve(std::forward
<T
>(aValue
));
181 static void RejectJSPromise(Promise
* aPromise
, const IOUtils::IOError
& aError
) {
182 const auto& errMsg
= aError
.Message();
184 switch (aError
.Code()) {
185 case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK
:
186 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
187 case NS_ERROR_FILE_NOT_FOUND
:
188 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
189 case NS_ERROR_FILE_INVALID_PATH
:
190 aPromise
->MaybeRejectWithNotFoundError(errMsg
.refOr("File not found"_ns
));
192 case NS_ERROR_FILE_IS_LOCKED
:
193 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
194 case NS_ERROR_FILE_ACCESS_DENIED
:
195 aPromise
->MaybeRejectWithNotAllowedError(
196 errMsg
.refOr("Access was denied to the target file"_ns
));
198 case NS_ERROR_FILE_TOO_BIG
:
199 aPromise
->MaybeRejectWithNotReadableError(
200 errMsg
.refOr("Target file is too big"_ns
));
202 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
203 aPromise
->MaybeRejectWithNotReadableError(
204 errMsg
.refOr("Target device is full"_ns
));
206 case NS_ERROR_FILE_ALREADY_EXISTS
:
207 aPromise
->MaybeRejectWithNoModificationAllowedError(
208 errMsg
.refOr("Target file already exists"_ns
));
210 case NS_ERROR_FILE_COPY_OR_MOVE_FAILED
:
211 aPromise
->MaybeRejectWithOperationError(
212 errMsg
.refOr("Failed to copy or move the target file"_ns
));
214 case NS_ERROR_FILE_READ_ONLY
:
215 aPromise
->MaybeRejectWithReadOnlyError(
216 errMsg
.refOr("Target file is read only"_ns
));
218 case NS_ERROR_FILE_NOT_DIRECTORY
:
219 [[fallthrough
]]; // to NS_ERROR_FILE_DESTINATION_NOT_DIR
220 case NS_ERROR_FILE_DESTINATION_NOT_DIR
:
221 aPromise
->MaybeRejectWithInvalidAccessError(
222 errMsg
.refOr("Target file is not a directory"_ns
));
224 case NS_ERROR_FILE_IS_DIRECTORY
:
225 aPromise
->MaybeRejectWithInvalidAccessError(
226 errMsg
.refOr("Target file is a directory"_ns
));
228 case NS_ERROR_FILE_UNKNOWN_TYPE
:
229 aPromise
->MaybeRejectWithInvalidAccessError(
230 errMsg
.refOr("Target file is of unknown type"_ns
));
232 case NS_ERROR_FILE_NAME_TOO_LONG
:
233 aPromise
->MaybeRejectWithOperationError(
234 errMsg
.refOr("Target file path is too long"_ns
));
236 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
237 aPromise
->MaybeRejectWithOperationError(
238 errMsg
.refOr("Target file path is not recognized"_ns
));
240 case NS_ERROR_FILE_DIR_NOT_EMPTY
:
241 aPromise
->MaybeRejectWithOperationError(
242 errMsg
.refOr("Target directory is not empty"_ns
));
244 case NS_ERROR_FILE_DEVICE_FAILURE
:
245 [[fallthrough
]]; // to NS_ERROR_FILE_FS_CORRUPTED
246 case NS_ERROR_FILE_FS_CORRUPTED
:
247 aPromise
->MaybeRejectWithNotReadableError(
248 errMsg
.refOr("Target file system may be corrupt or unavailable"_ns
));
250 case NS_ERROR_FILE_CORRUPTED
:
251 aPromise
->MaybeRejectWithNotReadableError(
252 errMsg
.refOr("Target file could not be read and may be corrupt"_ns
));
254 case NS_ERROR_ILLEGAL_INPUT
:
255 [[fallthrough
]]; // NS_ERROR_ILLEGAL_VALUE
256 case NS_ERROR_ILLEGAL_VALUE
:
257 aPromise
->MaybeRejectWithDataError(
258 errMsg
.refOr("Argument is not allowed"_ns
));
260 case NS_ERROR_NOT_AVAILABLE
:
261 aPromise
->MaybeRejectWithNotFoundError(errMsg
.refOr("Unavailable"_ns
));
264 aPromise
->MaybeRejectWithAbortError(errMsg
.refOr("Operation aborted"_ns
));
267 aPromise
->MaybeRejectWithUnknownError(FormatErrorMessage(
268 aError
.Code(), errMsg
.refOr("Unexpected error"_ns
).get()));
272 static void RejectShuttingDown(Promise
* aPromise
) {
273 RejectJSPromise(aPromise
,
274 IOUtils::IOError(NS_ERROR_ABORT
).WithMessage(SHUTDOWN_ERROR
));
277 static bool AssertParentProcessWithCallerLocationImpl(GlobalObject
& aGlobal
,
279 if (MOZ_LIKELY(XRE_IsParentProcess())) {
284 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
285 MOZ_ALWAYS_TRUE(global
);
286 MOZ_ALWAYS_TRUE(jsapi
.Init(global
));
288 JSContext
* cx
= jsapi
.cx();
290 JS::AutoFilename scriptFilename
;
295 JS::DescribeScriptedCaller(cx
, &scriptFilename
, &lineNo
, &colNo
), false);
297 NS_ENSURE_TRUE(scriptFilename
.get(), false);
299 reason
.AppendPrintf(" Called from %s:%d:%d.", scriptFilename
.get(), lineNo
,
304 static void AssertParentProcessWithCallerLocation(GlobalObject
& aGlobal
) {
305 nsCString reason
= "IOUtils can only be used in the parent process."_ns
;
306 if (!AssertParentProcessWithCallerLocationImpl(aGlobal
, reason
)) {
307 MOZ_CRASH_UNSAFE_PRINTF("%s", reason
.get());
311 // IOUtils implementation
313 IOUtils::StateMutex
IOUtils::sState
{"IOUtils::sState"};
316 template <typename Fn
>
317 already_AddRefed
<Promise
> IOUtils::WithPromiseAndState(GlobalObject
& aGlobal
,
320 AssertParentProcessWithCallerLocation(aGlobal
);
322 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
, aError
);
327 if (auto state
= GetState()) {
328 aFn(promise
, state
.ref());
330 RejectShuttingDown(promise
);
332 return promise
.forget();
336 template <typename OkT
, typename Fn
>
337 void IOUtils::DispatchAndResolve(IOUtils::EventQueue
* aQueue
, Promise
* aPromise
,
339 RefPtr
<StrongWorkerRef
> workerRef
;
340 if (!NS_IsMainThread()) {
341 // We need to manually keep the worker alive until the promise returned by
342 // Dispatch() resolves or rejects.
343 workerRef
= StrongWorkerRef::CreateForcibly(GetCurrentThreadWorkerPrivate(),
347 if (RefPtr
<IOPromise
<OkT
>> p
= aQueue
->Dispatch
<OkT
, Fn
>(std::move(aFunc
))) {
349 GetCurrentSerialEventTarget(), __func__
,
350 [workerRef
, promise
= RefPtr(aPromise
)](OkT
&& ok
) {
351 ResolveJSPromise(promise
, std::forward
<OkT
>(ok
));
353 [workerRef
, promise
= RefPtr(aPromise
)](const IOError
& err
) {
354 RejectJSPromise(promise
, err
);
360 already_AddRefed
<Promise
> IOUtils::Read(GlobalObject
& aGlobal
,
361 const nsAString
& aPath
,
362 const ReadOptions
& aOptions
,
363 ErrorResult
& aError
) {
364 return WithPromiseAndState(
365 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
366 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
367 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
369 Maybe
<uint32_t> toRead
= Nothing();
370 if (!aOptions
.mMaxBytes
.IsNull()) {
371 if (aOptions
.mMaxBytes
.Value() == 0) {
372 // Resolve with an empty buffer.
373 nsTArray
<uint8_t> arr(0);
374 promise
->MaybeResolve(TypedArrayCreator
<Uint8Array
>(arr
));
377 toRead
.emplace(aOptions
.mMaxBytes
.Value());
380 DispatchAndResolve
<JsBuffer
>(
381 state
->mEventQueue
, promise
,
382 [file
= std::move(file
), offset
= aOptions
.mOffset
, toRead
,
383 decompress
= aOptions
.mDecompress
]() {
384 return ReadSync(file
, offset
, toRead
, decompress
,
385 BufferKind::Uint8Array
);
391 RefPtr
<SyncReadFile
> IOUtils::OpenFileForSyncReading(GlobalObject
& aGlobal
,
392 const nsAString
& aPath
,
394 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
396 // This API is only exposed to workers, so we should not be on the main
398 MOZ_RELEASE_ASSERT(!NS_IsMainThread());
400 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
401 if (nsresult rv
= PathUtils::InitFileWithPath(file
, aPath
); NS_FAILED(rv
)) {
402 aRv
.ThrowOperationError(FormatErrorMessage(
403 rv
, "Could not parse path (%s)", NS_ConvertUTF16toUTF8(aPath
).get()));
407 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
409 stream
->Init(file
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
411 aRv
.ThrowOperationError(
412 FormatErrorMessage(rv
, "Could not open the file at %s",
413 NS_ConvertUTF16toUTF8(aPath
).get()));
418 if (nsresult rv
= stream
->GetSize(&size
); NS_FAILED(rv
)) {
419 aRv
.ThrowOperationError(FormatErrorMessage(
420 rv
, "Could not get the stream size for the file at %s",
421 NS_ConvertUTF16toUTF8(aPath
).get()));
425 return new SyncReadFile(aGlobal
.GetAsSupports(), std::move(stream
), size
);
429 already_AddRefed
<Promise
> IOUtils::ReadUTF8(GlobalObject
& aGlobal
,
430 const nsAString
& aPath
,
431 const ReadUTF8Options
& aOptions
,
432 ErrorResult
& aError
) {
433 return WithPromiseAndState(
434 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
435 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
436 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
438 DispatchAndResolve
<JsBuffer
>(
439 state
->mEventQueue
, promise
,
440 [file
= std::move(file
), decompress
= aOptions
.mDecompress
]() {
441 return ReadUTF8Sync(file
, decompress
);
447 already_AddRefed
<Promise
> IOUtils::ReadJSON(GlobalObject
& aGlobal
,
448 const nsAString
& aPath
,
449 const ReadUTF8Options
& aOptions
,
450 ErrorResult
& aError
) {
451 return WithPromiseAndState(
452 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
453 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
454 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
456 RefPtr
<StrongWorkerRef
> workerRef
;
457 if (!NS_IsMainThread()) {
458 // We need to manually keep the worker alive until the promise
459 // returned by Dispatch() resolves or rejects.
460 workerRef
= StrongWorkerRef::CreateForcibly(
461 GetCurrentThreadWorkerPrivate(), __func__
);
465 ->template Dispatch
<JsBuffer
>(
466 [file
, decompress
= aOptions
.mDecompress
]() {
467 return ReadUTF8Sync(file
, decompress
);
470 GetCurrentSerialEventTarget(), __func__
,
471 [workerRef
, promise
= RefPtr
{promise
},
472 file
](JsBuffer
&& aBuffer
) {
474 if (NS_WARN_IF(!jsapi
.Init(promise
->GetGlobalObject()))) {
475 promise
->MaybeRejectWithUnknownError(
476 "Could not initialize JS API");
479 JSContext
* cx
= jsapi
.cx();
481 JS::Rooted
<JSString
*> jsonStr(
483 IOUtils::JsBuffer::IntoString(cx
, std::move(aBuffer
)));
485 RejectJSPromise(promise
, IOError(NS_ERROR_OUT_OF_MEMORY
));
489 JS::Rooted
<JS::Value
> val(cx
);
490 if (!JS_ParseJSON(cx
, jsonStr
, &val
)) {
491 JS::Rooted
<JS::Value
> exn(cx
);
492 if (JS_GetPendingException(cx
, &exn
)) {
493 JS_ClearPendingException(cx
);
494 promise
->MaybeReject(exn
);
498 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
500 "ParseJSON threw an uncatchable exception "
501 "while parsing file(%s)",
502 file
->HumanReadablePath().get()));
508 promise
->MaybeResolve(val
);
510 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
511 RejectJSPromise(promise
, aErr
);
517 already_AddRefed
<Promise
> IOUtils::Write(GlobalObject
& aGlobal
,
518 const nsAString
& aPath
,
519 const Uint8Array
& aData
,
520 const WriteOptions
& aOptions
,
521 ErrorResult
& aError
) {
522 return WithPromiseAndState(
523 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
524 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
525 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
527 aData
.ComputeState();
529 Buffer
<uint8_t>::CopyFrom(Span(aData
.Data(), aData
.Length()));
530 if (buf
.isNothing()) {
531 promise
->MaybeRejectWithOperationError(
532 "Out of memory: Could not allocate buffer while writing to file");
536 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
538 RejectJSPromise(promise
, opts
.unwrapErr());
542 DispatchAndResolve
<uint32_t>(
543 state
->mEventQueue
, promise
,
544 [file
= std::move(file
), buf
= std::move(*buf
),
545 opts
= opts
.unwrap()]() { return WriteSync(file
, buf
, opts
); });
550 already_AddRefed
<Promise
> IOUtils::WriteUTF8(GlobalObject
& aGlobal
,
551 const nsAString
& aPath
,
552 const nsACString
& aString
,
553 const WriteOptions
& aOptions
,
554 ErrorResult
& aError
) {
555 return WithPromiseAndState(
556 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
557 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
558 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
560 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
562 RejectJSPromise(promise
, opts
.unwrapErr());
566 DispatchAndResolve
<uint32_t>(
567 state
->mEventQueue
, promise
,
568 [file
= std::move(file
), str
= nsCString(aString
),
569 opts
= opts
.unwrap()]() {
570 return WriteSync(file
, AsBytes(Span(str
)), opts
);
575 static bool AppendJsonAsUtf8(const char16_t
* aData
, uint32_t aLen
, void* aStr
) {
576 nsCString
* str
= static_cast<nsCString
*>(aStr
);
577 return AppendUTF16toUTF8(Span
<const char16_t
>(aData
, aLen
), *str
, fallible
);
581 already_AddRefed
<Promise
> IOUtils::WriteJSON(GlobalObject
& aGlobal
,
582 const nsAString
& aPath
,
583 JS::Handle
<JS::Value
> aValue
,
584 const WriteOptions
& aOptions
,
585 ErrorResult
& aError
) {
586 return WithPromiseAndState(
587 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
588 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
589 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
591 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
593 RejectJSPromise(promise
, opts
.unwrapErr());
597 if (opts
.inspect().mMode
== WriteMode::Append
||
598 opts
.inspect().mMode
== WriteMode::AppendOrCreate
) {
599 promise
->MaybeRejectWithNotSupportedError(
600 "IOUtils.writeJSON does not support appending to files."_ns
);
604 JSContext
* cx
= aGlobal
.Context();
605 JS::Rooted
<JS::Value
> rootedValue(cx
, aValue
);
608 if (!JS_Stringify(cx
, &rootedValue
, nullptr, JS::NullHandleValue
,
609 AppendJsonAsUtf8
, &utf8Str
)) {
610 JS::Rooted
<JS::Value
> exn(cx
, JS::UndefinedValue());
611 if (JS_GetPendingException(cx
, &exn
)) {
612 JS_ClearPendingException(cx
);
613 promise
->MaybeReject(exn
);
617 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
618 .WithMessage("Could not serialize object to JSON"));
623 DispatchAndResolve
<uint32_t>(
624 state
->mEventQueue
, promise
,
625 [file
= std::move(file
), utf8Str
= std::move(utf8Str
),
626 opts
= opts
.unwrap()]() {
627 return WriteSync(file
, AsBytes(Span(utf8Str
)), opts
);
633 already_AddRefed
<Promise
> IOUtils::Move(GlobalObject
& aGlobal
,
634 const nsAString
& aSourcePath
,
635 const nsAString
& aDestPath
,
636 const MoveOptions
& aOptions
,
637 ErrorResult
& aError
) {
638 return WithPromiseAndState(
639 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
640 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
641 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
643 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
644 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
646 DispatchAndResolve
<Ok
>(
647 state
->mEventQueue
, promise
,
648 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
649 noOverwrite
= aOptions
.mNoOverwrite
]() {
650 return MoveSync(sourceFile
, destFile
, noOverwrite
);
656 already_AddRefed
<Promise
> IOUtils::Remove(GlobalObject
& aGlobal
,
657 const nsAString
& aPath
,
658 const RemoveOptions
& aOptions
,
659 ErrorResult
& aError
) {
660 return WithPromiseAndState(
661 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
662 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
663 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
665 DispatchAndResolve
<Ok
>(
666 state
->mEventQueue
, promise
,
667 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
,
668 recursive
= aOptions
.mRecursive
,
669 retryReadonly
= aOptions
.mRetryReadonly
]() {
670 return RemoveSync(file
, ignoreAbsent
, recursive
, retryReadonly
);
676 already_AddRefed
<Promise
> IOUtils::MakeDirectory(
677 GlobalObject
& aGlobal
, const nsAString
& aPath
,
678 const MakeDirectoryOptions
& aOptions
, ErrorResult
& aError
) {
679 return WithPromiseAndState(
680 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
681 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
682 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
684 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
685 [file
= std::move(file
),
686 createAncestors
= aOptions
.mCreateAncestors
,
687 ignoreExisting
= aOptions
.mIgnoreExisting
,
688 permissions
= aOptions
.mPermissions
]() {
689 return MakeDirectorySync(file
, createAncestors
,
696 already_AddRefed
<Promise
> IOUtils::Stat(GlobalObject
& aGlobal
,
697 const nsAString
& aPath
,
698 ErrorResult
& aError
) {
699 return WithPromiseAndState(
700 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
701 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
702 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
704 DispatchAndResolve
<InternalFileInfo
>(
705 state
->mEventQueue
, promise
,
706 [file
= std::move(file
)]() { return StatSync(file
); });
711 already_AddRefed
<Promise
> IOUtils::Copy(GlobalObject
& aGlobal
,
712 const nsAString
& aSourcePath
,
713 const nsAString
& aDestPath
,
714 const CopyOptions
& aOptions
,
715 ErrorResult
& aError
) {
716 return WithPromiseAndState(
717 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
718 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
719 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
721 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
722 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
724 DispatchAndResolve
<Ok
>(
725 state
->mEventQueue
, promise
,
726 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
727 noOverwrite
= aOptions
.mNoOverwrite
,
728 recursive
= aOptions
.mRecursive
]() {
729 return CopySync(sourceFile
, destFile
, noOverwrite
, recursive
);
735 already_AddRefed
<Promise
> IOUtils::SetAccessTime(
736 GlobalObject
& aGlobal
, const nsAString
& aPath
,
737 const Optional
<int64_t>& aAccess
, ErrorResult
& aError
) {
738 return SetTime(aGlobal
, aPath
, aAccess
, &nsIFile::SetLastAccessedTime
,
743 already_AddRefed
<Promise
> IOUtils::SetModificationTime(
744 GlobalObject
& aGlobal
, const nsAString
& aPath
,
745 const Optional
<int64_t>& aModification
, ErrorResult
& aError
) {
746 return SetTime(aGlobal
, aPath
, aModification
, &nsIFile::SetLastModifiedTime
,
751 already_AddRefed
<Promise
> IOUtils::SetTime(GlobalObject
& aGlobal
,
752 const nsAString
& aPath
,
753 const Optional
<int64_t>& aNewTime
,
754 IOUtils::SetTimeFn aSetTimeFn
,
755 ErrorResult
& aError
) {
756 return WithPromiseAndState(
757 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
758 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
759 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
761 int64_t newTime
= aNewTime
.WasPassed() ? aNewTime
.Value()
762 : PR_Now() / PR_USEC_PER_MSEC
;
763 DispatchAndResolve
<int64_t>(
764 state
->mEventQueue
, promise
,
765 [file
= std::move(file
), aSetTimeFn
, newTime
]() {
766 return SetTimeSync(file
, aSetTimeFn
, newTime
);
772 already_AddRefed
<Promise
> IOUtils::GetChildren(
773 GlobalObject
& aGlobal
, const nsAString
& aPath
,
774 const GetChildrenOptions
& aOptions
, ErrorResult
& aError
) {
775 return WithPromiseAndState(
776 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
777 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
778 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
780 DispatchAndResolve
<nsTArray
<nsString
>>(
781 state
->mEventQueue
, promise
,
782 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
]() {
783 return GetChildrenSync(file
, ignoreAbsent
);
789 already_AddRefed
<Promise
> IOUtils::SetPermissions(GlobalObject
& aGlobal
,
790 const nsAString
& aPath
,
791 uint32_t aPermissions
,
792 const bool aHonorUmask
,
793 ErrorResult
& aError
) {
794 return WithPromiseAndState(
795 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
796 #if defined(XP_UNIX) && !defined(ANDROID)
798 aPermissions
&= ~nsSystemInfo::gUserUmask
;
802 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
803 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
805 DispatchAndResolve
<Ok
>(
806 state
->mEventQueue
, promise
,
807 [file
= std::move(file
), permissions
= aPermissions
]() {
808 return SetPermissionsSync(file
, permissions
);
814 already_AddRefed
<Promise
> IOUtils::Exists(GlobalObject
& aGlobal
,
815 const nsAString
& aPath
,
816 ErrorResult
& aError
) {
817 return WithPromiseAndState(
818 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
819 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
820 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
822 DispatchAndResolve
<bool>(
823 state
->mEventQueue
, promise
,
824 [file
= std::move(file
)]() { return ExistsSync(file
); });
829 already_AddRefed
<Promise
> IOUtils::CreateUniqueFile(GlobalObject
& aGlobal
,
830 const nsAString
& aParent
,
831 const nsAString
& aPrefix
,
832 const uint32_t aPermissions
,
833 ErrorResult
& aError
) {
834 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::NORMAL_FILE_TYPE
,
835 aPermissions
, aError
);
839 already_AddRefed
<Promise
> IOUtils::CreateUniqueDirectory(
840 GlobalObject
& aGlobal
, const nsAString
& aParent
, const nsAString
& aPrefix
,
841 const uint32_t aPermissions
, ErrorResult
& aError
) {
842 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::DIRECTORY_TYPE
,
843 aPermissions
, aError
);
847 already_AddRefed
<Promise
> IOUtils::CreateUnique(GlobalObject
& aGlobal
,
848 const nsAString
& aParent
,
849 const nsAString
& aPrefix
,
850 const uint32_t aFileType
,
851 const uint32_t aPermissions
,
852 ErrorResult
& aError
) {
853 return WithPromiseAndState(
854 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
855 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
856 REJECT_IF_INIT_PATH_FAILED(file
, aParent
, promise
);
858 if (nsresult rv
= file
->Append(aPrefix
); NS_FAILED(rv
)) {
859 RejectJSPromise(promise
,
860 IOError(rv
).WithMessage(
861 "Could not append prefix `%s' to parent `%s'",
862 NS_ConvertUTF16toUTF8(aPrefix
).get(),
863 file
->HumanReadablePath().get()));
867 DispatchAndResolve
<nsString
>(
868 state
->mEventQueue
, promise
,
869 [file
= std::move(file
), aPermissions
, aFileType
]() {
870 return CreateUniqueSync(file
, aFileType
, aPermissions
);
876 already_AddRefed
<Promise
> IOUtils::ComputeHexDigest(
877 GlobalObject
& aGlobal
, const nsAString
& aPath
,
878 const HashAlgorithm aAlgorithm
, ErrorResult
& aError
) {
879 const bool nssInitialized
= EnsureNSSInitializedChromeOrContent();
881 return WithPromiseAndState(
882 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
883 if (!nssInitialized
) {
884 RejectJSPromise(promise
,
885 IOError(NS_ERROR_UNEXPECTED
)
886 .WithMessage("Could not initialize NSS"));
890 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
891 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
893 DispatchAndResolve
<nsCString
>(state
->mEventQueue
, promise
,
894 [file
= std::move(file
), aAlgorithm
]() {
895 return ComputeHexDigestSync(file
,
904 already_AddRefed
<Promise
> IOUtils::GetWindowsAttributes(GlobalObject
& aGlobal
,
905 const nsAString
& aPath
,
906 ErrorResult
& aError
) {
907 return WithPromiseAndState(
908 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
909 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
910 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
912 RefPtr
<StrongWorkerRef
> workerRef
;
913 if (!NS_IsMainThread()) {
914 // We need to manually keep the worker alive until the promise
915 // returned by Dispatch() resolves or rejects.
916 workerRef
= StrongWorkerRef::CreateForcibly(
917 GetCurrentThreadWorkerPrivate(), __func__
);
921 ->template Dispatch
<uint32_t>([file
= std::move(file
)]() {
922 return GetWindowsAttributesSync(file
);
925 GetCurrentSerialEventTarget(), __func__
,
926 [workerRef
, promise
= RefPtr
{promise
}](const uint32_t aAttrs
) {
927 WindowsFileAttributes attrs
;
929 attrs
.mReadOnly
.Construct(aAttrs
& FILE_ATTRIBUTE_READONLY
);
930 attrs
.mHidden
.Construct(aAttrs
& FILE_ATTRIBUTE_HIDDEN
);
931 attrs
.mSystem
.Construct(aAttrs
& FILE_ATTRIBUTE_SYSTEM
);
933 promise
->MaybeResolve(attrs
);
935 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
936 RejectJSPromise(promise
, aErr
);
942 already_AddRefed
<Promise
> IOUtils::SetWindowsAttributes(
943 GlobalObject
& aGlobal
, const nsAString
& aPath
,
944 const WindowsFileAttributes
& aAttrs
, ErrorResult
& aError
) {
945 return WithPromiseAndState(
946 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
947 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
948 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
950 uint32_t setAttrs
= 0;
951 uint32_t clearAttrs
= 0;
953 if (aAttrs
.mReadOnly
.WasPassed()) {
954 if (aAttrs
.mReadOnly
.Value()) {
955 setAttrs
|= FILE_ATTRIBUTE_READONLY
;
957 clearAttrs
|= FILE_ATTRIBUTE_READONLY
;
961 if (aAttrs
.mHidden
.WasPassed()) {
962 if (aAttrs
.mHidden
.Value()) {
963 setAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
965 clearAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
969 if (aAttrs
.mSystem
.WasPassed()) {
970 if (aAttrs
.mSystem
.Value()) {
971 setAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
973 clearAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
977 DispatchAndResolve
<Ok
>(
978 state
->mEventQueue
, promise
,
979 [file
= std::move(file
), setAttrs
, clearAttrs
]() {
980 return SetWindowsAttributesSync(file
, setAttrs
, clearAttrs
);
985 #elif defined(XP_MACOSX)
988 already_AddRefed
<Promise
> IOUtils::HasMacXAttr(GlobalObject
& aGlobal
,
989 const nsAString
& aPath
,
990 const nsACString
& aAttr
,
991 ErrorResult
& aError
) {
992 return WithPromiseAndState(
993 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
994 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
995 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
997 DispatchAndResolve
<bool>(
998 state
->mEventQueue
, promise
,
999 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1000 return HasMacXAttrSync(file
, attr
);
1006 already_AddRefed
<Promise
> IOUtils::GetMacXAttr(GlobalObject
& aGlobal
,
1007 const nsAString
& aPath
,
1008 const nsACString
& aAttr
,
1009 ErrorResult
& aError
) {
1010 return WithPromiseAndState(
1011 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1012 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1013 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1015 DispatchAndResolve
<nsTArray
<uint8_t>>(
1016 state
->mEventQueue
, promise
,
1017 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1018 return GetMacXAttrSync(file
, attr
);
1024 already_AddRefed
<Promise
> IOUtils::SetMacXAttr(GlobalObject
& aGlobal
,
1025 const nsAString
& aPath
,
1026 const nsACString
& aAttr
,
1027 const Uint8Array
& aValue
,
1028 ErrorResult
& aError
) {
1029 return WithPromiseAndState(
1030 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1031 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1032 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1034 aValue
.ComputeState();
1035 nsTArray
<uint8_t> value
;
1037 if (!value
.AppendElements(aValue
.Data(), aValue
.Length(), fallible
)) {
1040 IOError(NS_ERROR_OUT_OF_MEMORY
)
1042 "Could not allocate buffer to set extended attribute"));
1046 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
1047 [file
= std::move(file
), attr
= nsCString(aAttr
),
1048 value
= std::move(value
)] {
1049 return SetMacXAttrSync(file
, attr
, value
);
1055 already_AddRefed
<Promise
> IOUtils::DelMacXAttr(GlobalObject
& aGlobal
,
1056 const nsAString
& aPath
,
1057 const nsACString
& aAttr
,
1058 ErrorResult
& aError
) {
1059 return WithPromiseAndState(
1060 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1061 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1062 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
1064 DispatchAndResolve
<Ok
>(
1065 state
->mEventQueue
, promise
,
1066 [file
= std::move(file
), attr
= nsCString(aAttr
)] {
1067 return DelMacXAttrSync(file
, attr
);
1075 already_AddRefed
<Promise
> IOUtils::GetFile(
1076 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1077 ErrorResult
& aError
) {
1078 return WithPromiseAndState(
1079 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1080 ErrorResult joinErr
;
1081 nsCOMPtr
<nsIFile
> file
= PathUtils::Join(aComponents
, joinErr
);
1082 if (joinErr
.Failed()) {
1083 promise
->MaybeReject(std::move(joinErr
));
1087 nsCOMPtr
<nsIFile
> parent
;
1088 if (nsresult rv
= file
->GetParent(getter_AddRefs(parent
));
1090 RejectJSPromise(promise
, IOError(rv
).WithMessage(
1091 "Could not get parent directory"));
1096 ->template Dispatch
<Ok
>([parent
= std::move(parent
)]() {
1097 return MakeDirectorySync(parent
, /* aCreateAncestors = */ true,
1098 /* aIgnoreExisting = */ true, 0755);
1101 GetCurrentSerialEventTarget(), __func__
,
1102 [file
= std::move(file
), promise
= RefPtr(promise
)](const Ok
&) {
1103 promise
->MaybeResolve(file
);
1105 [promise
= RefPtr(promise
)](const IOError
& err
) {
1106 RejectJSPromise(promise
, err
);
1112 already_AddRefed
<Promise
> IOUtils::GetDirectory(
1113 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1114 ErrorResult
& aError
) {
1115 return WithPromiseAndState(
1116 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1117 ErrorResult joinErr
;
1118 nsCOMPtr
<nsIFile
> dir
= PathUtils::Join(aComponents
, joinErr
);
1119 if (joinErr
.Failed()) {
1120 promise
->MaybeReject(std::move(joinErr
));
1125 ->template Dispatch
<Ok
>([dir
]() {
1126 return MakeDirectorySync(dir
, /* aCreateAncestors = */ true,
1127 /* aIgnoreExisting = */ true, 0755);
1130 GetCurrentSerialEventTarget(), __func__
,
1131 [dir
, promise
= RefPtr(promise
)](const Ok
&) {
1132 promise
->MaybeResolve(dir
);
1134 [promise
= RefPtr(promise
)](const IOError
& err
) {
1135 RejectJSPromise(promise
, err
);
1141 already_AddRefed
<Promise
> IOUtils::CreateJSPromise(GlobalObject
& aGlobal
,
1142 ErrorResult
& aError
) {
1143 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1144 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
1145 if (aError
.Failed()) {
1148 MOZ_ASSERT(promise
);
1149 return do_AddRef(promise
);
1153 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadSync(
1154 nsIFile
* aFile
, const uint64_t aOffset
, const Maybe
<uint32_t> aMaxBytes
,
1155 const bool aDecompress
, IOUtils::BufferKind aBufferKind
) {
1156 MOZ_ASSERT(!NS_IsMainThread());
1158 if (aMaxBytes
.isSome() && aDecompress
) {
1160 IOError(NS_ERROR_ILLEGAL_INPUT
)
1162 "The `maxBytes` and `decompress` options are not compatible"));
1165 if (aOffset
> static_cast<uint64_t>(INT64_MAX
)) {
1166 return Err(IOError(NS_ERROR_ILLEGAL_INPUT
)
1167 .WithMessage("Requested offset is too large (%" PRIu64
1169 aOffset
, INT64_MAX
));
1172 const int64_t offset
= static_cast<int64_t>(aOffset
);
1174 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
1176 stream
->Init(aFile
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
1178 return Err(IOError(rv
).WithMessage("Could not open the file at %s",
1179 aFile
->HumanReadablePath().get()));
1182 uint32_t bufSize
= 0;
1184 if (aMaxBytes
.isNothing()) {
1185 // Limitation: We cannot read more than the maximum size of a TypedArray
1186 // (UINT32_MAX bytes). Reject if we have been requested to
1187 // perform too large of a read.
1189 int64_t rawStreamSize
= -1;
1190 if (nsresult rv
= stream
->GetSize(&rawStreamSize
); NS_FAILED(rv
)) {
1191 return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED
)
1192 .WithMessage("Could not get info for the file at %s",
1193 aFile
->HumanReadablePath().get()));
1195 MOZ_RELEASE_ASSERT(rawStreamSize
>= 0);
1197 uint64_t streamSize
= static_cast<uint64_t>(rawStreamSize
);
1198 if (aOffset
>= streamSize
) {
1201 if (streamSize
- offset
> static_cast<int64_t>(UINT32_MAX
)) {
1202 return Err(IOError(NS_ERROR_FILE_TOO_BIG
)
1204 "Could not read the file at %s with offset %" PRIu32
1205 " because it is too large(size=%" PRIu64
" bytes)",
1206 aFile
->HumanReadablePath().get(), offset
,
1210 bufSize
= static_cast<uint32_t>(streamSize
- offset
);
1213 bufSize
= aMaxBytes
.value();
1217 if (nsresult rv
= stream
->Seek(PR_SEEK_SET
, offset
); NS_FAILED(rv
)) {
1218 return Err(IOError(rv
).WithMessage(
1219 "Could not seek to position %" PRId64
" in file %s", offset
,
1220 aFile
->HumanReadablePath().get()));
1224 JsBuffer buffer
= JsBuffer::CreateEmpty(aBufferKind
);
1227 auto result
= JsBuffer::Create(aBufferKind
, bufSize
);
1228 if (result
.isErr()) {
1229 return result
.propagateErr();
1231 buffer
= result
.unwrap();
1232 Span
<char> toRead
= buffer
.BeginWriting();
1234 // Read the file from disk.
1235 uint32_t totalRead
= 0;
1236 while (totalRead
!= bufSize
) {
1237 // Read no more than INT32_MAX on each call to stream->Read, otherwise it
1238 // returns an error.
1239 uint32_t bytesToReadThisChunk
=
1240 std::min
<uint32_t>(bufSize
- totalRead
, INT32_MAX
);
1241 uint32_t bytesRead
= 0;
1243 stream
->Read(toRead
.Elements(), bytesToReadThisChunk
, &bytesRead
);
1245 return Err(IOError(rv
).WithMessage(
1246 "Encountered an unexpected error while reading file(%s)",
1247 aFile
->HumanReadablePath().get()));
1249 if (bytesRead
== 0) {
1252 totalRead
+= bytesRead
;
1253 toRead
= toRead
.From(bytesRead
);
1256 buffer
.SetLength(totalRead
);
1259 // Decompress the file contents, if required.
1261 return MozLZ4::Decompress(AsBytes(buffer
.BeginReading()), aBufferKind
);
1264 return std::move(buffer
);
1268 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadUTF8Sync(
1269 nsIFile
* aFile
, bool aDecompress
) {
1270 auto result
= ReadSync(aFile
, 0, Nothing
{}, aDecompress
, BufferKind::String
);
1271 if (result
.isErr()) {
1272 return result
.propagateErr();
1275 JsBuffer buffer
= result
.unwrap();
1276 if (!IsUtf8(buffer
.BeginReading())) {
1278 IOError(NS_ERROR_FILE_CORRUPTED
)
1280 "Could not read file(%s) because it is not UTF-8 encoded",
1281 aFile
->HumanReadablePath().get()));
1288 Result
<uint32_t, IOUtils::IOError
> IOUtils::WriteSync(
1289 nsIFile
* aFile
, const Span
<const uint8_t>& aByteArray
,
1290 const IOUtils::InternalWriteOpts
& aOptions
) {
1291 MOZ_ASSERT(!NS_IsMainThread());
1293 nsIFile
* backupFile
= aOptions
.mBackupFile
;
1294 nsIFile
* tempFile
= aOptions
.mTmpFile
;
1296 bool exists
= false;
1297 MOZ_TRY(aFile
->Exists(&exists
));
1299 if (exists
&& aOptions
.mMode
== WriteMode::Create
) {
1300 return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS
)
1301 .WithMessage("Refusing to overwrite the file at %s\n"
1302 "Specify `mode: \"overwrite\"` to allow "
1303 "overwriting the destination",
1304 aFile
->HumanReadablePath().get()));
1307 // If backupFile was specified, perform the backup as a move.
1308 if (exists
&& backupFile
) {
1309 // We copy `destFile` here to a new `nsIFile` because
1310 // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we
1311 // did not do this, we would end up having `destFile` point to the same
1312 // location as `backupFile`. Then, when we went to write to `destFile`, we
1313 // would end up overwriting `backupFile` and never actually write to the
1314 // file we were supposed to.
1315 nsCOMPtr
<nsIFile
> toMove
;
1316 MOZ_ALWAYS_SUCCEEDS(aFile
->Clone(getter_AddRefs(toMove
)));
1318 bool noOverwrite
= aOptions
.mMode
== WriteMode::Create
;
1320 if (MoveSync(toMove
, backupFile
, noOverwrite
).isErr()) {
1321 return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1322 .WithMessage("Failed to backup the source file(%s) to %s",
1323 aFile
->HumanReadablePath().get(),
1324 backupFile
->HumanReadablePath().get()));
1328 // If tempFile was specified, we will write to there first, then perform a
1329 // move to ensure the file ends up at the final requested destination.
1333 writeFile
= tempFile
;
1338 int32_t flags
= PR_WRONLY
;
1340 switch (aOptions
.mMode
) {
1341 case WriteMode::Overwrite
:
1342 flags
|= PR_TRUNCATE
| PR_CREATE_FILE
;
1345 case WriteMode::Append
:
1349 case WriteMode::AppendOrCreate
:
1350 flags
|= PR_APPEND
| PR_CREATE_FILE
;
1353 case WriteMode::Create
:
1354 flags
|= PR_CREATE_FILE
| PR_EXCL
;
1358 MOZ_CRASH("IOUtils: unknown write mode");
1361 if (aOptions
.mFlush
) {
1365 // Try to perform the write and ensure that the file is closed before
1367 uint32_t totalWritten
= 0;
1369 // Compress the byte array if required.
1370 nsTArray
<uint8_t> compressed
;
1371 Span
<const char> bytes
;
1372 if (aOptions
.mCompress
) {
1373 auto rv
= MozLZ4::Compress(aByteArray
);
1375 return rv
.propagateErr();
1377 compressed
= rv
.unwrap();
1378 bytes
= Span(reinterpret_cast<const char*>(compressed
.Elements()),
1379 compressed
.Length());
1381 bytes
= Span(reinterpret_cast<const char*>(aByteArray
.Elements()),
1382 aByteArray
.Length());
1385 RefPtr
<nsFileOutputStream
> stream
= new nsFileOutputStream();
1386 if (nsresult rv
= stream
->Init(writeFile
, flags
, 0666, 0); NS_FAILED(rv
)) {
1387 // Normalize platform-specific errors for opening a directory to an access
1389 if (rv
== nsresult::NS_ERROR_FILE_IS_DIRECTORY
) {
1390 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
1393 IOError(rv
).WithMessage("Could not open the file at %s for writing",
1394 writeFile
->HumanReadablePath().get()));
1397 // nsFileRandomAccessStream::Write uses PR_Write under the hood, which
1398 // accepts a *int32_t* for the chunk size.
1399 uint32_t chunkSize
= INT32_MAX
;
1400 Span
<const char> pendingBytes
= bytes
;
1402 while (pendingBytes
.Length() > 0) {
1403 if (pendingBytes
.Length() < chunkSize
) {
1404 chunkSize
= pendingBytes
.Length();
1407 uint32_t bytesWritten
= 0;
1409 stream
->Write(pendingBytes
.Elements(), chunkSize
, &bytesWritten
);
1411 return Err(IOError(rv
).WithMessage(
1412 "Could not write chunk (size = %" PRIu32
1413 ") to file %s. The file may be corrupt.",
1414 chunkSize
, writeFile
->HumanReadablePath().get()));
1416 pendingBytes
= pendingBytes
.From(bytesWritten
);
1417 totalWritten
+= bytesWritten
;
1421 // If tempFile was passed, check destFile against writeFile and, if they
1422 // differ, the operation is finished by performing a move.
1424 nsAutoStringN
<256> destPath
;
1425 nsAutoStringN
<256> writePath
;
1427 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(destPath
));
1428 MOZ_ALWAYS_SUCCEEDS(writeFile
->GetPath(writePath
));
1430 // nsIFile::MoveToFollowingLinks will only update the path of the file if
1431 // the move succeeds.
1432 if (destPath
!= writePath
) {
1433 if (aOptions
.mTmpFile
) {
1435 if (nsresult rv
= aFile
->IsDirectory(&isDir
);
1436 NS_FAILED(rv
) && !IsFileNotFound(rv
)) {
1437 return Err(IOError(rv
).WithMessage("Could not stat the file at %s",
1438 aFile
->HumanReadablePath().get()));
1441 // If we attempt to write to a directory *without* a temp file, we get a
1442 // permission error.
1444 // However, if we are writing to a temp file first, when we copy the
1445 // temp file over the destination file, we actually end up copying it
1446 // inside the directory, which is not what we want. In this case, we are
1447 // just going to bail out early.
1450 IOError(NS_ERROR_FILE_ACCESS_DENIED
)
1451 .WithMessage("Could not open the file at %s for writing",
1452 aFile
->HumanReadablePath().get()));
1456 if (MoveSync(writeFile
, aFile
, /* aNoOverwrite = */ false).isErr()) {
1458 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1460 "Could not move temporary file(%s) to destination(%s)",
1461 writeFile
->HumanReadablePath().get(),
1462 aFile
->HumanReadablePath().get()));
1466 return totalWritten
;
1470 Result
<Ok
, IOUtils::IOError
> IOUtils::MoveSync(nsIFile
* aSourceFile
,
1472 bool aNoOverwrite
) {
1473 MOZ_ASSERT(!NS_IsMainThread());
1475 // Ensure the source file exists before continuing. If it doesn't exist,
1476 // subsequent operations can fail in different ways on different platforms.
1477 bool srcExists
= false;
1478 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1481 IOError(NS_ERROR_FILE_NOT_FOUND
)
1483 "Could not move source file(%s) because it does not exist",
1484 aSourceFile
->HumanReadablePath().get()));
1487 return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks
, "move", aSourceFile
,
1488 aDestFile
, aNoOverwrite
);
1492 Result
<Ok
, IOUtils::IOError
> IOUtils::CopySync(nsIFile
* aSourceFile
,
1496 MOZ_ASSERT(!NS_IsMainThread());
1498 // Ensure the source file exists before continuing. If it doesn't exist,
1499 // subsequent operations can fail in different ways on different platforms.
1501 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1504 IOError(NS_ERROR_FILE_NOT_FOUND
)
1506 "Could not copy source file(%s) because it does not exist",
1507 aSourceFile
->HumanReadablePath().get()));
1510 // If source is a directory, fail immediately unless the recursive option is
1512 bool srcIsDir
= false;
1513 MOZ_TRY(aSourceFile
->IsDirectory(&srcIsDir
));
1514 if (srcIsDir
&& !aRecursive
) {
1516 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1518 "Refused to copy source directory(%s) to the destination(%s)\n"
1519 "Specify the `recursive: true` option to allow copying "
1521 aSourceFile
->HumanReadablePath().get(),
1522 aDestFile
->HumanReadablePath().get()));
1525 return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks
, "copy", aSourceFile
,
1526 aDestFile
, aNoOverwrite
);
1530 template <typename CopyOrMoveFn
>
1531 Result
<Ok
, IOUtils::IOError
> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod
,
1532 const char* aMethodName
,
1535 bool aNoOverwrite
) {
1536 MOZ_ASSERT(!NS_IsMainThread());
1538 // Case 1: Destination is an existing directory. Copy/move source into dest.
1539 bool destIsDir
= false;
1540 bool destExists
= true;
1542 nsresult rv
= aDest
->IsDirectory(&destIsDir
);
1543 if (NS_SUCCEEDED(rv
) && destIsDir
) {
1544 rv
= (aSource
->*aMethod
)(aDest
, u
""_ns
);
1545 if (NS_FAILED(rv
)) {
1546 return Err(IOError(rv
).WithMessage(
1547 "Could not %s source file(%s) to destination directory(%s)",
1548 aMethodName
, aSource
->HumanReadablePath().get(),
1549 aDest
->HumanReadablePath().get()));
1554 if (NS_FAILED(rv
)) {
1555 if (!IsFileNotFound(rv
)) {
1556 // It's ok if the dest file doesn't exist. Case 2 handles this below.
1557 // Bail out early for any other kind of error though.
1558 return Err(IOError(rv
));
1563 // Case 2: Destination is a file which may or may not exist.
1564 // Try to copy or rename the source to the destination.
1565 // If the destination exists and the source is not a regular file,
1566 // then this may fail.
1567 if (aNoOverwrite
&& destExists
) {
1569 IOError(NS_ERROR_FILE_ALREADY_EXISTS
)
1571 "Could not %s source file(%s) to destination(%s) because the "
1572 "destination already exists and overwrites are not allowed\n"
1573 "Specify the `noOverwrite: false` option to mitigate this "
1575 aMethodName
, aSource
->HumanReadablePath().get(),
1576 aDest
->HumanReadablePath().get()));
1578 if (destExists
&& !destIsDir
) {
1579 // If the source file is a directory, but the target is a file, abort early.
1580 // Different implementations of |CopyTo| and |MoveTo| seem to handle this
1581 // error case differently (or not at all), so we explicitly handle it here.
1582 bool srcIsDir
= false;
1583 MOZ_TRY(aSource
->IsDirectory(&srcIsDir
));
1585 return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR
)
1586 .WithMessage("Could not %s the source directory(%s) to "
1587 "the destination(%s) because the destination "
1588 "is not a directory",
1590 aSource
->HumanReadablePath().get(),
1591 aDest
->HumanReadablePath().get()));
1595 nsCOMPtr
<nsIFile
> destDir
;
1596 nsAutoString destName
;
1597 MOZ_TRY(aDest
->GetLeafName(destName
));
1598 MOZ_TRY(aDest
->GetParent(getter_AddRefs(destDir
)));
1600 // We know `destName` is a file and therefore must have a parent directory.
1601 MOZ_RELEASE_ASSERT(destDir
);
1603 // NB: if destDir doesn't exist, then |CopyToFollowingLinks| or
1604 // |MoveToFollowingLinks| will create it.
1605 rv
= (aSource
->*aMethod
)(destDir
, destName
);
1606 if (NS_FAILED(rv
)) {
1607 return Err(IOError(rv
).WithMessage(
1608 "Could not %s the source file(%s) to the destination(%s)", aMethodName
,
1609 aSource
->HumanReadablePath().get(), aDest
->HumanReadablePath().get()));
1615 Result
<Ok
, IOUtils::IOError
> IOUtils::RemoveSync(nsIFile
* aFile
,
1618 bool aRetryReadonly
) {
1619 MOZ_ASSERT(!NS_IsMainThread());
1621 // Prevent an unused variable warning.
1622 (void)aRetryReadonly
;
1624 nsresult rv
= aFile
->Remove(aRecursive
);
1625 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1628 if (NS_FAILED(rv
)) {
1630 if (IsFileNotFound(rv
)) {
1631 return Err(err
.WithMessage(
1632 "Could not remove the file at %s because it does not exist.\n"
1633 "Specify the `ignoreAbsent: true` option to mitigate this error",
1634 aFile
->HumanReadablePath().get()));
1636 if (rv
== NS_ERROR_FILE_DIR_NOT_EMPTY
) {
1637 return Err(err
.WithMessage(
1638 "Could not remove the non-empty directory at %s.\n"
1639 "Specify the `recursive: true` option to mitigate this error",
1640 aFile
->HumanReadablePath().get()));
1645 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
&& aRetryReadonly
) {
1646 MOZ_TRY(SetWindowsAttributesSync(aFile
, 0, FILE_ATTRIBUTE_READONLY
));
1647 return RemoveSync(aFile
, aIgnoreAbsent
, aRecursive
,
1648 /* aRetryReadonly = */ false);
1653 return Err(err
.WithMessage("Could not remove the file at %s",
1654 aFile
->HumanReadablePath().get()));
1660 Result
<Ok
, IOUtils::IOError
> IOUtils::MakeDirectorySync(nsIFile
* aFile
,
1661 bool aCreateAncestors
,
1662 bool aIgnoreExisting
,
1664 MOZ_ASSERT(!NS_IsMainThread());
1666 nsCOMPtr
<nsIFile
> parent
;
1667 MOZ_TRY(aFile
->GetParent(getter_AddRefs(parent
)));
1669 // If we don't have a parent directory, we were called with a
1670 // root directory. If the directory doesn't already exist (e.g., asking
1671 // for a drive on Windows that does not exist), we will not be able to
1674 // Calling `nsLocalFile::Create()` on Windows can fail with
1675 // `NS_ERROR_ACCESS_DENIED` trying to create a root directory, but we
1676 // would rather the call succeed, so return early if the directory exists.
1678 // Otherwise, we fall through to `nsiFile::Create()` and let it fail there
1680 bool exists
= false;
1681 MOZ_TRY(aFile
->Exists(&exists
));
1688 aFile
->Create(nsIFile::DIRECTORY_TYPE
, aMode
, !aCreateAncestors
);
1689 if (NS_FAILED(rv
)) {
1690 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1691 // NB: We may report a success only if the target is an existing
1692 // directory. We don't want to silence errors that occur if the target is
1693 // an existing file, since trying to create a directory where a regular
1694 // file exists may be indicative of a logic error.
1696 MOZ_TRY(aFile
->IsDirectory(&isDirectory
));
1698 return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY
)
1699 .WithMessage("Could not create directory because the "
1700 "target file(%s) exists "
1701 "and is not a directory",
1702 aFile
->HumanReadablePath().get()));
1704 // The directory exists.
1705 // The caller may suppress this error.
1706 if (aIgnoreExisting
) {
1709 // Otherwise, forward it.
1710 return Err(IOError(rv
).WithMessage(
1711 "Could not create directory because it already exists at %s\n"
1712 "Specify the `ignoreExisting: true` option to mitigate this "
1714 aFile
->HumanReadablePath().get()));
1716 return Err(IOError(rv
).WithMessage("Could not create directory at %s",
1717 aFile
->HumanReadablePath().get()));
1722 Result
<IOUtils::InternalFileInfo
, IOUtils::IOError
> IOUtils::StatSync(
1724 MOZ_ASSERT(!NS_IsMainThread());
1726 InternalFileInfo info
;
1727 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(info
.mPath
));
1729 bool isRegular
= false;
1730 // IsFile will stat and cache info in the file object. If the file doesn't
1731 // exist, or there is an access error, we'll discover it here.
1732 // Any subsequent errors are unexpected and will just be forwarded.
1733 nsresult rv
= aFile
->IsFile(&isRegular
);
1734 if (NS_FAILED(rv
)) {
1736 if (IsFileNotFound(rv
)) {
1738 err
.WithMessage("Could not stat file(%s) because it does not exist",
1739 aFile
->HumanReadablePath().get()));
1744 // Now we can populate the info object by querying the file.
1745 info
.mType
= FileType::Regular
;
1748 MOZ_TRY(aFile
->IsDirectory(&isDir
));
1749 info
.mType
= isDir
? FileType::Directory
: FileType::Other
;
1753 if (info
.mType
== FileType::Regular
) {
1754 MOZ_TRY(aFile
->GetFileSize(&size
));
1758 PRTime creationTime
= 0;
1759 if (nsresult rv
= aFile
->GetCreationTime(&creationTime
); NS_SUCCEEDED(rv
)) {
1760 info
.mCreationTime
.emplace(static_cast<int64_t>(creationTime
));
1761 } else if (NS_FAILED(rv
) && rv
!= NS_ERROR_NOT_IMPLEMENTED
) {
1762 // This field is only supported on some platforms.
1763 return Err(IOError(rv
));
1766 PRTime lastAccessed
= 0;
1767 MOZ_TRY(aFile
->GetLastAccessedTime(&lastAccessed
));
1768 info
.mLastAccessed
= static_cast<int64_t>(lastAccessed
);
1770 PRTime lastModified
= 0;
1771 MOZ_TRY(aFile
->GetLastModifiedTime(&lastModified
));
1772 info
.mLastModified
= static_cast<int64_t>(lastModified
);
1774 MOZ_TRY(aFile
->GetPermissions(&info
.mPermissions
));
1780 Result
<int64_t, IOUtils::IOError
> IOUtils::SetTimeSync(
1781 nsIFile
* aFile
, IOUtils::SetTimeFn aSetTimeFn
, int64_t aNewTime
) {
1782 MOZ_ASSERT(!NS_IsMainThread());
1784 // nsIFile::SetLastModifiedTime will *not* do what is expected when passed 0
1785 // as an argument. Rather than setting the time to 0, it will recalculate the
1786 // system time and set it to that value instead. We explicit forbid this,
1787 // because this side effect is surprising.
1789 // If it ever becomes possible to set a file time to 0, this check should be
1790 // removed, though this use case seems rare.
1791 if (aNewTime
== 0) {
1793 IOError(NS_ERROR_ILLEGAL_VALUE
)
1795 "Refusing to set the modification time of file(%s) to 0.\n"
1796 "To use the current system time, call `setModificationTime` "
1797 "with no arguments",
1798 aFile
->HumanReadablePath().get()));
1801 nsresult rv
= (aFile
->*aSetTimeFn
)(aNewTime
);
1803 if (NS_FAILED(rv
)) {
1805 if (IsFileNotFound(rv
)) {
1807 err
.WithMessage("Could not set modification time of file(%s) "
1808 "because it does not exist",
1809 aFile
->HumanReadablePath().get()));
1817 Result
<nsTArray
<nsString
>, IOUtils::IOError
> IOUtils::GetChildrenSync(
1818 nsIFile
* aFile
, bool aIgnoreAbsent
) {
1819 MOZ_ASSERT(!NS_IsMainThread());
1821 nsTArray
<nsString
> children
;
1822 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
1823 nsresult rv
= aFile
->GetDirectoryEntries(getter_AddRefs(iter
));
1824 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1827 if (NS_FAILED(rv
)) {
1829 if (IsFileNotFound(rv
)) {
1830 return Err(err
.WithMessage(
1831 "Could not get children of file(%s) because it does not exist",
1832 aFile
->HumanReadablePath().get()));
1834 if (IsNotDirectory(rv
)) {
1835 return Err(err
.WithMessage(
1836 "Could not get children of file(%s) because it is not a directory",
1837 aFile
->HumanReadablePath().get()));
1842 bool hasMoreElements
= false;
1843 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1844 while (hasMoreElements
) {
1845 nsCOMPtr
<nsIFile
> child
;
1846 MOZ_TRY(iter
->GetNextFile(getter_AddRefs(child
)));
1849 MOZ_TRY(child
->GetPath(path
));
1850 children
.AppendElement(path
);
1852 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1859 Result
<Ok
, IOUtils::IOError
> IOUtils::SetPermissionsSync(
1860 nsIFile
* aFile
, const uint32_t aPermissions
) {
1861 MOZ_ASSERT(!NS_IsMainThread());
1863 MOZ_TRY(aFile
->SetPermissions(aPermissions
));
1868 Result
<bool, IOUtils::IOError
> IOUtils::ExistsSync(nsIFile
* aFile
) {
1869 MOZ_ASSERT(!NS_IsMainThread());
1871 bool exists
= false;
1872 MOZ_TRY(aFile
->Exists(&exists
));
1878 Result
<nsString
, IOUtils::IOError
> IOUtils::CreateUniqueSync(
1879 nsIFile
* aFile
, const uint32_t aFileType
, const uint32_t aPermissions
) {
1880 MOZ_ASSERT(!NS_IsMainThread());
1882 if (nsresult rv
= aFile
->CreateUnique(aFileType
, aPermissions
);
1884 return Err(IOError(rv
).WithMessage("Could not create unique path"));
1888 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(path
));
1894 Result
<nsCString
, IOUtils::IOError
> IOUtils::ComputeHexDigestSync(
1895 nsIFile
* aFile
, const HashAlgorithm aAlgorithm
) {
1896 static constexpr size_t BUFFER_SIZE
= 8192;
1899 switch (aAlgorithm
) {
1900 case HashAlgorithm::Sha1
:
1904 case HashAlgorithm::Sha256
:
1905 alg
= SEC_OID_SHA256
;
1908 case HashAlgorithm::Sha384
:
1909 alg
= SEC_OID_SHA384
;
1912 case HashAlgorithm::Sha512
:
1913 alg
= SEC_OID_SHA512
;
1917 MOZ_RELEASE_ASSERT(false, "Unexpected HashAlgorithm");
1921 if (nsresult rv
= digest
.Begin(alg
); NS_FAILED(rv
)) {
1922 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1923 aFile
->HumanReadablePath().get()));
1926 RefPtr
<nsIInputStream
> stream
;
1927 if (nsresult rv
= NS_NewLocalFileInputStream(getter_AddRefs(stream
), aFile
);
1929 return Err(IOError(rv
).WithMessage("Could not open the file at %s",
1930 aFile
->HumanReadablePath().get()));
1933 char buffer
[BUFFER_SIZE
];
1936 if (nsresult rv
= stream
->Read(buffer
, BUFFER_SIZE
, &read
); NS_FAILED(rv
)) {
1937 return Err(IOError(rv
).WithMessage(
1938 "Encountered an unexpected error while reading file(%s)",
1939 aFile
->HumanReadablePath().get()));
1946 digest
.Update(reinterpret_cast<unsigned char*>(buffer
), read
);
1948 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1949 aFile
->HumanReadablePath().get()));
1953 AutoTArray
<uint8_t, SHA512_LENGTH
> rawDigest
;
1954 if (nsresult rv
= digest
.End(rawDigest
); NS_FAILED(rv
)) {
1955 return Err(IOError(rv
).WithMessage("Could not hash file at %s",
1956 aFile
->HumanReadablePath().get()));
1959 nsCString hexDigest
;
1960 if (!hexDigest
.SetCapacity(2 * rawDigest
.Length(), fallible
)) {
1961 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
));
1964 const char HEX
[] = "0123456789abcdef";
1965 for (uint8_t b
: rawDigest
) {
1966 hexDigest
.Append(HEX
[(b
>> 4) & 0xF]);
1967 hexDigest
.Append(HEX
[b
& 0xF]);
1975 Result
<uint32_t, IOUtils::IOError
> IOUtils::GetWindowsAttributesSync(
1977 MOZ_ASSERT(!NS_IsMainThread());
1981 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
1984 if (nsresult rv
= file
->GetWindowsFileAttributes(&attrs
); NS_FAILED(rv
)) {
1985 return Err(IOError(rv
).WithMessage(
1986 "Could not get Windows file attributes for the file at `%s'",
1987 aFile
->HumanReadablePath().get()));
1992 Result
<Ok
, IOUtils::IOError
> IOUtils::SetWindowsAttributesSync(
1993 nsIFile
* aFile
, const uint32_t aSetAttrs
, const uint32_t aClearAttrs
) {
1994 MOZ_ASSERT(!NS_IsMainThread());
1996 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
1999 if (nsresult rv
= file
->SetWindowsFileAttributes(aSetAttrs
, aClearAttrs
);
2001 return Err(IOError(rv
).WithMessage(
2002 "Could not set Windows file attributes for the file at `%s'",
2003 aFile
->HumanReadablePath().get()));
2009 #elif defined(XP_MACOSX)
2012 Result
<bool, IOUtils::IOError
> IOUtils::HasMacXAttrSync(
2013 nsIFile
* aFile
, const nsCString
& aAttr
) {
2014 MOZ_ASSERT(!NS_IsMainThread());
2016 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2019 bool hasAttr
= false;
2020 if (nsresult rv
= file
->HasXAttr(aAttr
, &hasAttr
); NS_FAILED(rv
)) {
2021 return Err(IOError(rv
).WithMessage(
2022 "Could not read the extended attribute `%s' from the file `%s'",
2023 aAttr
.get(), aFile
->HumanReadablePath().get()));
2030 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::GetMacXAttrSync(
2031 nsIFile
* aFile
, const nsCString
& aAttr
) {
2032 MOZ_ASSERT(!NS_IsMainThread());
2034 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2037 nsTArray
<uint8_t> value
;
2038 if (nsresult rv
= file
->GetXAttr(aAttr
, value
); NS_FAILED(rv
)) {
2039 auto err
= IOError(rv
);
2041 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2042 return Err(err
.WithMessage(
2043 "The file `%s' does not have an extended attribute `%s'",
2044 aFile
->HumanReadablePath().get(), aAttr
.get()));
2047 return Err(err
.WithMessage(
2048 "Could not read the extended attribute `%s' from the file `%s'",
2049 aAttr
.get(), aFile
->HumanReadablePath().get()));
2056 Result
<Ok
, IOUtils::IOError
> IOUtils::SetMacXAttrSync(
2057 nsIFile
* aFile
, const nsCString
& aAttr
, const nsTArray
<uint8_t>& aValue
) {
2058 MOZ_ASSERT(!NS_IsMainThread());
2060 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2063 if (nsresult rv
= file
->SetXAttr(aAttr
, aValue
); NS_FAILED(rv
)) {
2064 return Err(IOError(rv
).WithMessage(
2065 "Could not set extended attribute `%s' on file `%s'", aAttr
.get(),
2066 aFile
->HumanReadablePath().get()));
2073 Result
<Ok
, IOUtils::IOError
> IOUtils::DelMacXAttrSync(nsIFile
* aFile
,
2074 const nsCString
& aAttr
) {
2075 MOZ_ASSERT(!NS_IsMainThread());
2077 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2080 if (nsresult rv
= file
->DelXAttr(aAttr
); NS_FAILED(rv
)) {
2081 auto err
= IOError(rv
);
2083 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2084 return Err(err
.WithMessage(
2085 "The file `%s' does not have an extended attribute `%s'",
2086 aFile
->HumanReadablePath().get(), aAttr
.get()));
2089 return Err(IOError(rv
).WithMessage(
2090 "Could not delete extended attribute `%s' on file `%s'", aAttr
.get(),
2091 aFile
->HumanReadablePath().get()));
2100 void IOUtils::GetProfileBeforeChange(GlobalObject
& aGlobal
,
2101 JS::MutableHandle
<JS::Value
> aClient
,
2103 return GetShutdownClient(aGlobal
, aClient
, aRv
,
2104 ShutdownPhase::ProfileBeforeChange
);
2108 void IOUtils::GetSendTelemetry(GlobalObject
& aGlobal
,
2109 JS::MutableHandle
<JS::Value
> aClient
,
2111 return GetShutdownClient(aGlobal
, aClient
, aRv
, ShutdownPhase::SendTelemetry
);
2115 * Assert that the given phase has a shutdown client exposed by IOUtils
2117 * There is no shutdown client exposed for XpcomWillShutdown.
2119 static void AssertHasShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2120 MOZ_RELEASE_ASSERT(aPhase
>= IOUtils::ShutdownPhase::ProfileBeforeChange
&&
2121 aPhase
< IOUtils::ShutdownPhase::XpcomWillShutdown
);
2125 void IOUtils::GetShutdownClient(GlobalObject
& aGlobal
,
2126 JS::MutableHandle
<JS::Value
> aClient
,
2128 const IOUtils::ShutdownPhase aPhase
) {
2129 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
2130 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2131 AssertHasShutdownClient(aPhase
);
2133 if (auto state
= GetState()) {
2134 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
!=
2135 ShutdownBlockerStatus::Uninitialized
);
2137 if (state
.ref()->mBlockerStatus
== ShutdownBlockerStatus::Failed
) {
2138 aRv
.ThrowAbortError("IOUtils: could not register shutdown blockers");
2142 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
==
2143 ShutdownBlockerStatus::Initialized
);
2144 auto result
= state
.ref()->mEventQueue
->GetShutdownClient(aPhase
);
2145 if (result
.isErr()) {
2146 aRv
.ThrowAbortError("IOUtils: could not get shutdown client");
2150 RefPtr
<nsIAsyncShutdownClient
> client
= result
.unwrap();
2151 MOZ_RELEASE_ASSERT(client
);
2152 if (nsresult rv
= client
->GetJsclient(aClient
); NS_FAILED(rv
)) {
2153 aRv
.ThrowAbortError("IOUtils: Could not get shutdown jsclient");
2158 aRv
.ThrowAbortError(
2159 "IOUtils: profileBeforeChange phase has already finished");
2163 Maybe
<IOUtils::StateMutex::AutoLock
> IOUtils::GetState() {
2164 auto state
= sState
.Lock();
2165 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2169 if (state
->mQueueStatus
== EventQueueStatus::Uninitialized
) {
2170 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2171 state
->mEventQueue
= new EventQueue();
2172 state
->mQueueStatus
= EventQueueStatus::Initialized
;
2174 MOZ_RELEASE_ASSERT(state
->mBlockerStatus
==
2175 ShutdownBlockerStatus::Uninitialized
);
2178 if (NS_IsMainThread() &&
2179 state
->mBlockerStatus
== ShutdownBlockerStatus::Uninitialized
) {
2180 state
->SetShutdownHooks();
2183 return Some(std::move(state
));
2186 IOUtils::EventQueue::EventQueue() {
2187 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
2188 "IOUtils::EventQueue", getter_AddRefs(mBackgroundEventTarget
)));
2190 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2193 void IOUtils::State::SetShutdownHooks() {
2194 if (mBlockerStatus
!= ShutdownBlockerStatus::Uninitialized
) {
2198 if (NS_WARN_IF(NS_FAILED(mEventQueue
->SetShutdownHooks()))) {
2199 mBlockerStatus
= ShutdownBlockerStatus::Failed
;
2201 mBlockerStatus
= ShutdownBlockerStatus::Initialized
;
2204 if (mBlockerStatus
!= ShutdownBlockerStatus::Initialized
) {
2205 NS_WARNING("IOUtils: could not register shutdown blockers.");
2209 nsresult
IOUtils::EventQueue::SetShutdownHooks() {
2210 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2212 constexpr static auto STACK
= u
"IOUtils::EventQueue::SetShutdownHooks"_ns
;
2213 constexpr static auto FILE = NS_LITERAL_STRING_FROM_CSTRING(__FILE__
);
2215 nsCOMPtr
<nsIAsyncShutdownService
> svc
= services::GetAsyncShutdownService();
2217 return NS_ERROR_NOT_AVAILABLE
;
2220 nsCOMPtr
<nsIAsyncShutdownBlocker
> profileBeforeChangeBlocker
;
2222 // Create a shutdown blocker for the profile-before-change phase.
2224 profileBeforeChangeBlocker
=
2225 new IOUtilsShutdownBlocker(ShutdownPhase::ProfileBeforeChange
);
2227 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2228 MOZ_TRY(svc
->GetProfileBeforeChange(getter_AddRefs(globalClient
)));
2229 MOZ_RELEASE_ASSERT(globalClient
);
2231 MOZ_TRY(globalClient
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
,
2235 // Create the shutdown barrier for profile-before-change so that consumers can
2236 // register shutdown blockers.
2238 // The blocker we just created will wait for all clients registered on this
2239 // barrier to finish.
2241 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2243 // It is okay for this to fail. The created shutdown blocker won't await
2244 // anything and shutdown will proceed.
2245 MOZ_TRY(svc
->MakeBarrier(
2246 u
"IOUtils: waiting for profileBeforeChange IO to complete"_ns
,
2247 getter_AddRefs(barrier
)));
2248 MOZ_RELEASE_ASSERT(barrier
);
2250 mBarriers
[ShutdownPhase::ProfileBeforeChange
] = std::move(barrier
);
2253 // Create a shutdown blocker for the profile-before-change-telemetry phase.
2254 nsCOMPtr
<nsIAsyncShutdownBlocker
> sendTelemetryBlocker
;
2256 sendTelemetryBlocker
=
2257 new IOUtilsShutdownBlocker(ShutdownPhase::SendTelemetry
);
2259 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2260 MOZ_TRY(svc
->GetSendTelemetry(getter_AddRefs(globalClient
)));
2261 MOZ_RELEASE_ASSERT(globalClient
);
2264 globalClient
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
, STACK
));
2267 // Create the shutdown barrier for profile-before-change-telemetry so that
2268 // consumers can register shutdown blockers.
2270 // The blocker we just created will wait for all clients registered on this
2271 // barrier to finish.
2273 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2275 MOZ_TRY(svc
->MakeBarrier(
2276 u
"IOUtils: waiting for sendTelemetry IO to complete"_ns
,
2277 getter_AddRefs(barrier
)));
2278 MOZ_RELEASE_ASSERT(barrier
);
2280 // Add a blocker on the previous shutdown phase.
2281 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2282 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2285 client
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
, STACK
));
2287 mBarriers
[ShutdownPhase::SendTelemetry
] = std::move(barrier
);
2290 // Create a shutdown blocker for the xpcom-will-shutdown phase.
2292 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2293 MOZ_TRY(svc
->GetXpcomWillShutdown(getter_AddRefs(globalClient
)));
2294 MOZ_RELEASE_ASSERT(globalClient
);
2296 nsCOMPtr
<nsIAsyncShutdownBlocker
> blocker
=
2297 new IOUtilsShutdownBlocker(ShutdownPhase::XpcomWillShutdown
);
2298 MOZ_TRY(globalClient
->AddBlocker(
2299 blocker
, FILE, __LINE__
, u
"IOUtils::EventQueue::SetShutdownHooks"_ns
));
2302 // Create a shutdown barrier for the xpcom-will-shutdown phase.
2304 // The blocker we just created will wait for all clients registered on this
2305 // barrier to finish.
2307 // The only client registered on this barrier should be a blocker for the
2308 // previous phase. This is to ensure that all shutdown IO happens when
2309 // shutdown phases do not happen (e.g., in xpcshell tests where
2310 // profile-before-change does not occur).
2312 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2314 MOZ_TRY(svc
->MakeBarrier(
2315 u
"IOUtils: waiting for xpcomWillShutdown IO to complete"_ns
,
2316 getter_AddRefs(barrier
)));
2317 MOZ_RELEASE_ASSERT(barrier
);
2319 // Add a blocker on the previous shutdown phase.
2320 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2321 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2323 client
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
,
2324 u
"IOUtils::EventQueue::SetShutdownHooks"_ns
);
2326 mBarriers
[ShutdownPhase::XpcomWillShutdown
] = std::move(barrier
);
2332 template <typename OkT
, typename Fn
>
2333 RefPtr
<IOUtils::IOPromise
<OkT
>> IOUtils::EventQueue::Dispatch(Fn aFunc
) {
2334 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2337 MakeRefPtr
<typename
IOUtils::IOPromise
<OkT
>::Private
>(__func__
);
2338 mBackgroundEventTarget
->Dispatch(
2339 NS_NewRunnableFunction("IOUtils::EventQueue::Dispatch",
2340 [promise
, func
= std::move(aFunc
)] {
2341 Result
<OkT
, IOError
> result
= func();
2342 if (result
.isErr()) {
2343 promise
->Reject(result
.unwrapErr(), __func__
);
2345 promise
->Resolve(result
.unwrap(), __func__
);
2348 NS_DISPATCH_EVENT_MAY_BLOCK
);
2352 Result
<already_AddRefed
<nsIAsyncShutdownBarrier
>, nsresult
>
2353 IOUtils::EventQueue::GetShutdownBarrier(const IOUtils::ShutdownPhase aPhase
) {
2354 if (!mBarriers
[aPhase
]) {
2355 return Err(NS_ERROR_NOT_AVAILABLE
);
2358 return do_AddRef(mBarriers
[aPhase
]);
2361 Result
<already_AddRefed
<nsIAsyncShutdownClient
>, nsresult
>
2362 IOUtils::EventQueue::GetShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2363 AssertHasShutdownClient(aPhase
);
2365 if (!mBarriers
[aPhase
]) {
2366 return Err(NS_ERROR_NOT_AVAILABLE
);
2369 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2370 MOZ_TRY(mBarriers
[aPhase
]->GetClient(getter_AddRefs(client
)));
2372 return do_AddRef(client
);
2376 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::MozLZ4::Compress(
2377 Span
<const uint8_t> aUncompressed
) {
2378 nsTArray
<uint8_t> result
;
2379 size_t worstCaseSize
=
2380 Compression::LZ4::maxCompressedSize(aUncompressed
.Length()) + HEADER_SIZE
;
2381 if (!result
.SetCapacity(worstCaseSize
, fallible
)) {
2382 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
2383 .WithMessage("Could not allocate buffer to compress data"));
2385 result
.AppendElements(Span(MAGIC_NUMBER
.data(), MAGIC_NUMBER
.size()));
2386 std::array
<uint8_t, sizeof(uint32_t)> contentSizeBytes
{};
2387 LittleEndian::writeUint32(contentSizeBytes
.data(), aUncompressed
.Length());
2388 result
.AppendElements(Span(contentSizeBytes
.data(), contentSizeBytes
.size()));
2390 if (aUncompressed
.Length() == 0) {
2391 // Don't try to compress an empty buffer.
2392 // Just return the correctly formed header.
2393 result
.SetLength(HEADER_SIZE
);
2397 size_t compressed
= Compression::LZ4::compress(
2398 reinterpret_cast<const char*>(aUncompressed
.Elements()),
2399 aUncompressed
.Length(),
2400 reinterpret_cast<char*>(result
.Elements()) + HEADER_SIZE
);
2403 IOError(NS_ERROR_UNEXPECTED
).WithMessage("Could not compress data"));
2405 result
.SetLength(HEADER_SIZE
+ compressed
);
2410 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::MozLZ4::Decompress(
2411 Span
<const uint8_t> aFileContents
, IOUtils::BufferKind aBufferKind
) {
2412 if (aFileContents
.LengthBytes() < HEADER_SIZE
) {
2414 IOError(NS_ERROR_FILE_CORRUPTED
)
2416 "Could not decompress file because the buffer is too short"));
2418 auto header
= aFileContents
.To(HEADER_SIZE
);
2419 if (!std::equal(std::begin(MAGIC_NUMBER
), std::end(MAGIC_NUMBER
),
2420 std::begin(header
))) {
2423 for (; i
< header
.Length() - 1; ++i
) {
2424 magicStr
.AppendPrintf("%02X ", header
.at(i
));
2426 magicStr
.AppendPrintf("%02X", header
.at(i
));
2428 return Err(IOError(NS_ERROR_FILE_CORRUPTED
)
2429 .WithMessage("Could not decompress file because it has an "
2430 "invalid LZ4 header (wrong magic number: '%s')",
2433 size_t numBytes
= sizeof(uint32_t);
2434 Span
<const uint8_t> sizeBytes
= header
.Last(numBytes
);
2435 uint32_t expectedDecompressedSize
=
2436 LittleEndian::readUint32(sizeBytes
.data());
2437 if (expectedDecompressedSize
== 0) {
2438 return JsBuffer::CreateEmpty(aBufferKind
);
2440 auto contents
= aFileContents
.From(HEADER_SIZE
);
2441 auto result
= JsBuffer::Create(aBufferKind
, expectedDecompressedSize
);
2442 if (result
.isErr()) {
2443 return result
.propagateErr();
2446 JsBuffer decompressed
= result
.unwrap();
2447 size_t actualSize
= 0;
2448 if (!Compression::LZ4::decompress(
2449 reinterpret_cast<const char*>(contents
.Elements()), contents
.Length(),
2450 reinterpret_cast<char*>(decompressed
.Elements()),
2451 expectedDecompressedSize
, &actualSize
)) {
2453 IOError(NS_ERROR_FILE_CORRUPTED
)
2455 "Could not decompress file contents, the file may be corrupt"));
2457 decompressed
.SetLength(actualSize
);
2458 return decompressed
;
2461 NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker
, nsIAsyncShutdownBlocker
,
2462 nsIAsyncShutdownCompletionCallback
);
2464 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetName(nsAString
& aName
) {
2465 aName
= u
"IOUtils Blocker ("_ns
;
2466 aName
.Append(PHASE_NAMES
[mPhase
]);
2472 NS_IMETHODIMP
IOUtilsShutdownBlocker::BlockShutdown(
2473 nsIAsyncShutdownClient
* aBarrierClient
) {
2474 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2475 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2477 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2479 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2482 auto state
= IOUtils::sState
.Lock();
2483 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2484 // If the previous blockers have already run, then the event queue is
2485 // already torn down and we have nothing to do.
2487 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2488 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2490 Unused
<< NS_WARN_IF(NS_FAILED(aBarrierClient
->RemoveBlocker(this)));
2491 mParentClient
= nullptr;
2496 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
2498 mParentClient
= aBarrierClient
;
2500 barrier
= state
->mEventQueue
->GetShutdownBarrier(mPhase
).unwrapOr(nullptr);
2503 // We cannot barrier->Wait() while holding the mutex because it will lead to
2505 if (!barrier
|| NS_WARN_IF(NS_FAILED(barrier
->Wait(this)))) {
2506 // If we don't have a barrier, we still need to flush the IOUtils event
2507 // queue and disable task submission.
2509 // Likewise, if waiting on the barrier failed, we are going to make our best
2510 // attempt to clean up.
2517 NS_IMETHODIMP
IOUtilsShutdownBlocker::Done() {
2518 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2519 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2521 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2523 bool didFlush
= false;
2526 auto state
= IOUtils::sState
.Lock();
2528 if (state
->mEventQueue
) {
2529 MOZ_RELEASE_ASSERT(state
->mQueueStatus
== EventQueueStatus::Initialized
);
2531 // This method is called once we have served all shutdown clients. Now we
2532 // flush the remaining IO queue. This ensures any straggling IO that was
2533 // not part of the shutdown blocker finishes before we move to the next
2535 state
->mEventQueue
->Dispatch
<Ok
>([]() { return Ok
{}; })
2536 ->Then(GetMainThreadSerialEventTarget(), __func__
,
2537 [self
= RefPtr(this)]() { self
->OnFlush(); });
2539 // And if we're the last shutdown phase to allow IO, disable the event
2540 // queue to disallow further IO requests.
2541 if (mPhase
>= LAST_IO_PHASE
) {
2542 state
->mQueueStatus
= EventQueueStatus::Shutdown
;
2549 // If we have already shut down the event loop, then call OnFlush to stop
2550 // blocking our parent shutdown client.
2552 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2559 void IOUtilsShutdownBlocker::OnFlush() {
2560 if (mParentClient
) {
2561 (void)NS_WARN_IF(NS_FAILED(mParentClient
->RemoveBlocker(this)));
2562 mParentClient
= nullptr;
2564 // If we are past the last shutdown phase that allows IO,
2565 // we can shutdown the event queue here because no additional IO requests
2566 // will be allowed (see |Done()|).
2567 if (mPhase
>= LAST_IO_PHASE
) {
2568 auto state
= IOUtils::sState
.Lock();
2569 if (state
->mEventQueue
) {
2570 state
->mEventQueue
= nullptr;
2576 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetState(nsIPropertyBag
** aState
) {
2580 Result
<IOUtils::InternalWriteOpts
, IOUtils::IOError
>
2581 IOUtils::InternalWriteOpts::FromBinding(const WriteOptions
& aOptions
) {
2582 InternalWriteOpts opts
;
2583 opts
.mFlush
= aOptions
.mFlush
;
2584 opts
.mMode
= aOptions
.mMode
;
2586 if (aOptions
.mBackupFile
.WasPassed()) {
2587 opts
.mBackupFile
= new nsLocalFile();
2588 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mBackupFile
,
2589 aOptions
.mBackupFile
.Value());
2591 return Err(IOUtils::IOError(rv
).WithMessage(
2592 "Could not parse path of backupFile (%s)",
2593 NS_ConvertUTF16toUTF8(aOptions
.mBackupFile
.Value()).get()));
2597 if (aOptions
.mTmpPath
.WasPassed()) {
2598 opts
.mTmpFile
= new nsLocalFile();
2599 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mTmpFile
,
2600 aOptions
.mTmpPath
.Value());
2602 return Err(IOUtils::IOError(rv
).WithMessage(
2603 "Could not parse path of temp file (%s)",
2604 NS_ConvertUTF16toUTF8(aOptions
.mTmpPath
.Value()).get()));
2608 opts
.mCompress
= aOptions
.mCompress
;
2613 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::JsBuffer::Create(
2614 IOUtils::BufferKind aBufferKind
, size_t aCapacity
) {
2615 JsBuffer
buffer(aBufferKind
, aCapacity
);
2616 if (aCapacity
!= 0 && !buffer
.mBuffer
) {
2617 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
2618 .WithMessage("Could not allocate buffer"));
2624 IOUtils::JsBuffer
IOUtils::JsBuffer::CreateEmpty(
2625 IOUtils::BufferKind aBufferKind
) {
2626 JsBuffer
buffer(aBufferKind
, 0);
2627 MOZ_RELEASE_ASSERT(buffer
.mBuffer
== nullptr);
2631 IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind
, size_t aCapacity
)
2632 : mBufferKind(aBufferKind
), mCapacity(aCapacity
), mLength(0) {
2634 if (aBufferKind
== BufferKind::String
) {
2635 mBuffer
= JS::UniqueChars(
2636 js_pod_arena_malloc
<char>(js::StringBufferArena
, mCapacity
));
2638 MOZ_RELEASE_ASSERT(aBufferKind
== BufferKind::Uint8Array
);
2639 mBuffer
= JS::UniqueChars(
2640 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, mCapacity
));
2645 IOUtils::JsBuffer::JsBuffer(IOUtils::JsBuffer
&& aOther
) noexcept
2646 : mBufferKind(aOther
.mBufferKind
),
2647 mCapacity(aOther
.mCapacity
),
2648 mLength(aOther
.mLength
),
2649 mBuffer(std::move(aOther
.mBuffer
)) {
2650 aOther
.mCapacity
= 0;
2654 IOUtils::JsBuffer
& IOUtils::JsBuffer::operator=(
2655 IOUtils::JsBuffer
&& aOther
) noexcept
{
2656 mBufferKind
= aOther
.mBufferKind
;
2657 mCapacity
= aOther
.mCapacity
;
2658 mLength
= aOther
.mLength
;
2659 mBuffer
= std::move(aOther
.mBuffer
);
2661 // Invalidate aOther.
2662 aOther
.mCapacity
= 0;
2669 JSString
* IOUtils::JsBuffer::IntoString(JSContext
* aCx
, JsBuffer aBuffer
) {
2670 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::String
);
2672 if (!aBuffer
.mCapacity
) {
2673 return JS_GetEmptyString(aCx
);
2676 if (IsAscii(aBuffer
.BeginReading())) {
2677 // If the string is just plain ASCII, then we can hand the buffer off to
2678 // JavaScript as a Latin1 string (since ASCII is a subset of Latin1).
2679 JS::UniqueLatin1Chars
asLatin1(
2680 reinterpret_cast<JS::Latin1Char
*>(aBuffer
.mBuffer
.release()));
2681 return JS_NewLatin1String(aCx
, std::move(asLatin1
), aBuffer
.mLength
);
2684 // If the string is encodable as Latin1, we need to deflate the string to a
2685 // Latin1 string to accoutn for UTF-8 characters that are encoded as more than
2688 // Otherwise, the string contains characters outside Latin1 so we have to
2689 // inflate to UTF-16.
2690 return JS_NewStringCopyUTF8N(
2691 aCx
, JS::UTF8Chars(aBuffer
.mBuffer
.get(), aBuffer
.mLength
));
2695 JSObject
* IOUtils::JsBuffer::IntoUint8Array(JSContext
* aCx
, JsBuffer aBuffer
) {
2696 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::Uint8Array
);
2698 if (!aBuffer
.mCapacity
) {
2699 return JS_NewUint8Array(aCx
, 0);
2702 char* rawBuffer
= aBuffer
.mBuffer
.release();
2703 MOZ_RELEASE_ASSERT(rawBuffer
);
2704 JS::Rooted
<JSObject
*> arrayBuffer(
2705 aCx
, JS::NewArrayBufferWithContents(aCx
, aBuffer
.mLength
,
2706 reinterpret_cast<void*>(rawBuffer
)));
2709 // The array buffer does not take ownership of the data pointer unless
2710 // creation succeeds. We are still on the hook to free it.
2712 // aBuffer will be destructed at end of scope, but its destructor does not
2713 // take into account |mCapacity| or |mLength|, so it is OK for them to be
2714 // non-zero here with a null |mBuffer|.
2719 return JS_NewUint8ArrayWithBuffer(aCx
, arrayBuffer
, 0, aBuffer
.mLength
);
2722 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, IOUtils::JsBuffer
&& aBuffer
,
2723 JS::MutableHandle
<JS::Value
> aValue
) {
2724 if (aBuffer
.mBufferKind
== IOUtils::BufferKind::String
) {
2725 JSString
* str
= IOUtils::JsBuffer::IntoString(aCx
, std::move(aBuffer
));
2730 aValue
.setString(str
);
2734 JSObject
* array
= IOUtils::JsBuffer::IntoUint8Array(aCx
, std::move(aBuffer
));
2739 aValue
.setObject(*array
);
2745 NS_IMPL_CYCLE_COLLECTING_ADDREF(SyncReadFile
)
2746 NS_IMPL_CYCLE_COLLECTING_RELEASE(SyncReadFile
)
2748 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SyncReadFile
)
2749 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
2750 NS_INTERFACE_MAP_ENTRY(nsISupports
)
2751 NS_INTERFACE_MAP_END
2753 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SyncReadFile
, mParent
)
2755 SyncReadFile::SyncReadFile(nsISupports
* aParent
,
2756 RefPtr
<nsFileRandomAccessStream
>&& aStream
,
2758 : mParent(aParent
), mStream(std::move(aStream
)), mSize(aSize
) {
2759 MOZ_RELEASE_ASSERT(mSize
>= 0);
2762 SyncReadFile::~SyncReadFile() = default;
2764 JSObject
* SyncReadFile::WrapObject(JSContext
* aCx
,
2765 JS::Handle
<JSObject
*> aGivenProto
) {
2766 return SyncReadFile_Binding::Wrap(aCx
, this, aGivenProto
);
2769 void SyncReadFile::ReadBytesInto(const Uint8Array
& aDestArray
,
2770 const int64_t aOffset
, ErrorResult
& aRv
) {
2772 return aRv
.ThrowOperationError("SyncReadFile is closed");
2775 aDestArray
.ComputeState();
2777 auto rangeEnd
= CheckedInt64(aOffset
) + aDestArray
.Length();
2778 if (!rangeEnd
.isValid()) {
2779 return aRv
.ThrowOperationError("Requested range overflows i64");
2782 if (rangeEnd
.value() > mSize
) {
2783 return aRv
.ThrowOperationError(
2784 "Requested range overflows SyncReadFile size");
2787 uint32_t readLen
{aDestArray
.Length()};
2792 if (nsresult rv
= mStream
->Seek(PR_SEEK_SET
, aOffset
); NS_FAILED(rv
)) {
2793 return aRv
.ThrowOperationError(
2794 FormatErrorMessage(rv
, "Could not seek to position %lld", aOffset
));
2797 Span
<char> toRead(reinterpret_cast<char*>(aDestArray
.Data()), readLen
);
2799 uint32_t totalRead
= 0;
2800 while (totalRead
!= readLen
) {
2801 // Read no more than INT32_MAX on each call to mStream->Read, otherwise it
2802 // returns an error.
2803 uint32_t bytesToReadThisChunk
=
2804 std::min
<uint32_t>(readLen
- totalRead
, INT32_MAX
);
2806 uint32_t bytesRead
= 0;
2808 mStream
->Read(toRead
.Elements(), bytesToReadThisChunk
, &bytesRead
);
2810 return aRv
.ThrowOperationError(FormatErrorMessage(
2811 rv
, "Encountered an unexpected error while reading file stream"));
2813 if (bytesRead
== 0) {
2814 return aRv
.ThrowOperationError(
2815 "Reading stopped before the entire array was filled");
2817 totalRead
+= bytesRead
;
2818 toRead
= toRead
.From(bytesRead
);
2822 void SyncReadFile::Close() { mStream
= nullptr; }
2827 static nsCString
FromUnixString(const IOUtils::UnixString
& aString
) {
2828 if (aString
.IsUTF8String()) {
2829 return aString
.GetAsUTF8String();
2831 if (aString
.IsUint8Array()) {
2832 const auto& u8a
= aString
.GetAsUint8Array();
2834 // Cast to deal with char signedness
2835 return nsCString(reinterpret_cast<const char*>(u8a
.Data()), u8a
.Length());
2837 MOZ_CRASH("unreachable");
2843 uint32_t IOUtils::LaunchProcess(GlobalObject
& aGlobal
,
2844 const Sequence
<UnixString
>& aArgv
,
2845 const LaunchOptions
& aOptions
,
2847 // The binding is worker-only, so should always be off-main-thread.
2848 MOZ_ASSERT(!NS_IsMainThread());
2850 // This generally won't work in child processes due to sandboxing.
2851 AssertParentProcessWithCallerLocation(aGlobal
);
2853 std::vector
<std::string
> argv
;
2854 base::LaunchOptions options
;
2856 for (const auto& arg
: aArgv
) {
2857 argv
.push_back(FromUnixString(arg
).get());
2860 size_t envLen
= aOptions
.mEnvironment
.Length();
2861 base::EnvironmentArray
envp(new char*[envLen
+ 1]);
2862 for (size_t i
= 0; i
< envLen
; ++i
) {
2863 // EnvironmentArray is a UniquePtr instance which will `free`
2865 envp
[i
] = strdup(FromUnixString(aOptions
.mEnvironment
[i
]).get());
2867 envp
[envLen
] = nullptr;
2868 options
.full_env
= std::move(envp
);
2870 if (aOptions
.mWorkdir
.WasPassed()) {
2871 options
.workdir
= FromUnixString(aOptions
.mWorkdir
.Value()).get();
2874 if (aOptions
.mFdMap
.WasPassed()) {
2875 for (const auto& fdItem
: aOptions
.mFdMap
.Value()) {
2876 options
.fds_to_remap
.push_back({fdItem
.mSrc
, fdItem
.mDst
});
2881 options
.disclaim
= aOptions
.mDisclaim
;
2884 base::ProcessHandle pid
;
2885 static_assert(sizeof(pid
) <= sizeof(uint32_t),
2886 "WebIDL long should be large enough for a pid");
2887 bool ok
= base::LaunchApp(argv
, options
, &pid
);
2889 aRv
.Throw(NS_ERROR_FAILURE
);
2893 MOZ_ASSERT(pid
>= 0);
2894 return static_cast<uint32_t>(pid
);
2898 } // namespace mozilla::dom
2900 #undef REJECT_IF_INIT_PATH_FAILED