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::ColumnNumberOneOrigin
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, _msg, ...) \
84 if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
86 (_promise)->MaybeRejectWithOperationError(FormatErrorMessage( \
87 _rv, _msg ": could not parse path", ##__VA_ARGS__)); \
92 #define IOUTILS_TRY_WITH_CONTEXT(_expr, _fmt, ...) \
94 if (nsresult _rv = (_expr); NS_FAILED(_rv)) { \
95 return Err(IOUtils::IOError(_rv, _fmt, ##__VA_ARGS__)); \
99 static constexpr auto SHUTDOWN_ERROR
=
100 "IOUtils: Shutting down and refusing additional I/O tasks"_ns
;
102 namespace mozilla::dom
{
104 // static helper functions
107 * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
108 * report I/O errors inconsistently. For convenience, this function will attempt
109 * to match a |nsresult| against known results which imply a file cannot be
112 * @see nsLocalFileWin.cpp
113 * @see nsLocalFileUnix.cpp
115 static bool IsFileNotFound(nsresult aResult
) {
116 return aResult
== NS_ERROR_FILE_NOT_FOUND
;
119 * Like |IsFileNotFound|, but checks for known results that suggest a file
120 * is not a directory.
122 static bool IsNotDirectory(nsresult aResult
) {
123 return aResult
== NS_ERROR_FILE_DESTINATION_NOT_DIR
||
124 aResult
== NS_ERROR_FILE_NOT_DIRECTORY
;
128 * Formats an error message and appends the error name to the end.
130 static nsCString
MOZ_FORMAT_PRINTF(2, 3)
131 FormatErrorMessage(nsresult aError
, const char* const aFmt
, ...) {
132 nsAutoCString errorName
;
133 GetErrorName(aError
, errorName
);
139 msg
.AppendVprintf(aFmt
, ap
);
142 msg
.AppendPrintf(" (%s)", errorName
.get());
147 static nsCString
FormatErrorMessage(nsresult aError
,
148 const nsCString
& aMessage
) {
149 nsAutoCString errorName
;
150 GetErrorName(aError
, errorName
);
152 nsCString
msg(aMessage
);
153 msg
.AppendPrintf(" (%s)", errorName
.get());
158 [[nodiscard
]] inline bool ToJSValue(
159 JSContext
* aCx
, const IOUtils::InternalFileInfo
& aInternalFileInfo
,
160 JS::MutableHandle
<JS::Value
> aValue
) {
162 info
.mPath
.Construct(aInternalFileInfo
.mPath
);
163 info
.mType
.Construct(aInternalFileInfo
.mType
);
164 info
.mSize
.Construct(aInternalFileInfo
.mSize
);
166 if (aInternalFileInfo
.mCreationTime
.isSome()) {
167 info
.mCreationTime
.Construct(aInternalFileInfo
.mCreationTime
.ref());
169 info
.mLastAccessed
.Construct(aInternalFileInfo
.mLastAccessed
);
170 info
.mLastModified
.Construct(aInternalFileInfo
.mLastModified
);
172 info
.mPermissions
.Construct(aInternalFileInfo
.mPermissions
);
174 return ToJSValue(aCx
, info
, aValue
);
177 template <typename T
>
178 static void ResolveJSPromise(Promise
* aPromise
, T
&& aValue
) {
179 if constexpr (std::is_same_v
<T
, Ok
>) {
180 aPromise
->MaybeResolveWithUndefined();
181 } else if constexpr (std::is_same_v
<T
, nsTArray
<uint8_t>>) {
182 TypedArrayCreator
<Uint8Array
> array(aValue
);
183 aPromise
->MaybeResolve(array
);
185 aPromise
->MaybeResolve(std::forward
<T
>(aValue
));
189 static void RejectJSPromise(Promise
* aPromise
, const IOUtils::IOError
& aError
) {
190 const auto errMsg
= FormatErrorMessage(aError
.Code(), aError
.Message());
192 switch (aError
.Code()) {
193 case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK
:
195 case NS_ERROR_FILE_NOT_FOUND
:
197 case NS_ERROR_FILE_INVALID_PATH
:
199 case NS_ERROR_NOT_AVAILABLE
:
200 aPromise
->MaybeRejectWithNotFoundError(errMsg
);
203 case NS_ERROR_FILE_IS_LOCKED
:
205 case NS_ERROR_FILE_ACCESS_DENIED
:
206 aPromise
->MaybeRejectWithNotAllowedError(errMsg
);
209 case NS_ERROR_FILE_TOO_BIG
:
211 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
213 case NS_ERROR_FILE_DEVICE_FAILURE
:
215 case NS_ERROR_FILE_FS_CORRUPTED
:
217 case NS_ERROR_FILE_CORRUPTED
:
218 aPromise
->MaybeRejectWithNotReadableError(errMsg
);
221 case NS_ERROR_FILE_ALREADY_EXISTS
:
222 aPromise
->MaybeRejectWithNoModificationAllowedError(errMsg
);
225 case NS_ERROR_FILE_COPY_OR_MOVE_FAILED
:
227 case NS_ERROR_FILE_NAME_TOO_LONG
:
229 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
231 case NS_ERROR_FILE_DIR_NOT_EMPTY
:
232 aPromise
->MaybeRejectWithOperationError(errMsg
);
235 case NS_ERROR_FILE_READ_ONLY
:
236 aPromise
->MaybeRejectWithReadOnlyError(errMsg
);
239 case NS_ERROR_FILE_NOT_DIRECTORY
:
241 case NS_ERROR_FILE_DESTINATION_NOT_DIR
:
243 case NS_ERROR_FILE_IS_DIRECTORY
:
245 case NS_ERROR_FILE_UNKNOWN_TYPE
:
246 aPromise
->MaybeRejectWithInvalidAccessError(errMsg
);
249 case NS_ERROR_ILLEGAL_INPUT
:
251 case NS_ERROR_ILLEGAL_VALUE
:
252 aPromise
->MaybeRejectWithDataError(errMsg
);
256 aPromise
->MaybeRejectWithAbortError(errMsg
);
260 aPromise
->MaybeRejectWithUnknownError(errMsg
);
264 static void RejectShuttingDown(Promise
* aPromise
) {
265 RejectJSPromise(aPromise
, IOUtils::IOError(NS_ERROR_ABORT
, SHUTDOWN_ERROR
));
268 static bool AssertParentProcessWithCallerLocationImpl(GlobalObject
& aGlobal
,
270 if (MOZ_LIKELY(XRE_IsParentProcess())) {
275 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
276 MOZ_ALWAYS_TRUE(global
);
277 MOZ_ALWAYS_TRUE(jsapi
.Init(global
));
279 JSContext
* cx
= jsapi
.cx();
281 JS::AutoFilename scriptFilename
;
283 JS::ColumnNumberOneOrigin colNo
;
286 JS::DescribeScriptedCaller(cx
, &scriptFilename
, &lineNo
, &colNo
), false);
288 NS_ENSURE_TRUE(scriptFilename
.get(), false);
290 reason
.AppendPrintf(" Called from %s:%d:%d.", scriptFilename
.get(), lineNo
,
291 colNo
.oneOriginValue());
295 static void AssertParentProcessWithCallerLocation(GlobalObject
& aGlobal
) {
296 nsCString reason
= "IOUtils can only be used in the parent process."_ns
;
297 if (!AssertParentProcessWithCallerLocationImpl(aGlobal
, reason
)) {
298 MOZ_CRASH_UNSAFE_PRINTF("%s", reason
.get());
302 // IOUtils implementation
304 IOUtils::StateMutex
IOUtils::sState
{"IOUtils::sState"};
307 template <typename Fn
>
308 already_AddRefed
<Promise
> IOUtils::WithPromiseAndState(GlobalObject
& aGlobal
,
311 AssertParentProcessWithCallerLocation(aGlobal
);
313 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
, aError
);
318 if (auto state
= GetState()) {
319 aFn(promise
, state
.ref());
321 RejectShuttingDown(promise
);
323 return promise
.forget();
327 template <typename OkT
, typename Fn
>
328 void IOUtils::DispatchAndResolve(IOUtils::EventQueue
* aQueue
, Promise
* aPromise
,
330 RefPtr
<StrongWorkerRef
> workerRef
;
331 if (!NS_IsMainThread()) {
332 // We need to manually keep the worker alive until the promise returned by
333 // Dispatch() resolves or rejects.
334 workerRef
= StrongWorkerRef::CreateForcibly(GetCurrentThreadWorkerPrivate(),
338 if (RefPtr
<IOPromise
<OkT
>> p
= aQueue
->Dispatch
<OkT
, Fn
>(std::move(aFunc
))) {
340 GetCurrentSerialEventTarget(), __func__
,
341 [workerRef
, promise
= RefPtr(aPromise
)](OkT
&& ok
) {
342 ResolveJSPromise(promise
, std::forward
<OkT
>(ok
));
344 [workerRef
, promise
= RefPtr(aPromise
)](const IOError
& err
) {
345 RejectJSPromise(promise
, err
);
351 already_AddRefed
<Promise
> IOUtils::Read(GlobalObject
& aGlobal
,
352 const nsAString
& aPath
,
353 const ReadOptions
& aOptions
,
354 ErrorResult
& aError
) {
355 return WithPromiseAndState(
356 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
357 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
358 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
, "Could not read `%s'",
359 NS_ConvertUTF16toUTF8(aPath
).get());
361 Maybe
<uint32_t> toRead
= Nothing();
362 if (!aOptions
.mMaxBytes
.IsNull()) {
363 if (aOptions
.mDecompress
) {
365 promise
, IOError(NS_ERROR_ILLEGAL_INPUT
,
366 "Could not read `%s': the `maxBytes' and "
367 "`decompress' options are mutually exclusive",
368 file
->HumanReadablePath().get()));
372 if (aOptions
.mMaxBytes
.Value() == 0) {
373 // Resolve with an empty buffer.
374 nsTArray
<uint8_t> arr(0);
375 promise
->MaybeResolve(TypedArrayCreator
<Uint8Array
>(arr
));
378 toRead
.emplace(aOptions
.mMaxBytes
.Value());
381 DispatchAndResolve
<JsBuffer
>(
382 state
->mEventQueue
, promise
,
383 [file
= std::move(file
), offset
= aOptions
.mOffset
, toRead
,
384 decompress
= aOptions
.mDecompress
]() {
385 return ReadSync(file
, offset
, toRead
, decompress
,
386 BufferKind::Uint8Array
);
392 RefPtr
<SyncReadFile
> IOUtils::OpenFileForSyncReading(GlobalObject
& aGlobal
,
393 const nsAString
& aPath
,
395 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
397 // This API is only exposed to workers, so we should not be on the main
399 MOZ_RELEASE_ASSERT(!NS_IsMainThread());
401 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
402 if (nsresult rv
= PathUtils::InitFileWithPath(file
, aPath
); NS_FAILED(rv
)) {
403 aRv
.ThrowOperationError(FormatErrorMessage(
404 rv
, "Could not parse path (%s)", NS_ConvertUTF16toUTF8(aPath
).get()));
408 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
410 stream
->Init(file
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
412 aRv
.ThrowOperationError(
413 FormatErrorMessage(rv
, "Could not open the file at %s",
414 NS_ConvertUTF16toUTF8(aPath
).get()));
419 if (nsresult rv
= stream
->GetSize(&size
); NS_FAILED(rv
)) {
420 aRv
.ThrowOperationError(FormatErrorMessage(
421 rv
, "Could not get the stream size for the file at %s",
422 NS_ConvertUTF16toUTF8(aPath
).get()));
426 return new SyncReadFile(aGlobal
.GetAsSupports(), std::move(stream
), size
);
430 already_AddRefed
<Promise
> IOUtils::ReadUTF8(GlobalObject
& aGlobal
,
431 const nsAString
& aPath
,
432 const ReadUTF8Options
& aOptions
,
433 ErrorResult
& aError
) {
434 return WithPromiseAndState(
435 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
436 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
437 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
, "Could not read `%s'",
438 NS_ConvertUTF16toUTF8(aPath
).get());
440 DispatchAndResolve
<JsBuffer
>(
441 state
->mEventQueue
, promise
,
442 [file
= std::move(file
), decompress
= aOptions
.mDecompress
]() {
443 return ReadUTF8Sync(file
, decompress
);
449 already_AddRefed
<Promise
> IOUtils::ReadJSON(GlobalObject
& aGlobal
,
450 const nsAString
& aPath
,
451 const ReadUTF8Options
& aOptions
,
452 ErrorResult
& aError
) {
453 return WithPromiseAndState(
454 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
455 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
456 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
, "Could not read `%s'",
457 NS_ConvertUTF16toUTF8(aPath
).get());
459 RefPtr
<StrongWorkerRef
> workerRef
;
460 if (!NS_IsMainThread()) {
461 // We need to manually keep the worker alive until the promise
462 // returned by Dispatch() resolves or rejects.
463 workerRef
= StrongWorkerRef::CreateForcibly(
464 GetCurrentThreadWorkerPrivate(), __func__
);
468 ->template Dispatch
<JsBuffer
>(
469 [file
, decompress
= aOptions
.mDecompress
]() {
470 return ReadUTF8Sync(file
, decompress
);
473 GetCurrentSerialEventTarget(), __func__
,
474 [workerRef
, promise
= RefPtr
{promise
},
475 file
](JsBuffer
&& aBuffer
) {
477 if (NS_WARN_IF(!jsapi
.Init(promise
->GetGlobalObject()))) {
481 NS_ERROR_DOM_UNKNOWN_ERR
,
482 "Could not read `%s': could not initialize JS API",
483 file
->HumanReadablePath().get()));
486 JSContext
* cx
= jsapi
.cx();
488 JS::Rooted
<JSString
*> jsonStr(
490 IOUtils::JsBuffer::IntoString(cx
, std::move(aBuffer
)));
495 NS_ERROR_OUT_OF_MEMORY
,
496 "Could not read `%s': failed to allocate buffer",
497 file
->HumanReadablePath().get()));
501 JS::Rooted
<JS::Value
> val(cx
);
502 if (!JS_ParseJSON(cx
, jsonStr
, &val
)) {
503 JS::Rooted
<JS::Value
> exn(cx
);
504 if (JS_GetPendingException(cx
, &exn
)) {
505 JS_ClearPendingException(cx
);
506 promise
->MaybeReject(exn
);
508 RejectJSPromise(promise
,
509 IOError(NS_ERROR_DOM_UNKNOWN_ERR
,
510 "Could not read `%s': ParseJSON "
511 "threw an uncatchable exception",
512 file
->HumanReadablePath().get()));
518 promise
->MaybeResolve(val
);
520 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
521 RejectJSPromise(promise
, aErr
);
527 already_AddRefed
<Promise
> IOUtils::Write(GlobalObject
& aGlobal
,
528 const nsAString
& aPath
,
529 const Uint8Array
& aData
,
530 const WriteOptions
& aOptions
,
531 ErrorResult
& aError
) {
532 return WithPromiseAndState(
533 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
534 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
535 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
536 "Could not write to `%s'",
537 NS_ConvertUTF16toUTF8(aPath
).get());
539 Maybe
<Buffer
<uint8_t>> buf
= aData
.CreateFromData
<Buffer
<uint8_t>>();
540 if (buf
.isNothing()) {
541 promise
->MaybeRejectWithOperationError(nsPrintfCString(
542 "Could not write to `%s': could not allocate buffer",
543 file
->HumanReadablePath().get()));
547 auto result
= InternalWriteOpts::FromBinding(aOptions
);
548 if (result
.isErr()) {
551 IOError::WithCause(result
.unwrapErr(), "Could not write to `%s'",
552 file
->HumanReadablePath().get()));
556 DispatchAndResolve
<uint32_t>(
557 state
->mEventQueue
, promise
,
558 [file
= std::move(file
), buf
= buf
.extract(),
559 opts
= result
.unwrap()]() { return WriteSync(file
, buf
, opts
); });
564 already_AddRefed
<Promise
> IOUtils::WriteUTF8(GlobalObject
& aGlobal
,
565 const nsAString
& aPath
,
566 const nsACString
& aString
,
567 const WriteOptions
& aOptions
,
568 ErrorResult
& aError
) {
569 return WithPromiseAndState(
570 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
571 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
572 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
573 "Could not write to `%s'",
574 NS_ConvertUTF16toUTF8(aPath
).get());
576 auto result
= InternalWriteOpts::FromBinding(aOptions
);
577 if (result
.isErr()) {
580 IOError::WithCause(result
.unwrapErr(), "Could not write to `%s'",
581 file
->HumanReadablePath().get()));
585 DispatchAndResolve
<uint32_t>(
586 state
->mEventQueue
, promise
,
587 [file
= std::move(file
), str
= nsCString(aString
),
588 opts
= result
.unwrap()]() {
589 return WriteSync(file
, AsBytes(Span(str
)), opts
);
595 already_AddRefed
<Promise
> IOUtils::WriteJSON(GlobalObject
& aGlobal
,
596 const nsAString
& aPath
,
597 JS::Handle
<JS::Value
> aValue
,
598 const WriteOptions
& aOptions
,
599 ErrorResult
& aError
) {
600 return WithPromiseAndState(
601 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
602 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
603 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
604 "Could not write to `%s'",
605 NS_ConvertUTF16toUTF8(aPath
).get());
607 auto result
= InternalWriteOpts::FromBinding(aOptions
);
608 if (result
.isErr()) {
611 IOError::WithCause(result
.unwrapErr(), "Could not write to `%s'",
612 file
->HumanReadablePath().get()));
616 auto opts
= result
.unwrap();
618 if (opts
.mMode
== WriteMode::Append
||
619 opts
.mMode
== WriteMode::AppendOrCreate
) {
620 promise
->MaybeRejectWithNotSupportedError(
621 nsPrintfCString("Could not write to `%s': IOUtils.writeJSON does "
622 "not support appending to files.",
623 file
->HumanReadablePath().get()));
627 JSContext
* cx
= aGlobal
.Context();
628 JS::Rooted
<JS::Value
> rootedValue(cx
, aValue
);
630 if (!nsContentUtils::StringifyJSON(cx
, aValue
, string
,
631 UndefinedIsNullStringLiteral
)) {
632 JS::Rooted
<JS::Value
> exn(cx
, JS::UndefinedValue());
633 if (JS_GetPendingException(cx
, &exn
)) {
634 JS_ClearPendingException(cx
);
635 promise
->MaybeReject(exn
);
637 RejectJSPromise(promise
,
638 IOError(NS_ERROR_DOM_UNKNOWN_ERR
,
639 "Could not serialize object to JSON"_ns
));
644 DispatchAndResolve
<uint32_t>(
645 state
->mEventQueue
, promise
,
646 [file
= std::move(file
), string
= std::move(string
),
647 opts
= std::move(opts
)]() -> Result
<uint32_t, IOError
> {
648 nsAutoCString utf8Str
;
649 if (!CopyUTF16toUTF8(string
, utf8Str
, fallible
)) {
651 NS_ERROR_OUT_OF_MEMORY
,
652 "Failed to write to `%s': could not allocate buffer",
653 file
->HumanReadablePath().get()));
655 return WriteSync(file
, AsBytes(Span(utf8Str
)), opts
);
661 already_AddRefed
<Promise
> IOUtils::Move(GlobalObject
& aGlobal
,
662 const nsAString
& aSourcePath
,
663 const nsAString
& aDestPath
,
664 const MoveOptions
& aOptions
,
665 ErrorResult
& aError
) {
666 return WithPromiseAndState(
667 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
668 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
669 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
,
670 "Could not move `%s' to `%s'",
671 NS_ConvertUTF16toUTF8(aSourcePath
).get(),
672 NS_ConvertUTF16toUTF8(aDestPath
).get());
674 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
675 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
,
676 "Could not move `%s' to `%s'",
677 NS_ConvertUTF16toUTF8(aSourcePath
).get(),
678 NS_ConvertUTF16toUTF8(aDestPath
).get());
680 DispatchAndResolve
<Ok
>(
681 state
->mEventQueue
, promise
,
682 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
683 noOverwrite
= aOptions
.mNoOverwrite
]() {
684 return MoveSync(sourceFile
, destFile
, noOverwrite
);
690 already_AddRefed
<Promise
> IOUtils::Remove(GlobalObject
& aGlobal
,
691 const nsAString
& aPath
,
692 const RemoveOptions
& aOptions
,
693 ErrorResult
& aError
) {
694 return WithPromiseAndState(
695 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
696 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
697 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
698 "Could not remove `%s'",
699 NS_ConvertUTF16toUTF8(aPath
).get());
701 DispatchAndResolve
<Ok
>(
702 state
->mEventQueue
, promise
,
703 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
,
704 recursive
= aOptions
.mRecursive
,
705 retryReadonly
= aOptions
.mRetryReadonly
]() {
706 return RemoveSync(file
, ignoreAbsent
, recursive
, retryReadonly
);
712 already_AddRefed
<Promise
> IOUtils::MakeDirectory(
713 GlobalObject
& aGlobal
, const nsAString
& aPath
,
714 const MakeDirectoryOptions
& aOptions
, ErrorResult
& aError
) {
715 return WithPromiseAndState(
716 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
717 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
718 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
719 "Could not make directory `%s'",
720 NS_ConvertUTF16toUTF8(aPath
).get());
722 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
723 [file
= std::move(file
),
724 createAncestors
= aOptions
.mCreateAncestors
,
725 ignoreExisting
= aOptions
.mIgnoreExisting
,
726 permissions
= aOptions
.mPermissions
]() {
727 return MakeDirectorySync(file
, createAncestors
,
734 already_AddRefed
<Promise
> IOUtils::Stat(GlobalObject
& aGlobal
,
735 const nsAString
& aPath
,
736 ErrorResult
& aError
) {
737 return WithPromiseAndState(
738 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
739 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
740 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
, "Could not stat `%s'",
741 NS_ConvertUTF16toUTF8(aPath
).get());
743 DispatchAndResolve
<InternalFileInfo
>(
744 state
->mEventQueue
, promise
,
745 [file
= std::move(file
)]() { return StatSync(file
); });
750 already_AddRefed
<Promise
> IOUtils::Copy(GlobalObject
& aGlobal
,
751 const nsAString
& aSourcePath
,
752 const nsAString
& aDestPath
,
753 const CopyOptions
& aOptions
,
754 ErrorResult
& aError
) {
755 return WithPromiseAndState(
756 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
757 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
758 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
,
759 "Could not copy `%s' to `%s'",
760 NS_ConvertUTF16toUTF8(aSourcePath
).get(),
761 NS_ConvertUTF16toUTF8(aDestPath
).get());
763 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
764 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
,
765 "Could not copy `%s' to `%s'",
766 NS_ConvertUTF16toUTF8(aSourcePath
).get(),
767 NS_ConvertUTF16toUTF8(aDestPath
).get());
769 DispatchAndResolve
<Ok
>(
770 state
->mEventQueue
, promise
,
771 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
772 noOverwrite
= aOptions
.mNoOverwrite
,
773 recursive
= aOptions
.mRecursive
]() {
774 return CopySync(sourceFile
, destFile
, noOverwrite
, recursive
);
780 already_AddRefed
<Promise
> IOUtils::SetAccessTime(
781 GlobalObject
& aGlobal
, const nsAString
& aPath
,
782 const Optional
<int64_t>& aAccess
, ErrorResult
& aError
) {
783 return SetTime(aGlobal
, aPath
, aAccess
, &nsIFile::SetLastAccessedTime
,
788 already_AddRefed
<Promise
> IOUtils::SetModificationTime(
789 GlobalObject
& aGlobal
, const nsAString
& aPath
,
790 const Optional
<int64_t>& aModification
, ErrorResult
& aError
) {
791 return SetTime(aGlobal
, aPath
, aModification
, &nsIFile::SetLastModifiedTime
,
792 "modification", aError
);
796 already_AddRefed
<Promise
> IOUtils::SetTime(GlobalObject
& aGlobal
,
797 const nsAString
& aPath
,
798 const Optional
<int64_t>& aNewTime
,
799 IOUtils::SetTimeFn aSetTimeFn
,
800 const char* const aTimeKind
,
801 ErrorResult
& aError
) {
802 return WithPromiseAndState(
803 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
804 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
805 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
806 "Could not set %s time on `%s'", aTimeKind
,
807 NS_ConvertUTF16toUTF8(aPath
).get());
809 int64_t newTime
= aNewTime
.WasPassed() ? aNewTime
.Value()
810 : PR_Now() / PR_USEC_PER_MSEC
;
811 DispatchAndResolve
<int64_t>(
812 state
->mEventQueue
, promise
,
813 [file
= std::move(file
), aSetTimeFn
, newTime
]() {
814 return SetTimeSync(file
, aSetTimeFn
, newTime
);
820 already_AddRefed
<Promise
> IOUtils::GetChildren(
821 GlobalObject
& aGlobal
, const nsAString
& aPath
,
822 const GetChildrenOptions
& aOptions
, ErrorResult
& aError
) {
823 return WithPromiseAndState(
824 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
825 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
826 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
827 "Could not get children of `%s'",
828 NS_ConvertUTF16toUTF8(aPath
).get());
830 DispatchAndResolve
<nsTArray
<nsString
>>(
831 state
->mEventQueue
, promise
,
832 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
]() {
833 return GetChildrenSync(file
, ignoreAbsent
);
839 already_AddRefed
<Promise
> IOUtils::SetPermissions(GlobalObject
& aGlobal
,
840 const nsAString
& aPath
,
841 uint32_t aPermissions
,
842 const bool aHonorUmask
,
843 ErrorResult
& aError
) {
844 return WithPromiseAndState(
845 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
846 #if defined(XP_UNIX) && !defined(ANDROID)
848 aPermissions
&= ~nsSystemInfo::gUserUmask
;
852 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
853 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
854 "Could not set permissions on `%s'",
855 NS_ConvertUTF16toUTF8(aPath
).get());
857 DispatchAndResolve
<Ok
>(
858 state
->mEventQueue
, promise
,
859 [file
= std::move(file
), permissions
= aPermissions
]() {
860 return SetPermissionsSync(file
, permissions
);
866 already_AddRefed
<Promise
> IOUtils::Exists(GlobalObject
& aGlobal
,
867 const nsAString
& aPath
,
868 ErrorResult
& aError
) {
869 return WithPromiseAndState(
870 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
871 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
872 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
873 "Could not determine if `%s' exists",
874 NS_ConvertUTF16toUTF8(aPath
).get());
876 DispatchAndResolve
<bool>(
877 state
->mEventQueue
, promise
,
878 [file
= std::move(file
)]() { return ExistsSync(file
); });
883 already_AddRefed
<Promise
> IOUtils::CreateUniqueFile(GlobalObject
& aGlobal
,
884 const nsAString
& aParent
,
885 const nsAString
& aPrefix
,
886 const uint32_t aPermissions
,
887 ErrorResult
& aError
) {
888 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::NORMAL_FILE_TYPE
,
889 aPermissions
, aError
);
893 already_AddRefed
<Promise
> IOUtils::CreateUniqueDirectory(
894 GlobalObject
& aGlobal
, const nsAString
& aParent
, const nsAString
& aPrefix
,
895 const uint32_t aPermissions
, ErrorResult
& aError
) {
896 return CreateUnique(aGlobal
, aParent
, aPrefix
, nsIFile::DIRECTORY_TYPE
,
897 aPermissions
, aError
);
901 already_AddRefed
<Promise
> IOUtils::CreateUnique(GlobalObject
& aGlobal
,
902 const nsAString
& aParent
,
903 const nsAString
& aPrefix
,
904 const uint32_t aFileType
,
905 const uint32_t aPermissions
,
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(
911 file
, aParent
, promise
, "Could not create unique %s in `%s'",
912 aFileType
== nsIFile::NORMAL_FILE_TYPE
? "file" : "directory",
913 NS_ConvertUTF16toUTF8(aParent
).get());
915 if (nsresult rv
= file
->Append(aPrefix
); NS_FAILED(rv
)) {
920 "Could not create unique %s: could not append prefix `%s' to "
922 aFileType
== nsIFile::NORMAL_FILE_TYPE
? "file" : "directory",
923 NS_ConvertUTF16toUTF8(aPrefix
).get(),
924 file
->HumanReadablePath().get()));
928 DispatchAndResolve
<nsString
>(
929 state
->mEventQueue
, promise
,
930 [file
= std::move(file
), aPermissions
, aFileType
]() {
931 return CreateUniqueSync(file
, aFileType
, aPermissions
);
937 already_AddRefed
<Promise
> IOUtils::ComputeHexDigest(
938 GlobalObject
& aGlobal
, const nsAString
& aPath
,
939 const HashAlgorithm aAlgorithm
, ErrorResult
& aError
) {
940 const bool nssInitialized
= EnsureNSSInitializedChromeOrContent();
942 return WithPromiseAndState(
943 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
944 if (!nssInitialized
) {
945 RejectJSPromise(promise
, IOError(NS_ERROR_UNEXPECTED
,
946 "Could not initialize NSS"_ns
));
950 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
951 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
, "Could not hash `%s'",
952 NS_ConvertUTF16toUTF8(aPath
).get());
954 DispatchAndResolve
<nsCString
>(state
->mEventQueue
, promise
,
955 [file
= std::move(file
), aAlgorithm
]() {
956 return ComputeHexDigestSync(file
,
965 already_AddRefed
<Promise
> IOUtils::GetWindowsAttributes(GlobalObject
& aGlobal
,
966 const nsAString
& aPath
,
967 ErrorResult
& aError
) {
968 return WithPromiseAndState(
969 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
970 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
971 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
,
972 "Could not get Windows file attributes of "
974 NS_ConvertUTF16toUTF8(aPath
).get());
976 RefPtr
<StrongWorkerRef
> workerRef
;
977 if (!NS_IsMainThread()) {
978 // We need to manually keep the worker alive until the promise
979 // returned by Dispatch() resolves or rejects.
980 workerRef
= StrongWorkerRef::CreateForcibly(
981 GetCurrentThreadWorkerPrivate(), __func__
);
985 ->template Dispatch
<uint32_t>([file
= std::move(file
)]() {
986 return GetWindowsAttributesSync(file
);
989 GetCurrentSerialEventTarget(), __func__
,
990 [workerRef
, promise
= RefPtr
{promise
}](const uint32_t aAttrs
) {
991 WindowsFileAttributes attrs
;
993 attrs
.mReadOnly
.Construct(aAttrs
& FILE_ATTRIBUTE_READONLY
);
994 attrs
.mHidden
.Construct(aAttrs
& FILE_ATTRIBUTE_HIDDEN
);
995 attrs
.mSystem
.Construct(aAttrs
& FILE_ATTRIBUTE_SYSTEM
);
997 promise
->MaybeResolve(attrs
);
999 [workerRef
, promise
= RefPtr
{promise
}](const IOError
& aErr
) {
1000 RejectJSPromise(promise
, aErr
);
1006 already_AddRefed
<Promise
> IOUtils::SetWindowsAttributes(
1007 GlobalObject
& aGlobal
, const nsAString
& aPath
,
1008 const WindowsFileAttributes
& aAttrs
, ErrorResult
& aError
) {
1009 return WithPromiseAndState(
1010 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1011 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1012 REJECT_IF_INIT_PATH_FAILED(
1013 file
, aPath
, promise
,
1014 "Could not set Windows file attributes on `%s'",
1015 NS_ConvertUTF16toUTF8(aPath
).get());
1017 uint32_t setAttrs
= 0;
1018 uint32_t clearAttrs
= 0;
1020 if (aAttrs
.mReadOnly
.WasPassed()) {
1021 if (aAttrs
.mReadOnly
.Value()) {
1022 setAttrs
|= FILE_ATTRIBUTE_READONLY
;
1024 clearAttrs
|= FILE_ATTRIBUTE_READONLY
;
1028 if (aAttrs
.mHidden
.WasPassed()) {
1029 if (aAttrs
.mHidden
.Value()) {
1030 setAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
1032 clearAttrs
|= FILE_ATTRIBUTE_HIDDEN
;
1036 if (aAttrs
.mSystem
.WasPassed()) {
1037 if (aAttrs
.mSystem
.Value()) {
1038 setAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
1040 clearAttrs
|= FILE_ATTRIBUTE_SYSTEM
;
1044 DispatchAndResolve
<Ok
>(
1045 state
->mEventQueue
, promise
,
1046 [file
= std::move(file
), setAttrs
, clearAttrs
]() {
1047 return SetWindowsAttributesSync(file
, setAttrs
, clearAttrs
);
1052 #elif defined(XP_MACOSX)
1055 already_AddRefed
<Promise
> IOUtils::HasMacXAttr(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(
1063 file
, aPath
, promise
,
1064 "Could not read the extended attribute `%s' from `%s'",
1065 PromiseFlatCString(aAttr
).get(),
1066 NS_ConvertUTF16toUTF8(aPath
).get());
1068 DispatchAndResolve
<bool>(
1069 state
->mEventQueue
, promise
,
1070 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1071 return HasMacXAttrSync(file
, attr
);
1077 already_AddRefed
<Promise
> IOUtils::GetMacXAttr(GlobalObject
& aGlobal
,
1078 const nsAString
& aPath
,
1079 const nsACString
& aAttr
,
1080 ErrorResult
& aError
) {
1081 return WithPromiseAndState(
1082 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1083 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1084 REJECT_IF_INIT_PATH_FAILED(
1085 file
, aPath
, promise
,
1086 "Could not read extended attribute `%s' from `%s'",
1087 PromiseFlatCString(aAttr
).get(),
1088 NS_ConvertUTF16toUTF8(aPath
).get());
1090 DispatchAndResolve
<nsTArray
<uint8_t>>(
1091 state
->mEventQueue
, promise
,
1092 [file
= std::move(file
), attr
= nsCString(aAttr
)]() {
1093 return GetMacXAttrSync(file
, attr
);
1099 already_AddRefed
<Promise
> IOUtils::SetMacXAttr(GlobalObject
& aGlobal
,
1100 const nsAString
& aPath
,
1101 const nsACString
& aAttr
,
1102 const Uint8Array
& aValue
,
1103 ErrorResult
& aError
) {
1104 return WithPromiseAndState(
1105 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1106 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1107 REJECT_IF_INIT_PATH_FAILED(
1108 file
, aPath
, promise
,
1109 "Could not set the extended attribute `%s' on `%s'",
1110 PromiseFlatCString(aAttr
).get(),
1111 NS_ConvertUTF16toUTF8(aPath
).get());
1113 nsTArray
<uint8_t> value
;
1115 if (!aValue
.AppendDataTo(value
)) {
1117 promise
, IOError(NS_ERROR_OUT_OF_MEMORY
,
1118 "Could not set extended attribute `%s' on `%s': "
1119 "could not allocate buffer",
1120 PromiseFlatCString(aAttr
).get(),
1121 file
->HumanReadablePath().get()));
1125 DispatchAndResolve
<Ok
>(state
->mEventQueue
, promise
,
1126 [file
= std::move(file
), attr
= nsCString(aAttr
),
1127 value
= std::move(value
)] {
1128 return SetMacXAttrSync(file
, attr
, value
);
1134 already_AddRefed
<Promise
> IOUtils::DelMacXAttr(GlobalObject
& aGlobal
,
1135 const nsAString
& aPath
,
1136 const nsACString
& aAttr
,
1137 ErrorResult
& aError
) {
1138 return WithPromiseAndState(
1139 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1140 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
1141 REJECT_IF_INIT_PATH_FAILED(
1142 file
, aPath
, promise
,
1143 "Could not delete extended attribute `%s' on `%s'",
1144 PromiseFlatCString(aAttr
).get(),
1145 NS_ConvertUTF16toUTF8(aPath
).get());
1147 DispatchAndResolve
<Ok
>(
1148 state
->mEventQueue
, promise
,
1149 [file
= std::move(file
), attr
= nsCString(aAttr
)] {
1150 return DelMacXAttrSync(file
, attr
);
1158 already_AddRefed
<Promise
> IOUtils::GetFile(
1159 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1160 ErrorResult
& aError
) {
1161 return WithPromiseAndState(
1162 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1163 ErrorResult joinErr
;
1164 nsCOMPtr
<nsIFile
> file
= PathUtils::Join(aComponents
, joinErr
);
1165 if (joinErr
.Failed()) {
1166 promise
->MaybeReject(std::move(joinErr
));
1170 nsCOMPtr
<nsIFile
> parent
;
1171 if (nsresult rv
= file
->GetParent(getter_AddRefs(parent
));
1173 RejectJSPromise(promise
, IOError(rv
,
1174 "Could not get nsIFile for `%s': "
1175 "could not get parent directory",
1176 file
->HumanReadablePath().get()));
1181 ->template Dispatch
<Ok
>([parent
= std::move(parent
)]() {
1182 return MakeDirectorySync(parent
, /* aCreateAncestors = */ true,
1183 /* aIgnoreExisting = */ true, 0755);
1186 GetCurrentSerialEventTarget(), __func__
,
1187 [file
= std::move(file
), promise
= RefPtr(promise
)](const Ok
&) {
1188 promise
->MaybeResolve(file
);
1190 [promise
= RefPtr(promise
)](const IOError
& err
) {
1191 RejectJSPromise(promise
, err
);
1197 already_AddRefed
<Promise
> IOUtils::GetDirectory(
1198 GlobalObject
& aGlobal
, const Sequence
<nsString
>& aComponents
,
1199 ErrorResult
& aError
) {
1200 return WithPromiseAndState(
1201 aGlobal
, aError
, [&](Promise
* promise
, auto& state
) {
1202 ErrorResult joinErr
;
1203 nsCOMPtr
<nsIFile
> dir
= PathUtils::Join(aComponents
, joinErr
);
1204 if (joinErr
.Failed()) {
1205 promise
->MaybeReject(std::move(joinErr
));
1210 ->template Dispatch
<Ok
>([dir
]() {
1211 return MakeDirectorySync(dir
, /* aCreateAncestors = */ true,
1212 /* aIgnoreExisting = */ true, 0755);
1215 GetCurrentSerialEventTarget(), __func__
,
1216 [dir
, promise
= RefPtr(promise
)](const Ok
&) {
1217 promise
->MaybeResolve(dir
);
1219 [promise
= RefPtr(promise
)](const IOError
& err
) {
1220 RejectJSPromise(promise
, err
);
1226 already_AddRefed
<Promise
> IOUtils::CreateJSPromise(GlobalObject
& aGlobal
,
1227 ErrorResult
& aError
) {
1228 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1229 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
1230 if (aError
.Failed()) {
1233 MOZ_ASSERT(promise
);
1234 return do_AddRef(promise
);
1238 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadSync(
1239 nsIFile
* aFile
, const uint64_t aOffset
, const Maybe
<uint32_t> aMaxBytes
,
1240 const bool aDecompress
, IOUtils::BufferKind aBufferKind
) {
1241 MOZ_ASSERT(!NS_IsMainThread());
1242 // This is checked in IOUtils::Read.
1243 MOZ_ASSERT(aMaxBytes
.isNothing() || !aDecompress
,
1244 "maxBytes and decompress are mutually exclusive");
1246 if (aOffset
> static_cast<uint64_t>(INT64_MAX
)) {
1248 IOError(NS_ERROR_ILLEGAL_INPUT
,
1249 "Could not read `%s': requested offset is too large (%" PRIu64
1251 aFile
->HumanReadablePath().get(), aOffset
, INT64_MAX
));
1254 const int64_t offset
= static_cast<int64_t>(aOffset
);
1256 RefPtr
<nsFileRandomAccessStream
> stream
= new nsFileRandomAccessStream();
1258 stream
->Init(aFile
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
1260 if (IsFileNotFound(rv
)) {
1261 return Err(IOError(rv
, "Could not open `%s': file does not exist",
1262 aFile
->HumanReadablePath().get()));
1265 IOError(rv
, "Could not open `%s'", aFile
->HumanReadablePath().get()));
1268 uint32_t bufSize
= 0;
1270 if (aMaxBytes
.isNothing()) {
1271 // Limitation: We cannot read more than the maximum size of a TypedArray
1272 // (UINT32_MAX bytes). Reject if we have been requested to
1273 // perform too large of a read.
1275 int64_t rawStreamSize
= -1;
1276 if (nsresult rv
= stream
->GetSize(&rawStreamSize
); NS_FAILED(rv
)) {
1278 IOError(NS_ERROR_FILE_ACCESS_DENIED
,
1279 "Could not open `%s': could not stat file or directory",
1280 aFile
->HumanReadablePath().get()));
1282 MOZ_RELEASE_ASSERT(rawStreamSize
>= 0);
1284 uint64_t streamSize
= static_cast<uint64_t>(rawStreamSize
);
1285 if (aOffset
>= streamSize
) {
1288 if (streamSize
- offset
> static_cast<int64_t>(UINT32_MAX
)) {
1289 return Err(IOError(NS_ERROR_FILE_TOO_BIG
,
1290 "Could not read `%s' with offset %" PRIu64
1291 ": file is too large (%" PRIu64
" bytes)",
1292 aFile
->HumanReadablePath().get(), offset
,
1296 bufSize
= static_cast<uint32_t>(streamSize
- offset
);
1299 bufSize
= aMaxBytes
.value();
1303 if (nsresult rv
= stream
->Seek(PR_SEEK_SET
, offset
); NS_FAILED(rv
)) {
1305 rv
, "Could not read `%s': could not seek to position %" PRId64
,
1306 aFile
->HumanReadablePath().get(), offset
));
1310 JsBuffer buffer
= JsBuffer::CreateEmpty(aBufferKind
);
1313 auto result
= JsBuffer::Create(aBufferKind
, bufSize
);
1314 if (result
.isErr()) {
1315 return Err(IOError::WithCause(result
.unwrapErr(), "Could not read `%s'",
1316 aFile
->HumanReadablePath().get()));
1318 buffer
= result
.unwrap();
1319 Span
<char> toRead
= buffer
.BeginWriting();
1321 // Read the file from disk.
1322 uint32_t totalRead
= 0;
1323 while (totalRead
!= bufSize
) {
1324 // Read no more than INT32_MAX on each call to stream->Read, otherwise it
1325 // returns an error.
1326 uint32_t bytesToReadThisChunk
=
1327 std::min
<uint32_t>(bufSize
- totalRead
, INT32_MAX
);
1328 uint32_t bytesRead
= 0;
1330 stream
->Read(toRead
.Elements(), bytesToReadThisChunk
, &bytesRead
);
1333 IOError(rv
, "Could not read `%s': encountered an unexpected error",
1334 aFile
->HumanReadablePath().get()));
1336 if (bytesRead
== 0) {
1339 totalRead
+= bytesRead
;
1340 toRead
= toRead
.From(bytesRead
);
1343 buffer
.SetLength(totalRead
);
1346 // Decompress the file contents, if required.
1349 MozLZ4::Decompress(AsBytes(buffer
.BeginReading()), aBufferKind
);
1350 if (result
.isErr()) {
1351 return Err(IOError::WithCause(result
.unwrapErr(), "Could not read `%s'",
1352 aFile
->HumanReadablePath().get()));
1357 return std::move(buffer
);
1361 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadUTF8Sync(
1362 nsIFile
* aFile
, bool aDecompress
) {
1363 auto result
= ReadSync(aFile
, 0, Nothing
{}, aDecompress
, BufferKind::String
);
1364 if (result
.isErr()) {
1365 return result
.propagateErr();
1368 JsBuffer buffer
= result
.unwrap();
1369 if (!IsUtf8(buffer
.BeginReading())) {
1370 return Err(IOError(NS_ERROR_FILE_CORRUPTED
,
1371 "Could not read `%s': file is not UTF-8 encoded",
1372 aFile
->HumanReadablePath().get()));
1379 Result
<uint32_t, IOUtils::IOError
> IOUtils::WriteSync(
1380 nsIFile
* aFile
, const Span
<const uint8_t>& aByteArray
,
1381 const IOUtils::InternalWriteOpts
& aOptions
) {
1382 MOZ_ASSERT(!NS_IsMainThread());
1384 nsIFile
* backupFile
= aOptions
.mBackupFile
;
1385 nsIFile
* tempFile
= aOptions
.mTmpFile
;
1387 bool exists
= false;
1388 IOUTILS_TRY_WITH_CONTEXT(
1389 aFile
->Exists(&exists
),
1390 "Could not write to `%s': could not stat file or directory",
1391 aFile
->HumanReadablePath().get());
1393 if (exists
&& aOptions
.mMode
== WriteMode::Create
) {
1394 return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS
,
1395 "Could not write to `%s': refusing to overwrite file, "
1396 "`mode' is not \"overwrite\"",
1397 aFile
->HumanReadablePath().get()));
1400 // If backupFile was specified, perform the backup as a move.
1401 if (exists
&& backupFile
) {
1402 // We copy `destFile` here to a new `nsIFile` because
1403 // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we
1404 // did not do this, we would end up having `destFile` point to the same
1405 // location as `backupFile`. Then, when we went to write to `destFile`, we
1406 // would end up overwriting `backupFile` and never actually write to the
1407 // file we were supposed to.
1408 nsCOMPtr
<nsIFile
> toMove
;
1409 MOZ_ALWAYS_SUCCEEDS(aFile
->Clone(getter_AddRefs(toMove
)));
1411 bool noOverwrite
= aOptions
.mMode
== WriteMode::Create
;
1413 if (auto result
= MoveSync(toMove
, backupFile
, noOverwrite
);
1415 return Err(IOError::WithCause(
1417 "Could not write to `%s': failed to back up source file",
1418 aFile
->HumanReadablePath().get()));
1422 // If tempFile was specified, we will write to there first, then perform a
1423 // move to ensure the file ends up at the final requested destination.
1427 writeFile
= tempFile
;
1432 int32_t flags
= PR_WRONLY
;
1434 switch (aOptions
.mMode
) {
1435 case WriteMode::Overwrite
:
1436 flags
|= PR_TRUNCATE
| PR_CREATE_FILE
;
1439 case WriteMode::Append
:
1443 case WriteMode::AppendOrCreate
:
1444 flags
|= PR_APPEND
| PR_CREATE_FILE
;
1447 case WriteMode::Create
:
1448 flags
|= PR_CREATE_FILE
| PR_EXCL
;
1452 MOZ_CRASH("IOUtils: unknown write mode");
1455 if (aOptions
.mFlush
) {
1459 // Try to perform the write and ensure that the file is closed before
1461 uint32_t totalWritten
= 0;
1463 // Compress the byte array if required.
1464 nsTArray
<uint8_t> compressed
;
1465 Span
<const char> bytes
;
1466 if (aOptions
.mCompress
) {
1467 auto result
= MozLZ4::Compress(aByteArray
);
1468 if (result
.isErr()) {
1469 return Err(IOError::WithCause(result
.unwrapErr(),
1470 "Could not write to `%s'",
1471 writeFile
->HumanReadablePath().get()));
1473 compressed
= result
.unwrap();
1474 bytes
= Span(reinterpret_cast<const char*>(compressed
.Elements()),
1475 compressed
.Length());
1477 bytes
= Span(reinterpret_cast<const char*>(aByteArray
.Elements()),
1478 aByteArray
.Length());
1481 RefPtr
<nsFileOutputStream
> stream
= new nsFileOutputStream();
1482 if (nsresult rv
= stream
->Init(writeFile
, flags
, 0666, 0); NS_FAILED(rv
)) {
1483 // Normalize platform-specific errors for opening a directory to an access
1485 if (rv
== nsresult::NS_ERROR_FILE_IS_DIRECTORY
) {
1486 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
1489 rv
, "Could not write to `%s': failed to open file for writing",
1490 writeFile
->HumanReadablePath().get()));
1493 // nsFileRandomAccessStream::Write uses PR_Write under the hood, which
1494 // accepts a *int32_t* for the chunk size.
1495 uint32_t chunkSize
= INT32_MAX
;
1496 Span
<const char> pendingBytes
= bytes
;
1498 while (pendingBytes
.Length() > 0) {
1499 if (pendingBytes
.Length() < chunkSize
) {
1500 chunkSize
= pendingBytes
.Length();
1503 uint32_t bytesWritten
= 0;
1505 stream
->Write(pendingBytes
.Elements(), chunkSize
, &bytesWritten
);
1507 return Err(IOError(rv
,
1508 "Could not write to `%s': failed to write chunk; "
1509 "the file may be corrupt",
1510 writeFile
->HumanReadablePath().get()));
1512 pendingBytes
= pendingBytes
.From(bytesWritten
);
1513 totalWritten
+= bytesWritten
;
1517 // If tempFile was passed, check destFile against writeFile and, if they
1518 // differ, the operation is finished by performing a move.
1520 nsAutoStringN
<256> destPath
;
1521 nsAutoStringN
<256> writePath
;
1523 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(destPath
));
1524 MOZ_ALWAYS_SUCCEEDS(writeFile
->GetPath(writePath
));
1526 // nsIFile::MoveToFollowingLinks will only update the path of the file if
1527 // the move succeeds.
1528 if (destPath
!= writePath
) {
1529 if (aOptions
.mTmpFile
) {
1531 if (nsresult rv
= aFile
->IsDirectory(&isDir
);
1532 NS_FAILED(rv
) && !IsFileNotFound(rv
)) {
1534 rv
, "Could not write to `%s': could not stat file or directory",
1535 aFile
->HumanReadablePath().get()));
1538 // If we attempt to write to a directory *without* a temp file, we get a
1539 // permission error.
1541 // However, if we are writing to a temp file first, when we copy the
1542 // temp file over the destination file, we actually end up copying it
1543 // inside the directory, which is not what we want. In this case, we are
1544 // just going to bail out early.
1546 return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED
,
1547 "Could not write to `%s': file is a directory",
1548 aFile
->HumanReadablePath().get()));
1552 if (auto result
= MoveSync(writeFile
, aFile
, /* aNoOverwrite = */ false);
1554 return Err(IOError::WithCause(
1556 "Could not write to `%s': could not move overwite with temporary "
1558 aFile
->HumanReadablePath().get()));
1562 return totalWritten
;
1566 Result
<Ok
, IOUtils::IOError
> IOUtils::MoveSync(nsIFile
* aSourceFile
,
1568 bool aNoOverwrite
) {
1569 MOZ_ASSERT(!NS_IsMainThread());
1571 // Ensure the source file exists before continuing. If it doesn't exist,
1572 // subsequent operations can fail in different ways on different platforms.
1573 bool srcExists
= false;
1574 IOUTILS_TRY_WITH_CONTEXT(
1575 aSourceFile
->Exists(&srcExists
),
1576 "Could not move `%s' to `%s': could not stat source file or directory",
1577 aSourceFile
->HumanReadablePath().get(),
1578 aDestFile
->HumanReadablePath().get());
1582 IOError(NS_ERROR_FILE_NOT_FOUND
,
1583 "Could not move `%s' to `%s': source file does not exist",
1584 aSourceFile
->HumanReadablePath().get(),
1585 aDestFile
->HumanReadablePath().get()));
1588 return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks
, "move", aSourceFile
,
1589 aDestFile
, aNoOverwrite
);
1593 Result
<Ok
, IOUtils::IOError
> IOUtils::CopySync(nsIFile
* aSourceFile
,
1597 MOZ_ASSERT(!NS_IsMainThread());
1599 // Ensure the source file exists before continuing. If it doesn't exist,
1600 // subsequent operations can fail in different ways on different platforms.
1602 IOUTILS_TRY_WITH_CONTEXT(
1603 aSourceFile
->Exists(&srcExists
),
1604 "Could not copy `%s' to `%s': could not stat source file or directory",
1605 aSourceFile
->HumanReadablePath().get(),
1606 aDestFile
->HumanReadablePath().get());
1610 IOError(NS_ERROR_FILE_NOT_FOUND
,
1611 "Could not copy `%s' to `%s': source file does not exist",
1612 aSourceFile
->HumanReadablePath().get(),
1613 aDestFile
->HumanReadablePath().get()));
1616 // If source is a directory, fail immediately unless the recursive option is
1618 bool srcIsDir
= false;
1619 IOUTILS_TRY_WITH_CONTEXT(
1620 aSourceFile
->IsDirectory(&srcIsDir
),
1621 "Could not copy `%s' to `%s': could not stat source file or directory",
1622 aSourceFile
->HumanReadablePath().get(),
1623 aDestFile
->HumanReadablePath().get());
1625 if (srcIsDir
&& !aRecursive
) {
1626 return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
,
1627 "Refused to copy directory `%s' to `%s': `recursive' is "
1629 aSourceFile
->HumanReadablePath().get(),
1630 aDestFile
->HumanReadablePath().get()));
1633 return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks
, "copy", aSourceFile
,
1634 aDestFile
, aNoOverwrite
);
1638 template <typename CopyOrMoveFn
>
1639 Result
<Ok
, IOUtils::IOError
> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod
,
1640 const char* aMethodName
,
1643 bool aNoOverwrite
) {
1644 MOZ_ASSERT(!NS_IsMainThread());
1646 // Case 1: Destination is an existing directory. Copy/move source into dest.
1647 bool destIsDir
= false;
1648 bool destExists
= true;
1650 nsresult rv
= aDest
->IsDirectory(&destIsDir
);
1651 if (NS_SUCCEEDED(rv
) && destIsDir
) {
1652 rv
= (aSource
->*aMethod
)(aDest
, u
""_ns
);
1653 if (NS_FAILED(rv
)) {
1654 return Err(IOError(rv
, "Could not %s `%s' to `%s'", aMethodName
,
1655 aSource
->HumanReadablePath().get(),
1656 aDest
->HumanReadablePath().get()));
1661 if (NS_FAILED(rv
)) {
1662 if (!IsFileNotFound(rv
)) {
1663 // It's ok if the dest file doesn't exist. Case 2 handles this below.
1664 // Bail out early for any other kind of error though.
1665 return Err(IOError(rv
, "Could not %s `%s' to `%s'", aMethodName
,
1666 aSource
->HumanReadablePath().get(),
1667 aDest
->HumanReadablePath().get()));
1672 // Case 2: Destination is a file which may or may not exist.
1673 // Try to copy or rename the source to the destination.
1674 // If the destination exists and the source is not a regular file,
1675 // then this may fail.
1676 if (aNoOverwrite
&& destExists
) {
1677 return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS
,
1678 "Could not %s `%s' to `%s': destination file exists and "
1679 "`noOverwrite' is true",
1680 aMethodName
, aSource
->HumanReadablePath().get(),
1681 aDest
->HumanReadablePath().get()));
1683 if (destExists
&& !destIsDir
) {
1684 // If the source file is a directory, but the target is a file, abort early.
1685 // Different implementations of |CopyTo| and |MoveTo| seem to handle this
1686 // error case differently (or not at all), so we explicitly handle it here.
1687 bool srcIsDir
= false;
1688 IOUTILS_TRY_WITH_CONTEXT(
1689 aSource
->IsDirectory(&srcIsDir
),
1690 "Could not %s `%s' to `%s': could not stat source file or directory",
1691 aMethodName
, aSource
->HumanReadablePath().get(),
1692 aDest
->HumanReadablePath().get());
1695 NS_ERROR_FILE_DESTINATION_NOT_DIR
,
1696 "Could not %s directory `%s' to `%s': destination is not a directory",
1697 aMethodName
, aSource
->HumanReadablePath().get(),
1698 aDest
->HumanReadablePath().get()));
1702 // We would have already thrown if the path was zero-length.
1703 nsAutoString destName
;
1704 MOZ_ALWAYS_SUCCEEDS(aDest
->GetLeafName(destName
));
1706 nsCOMPtr
<nsIFile
> destDir
;
1707 IOUTILS_TRY_WITH_CONTEXT(
1708 aDest
->GetParent(getter_AddRefs(destDir
)),
1709 "Could not %s `%s` to `%s': path `%s' does not have a parent",
1710 aMethodName
, aSource
->HumanReadablePath().get(),
1711 aDest
->HumanReadablePath().get(), aDest
->HumanReadablePath().get());
1713 // We know `destName` is a file and therefore must have a parent directory.
1714 MOZ_RELEASE_ASSERT(destDir
);
1716 // NB: if destDir doesn't exist, then |CopyToFollowingLinks| or
1717 // |MoveToFollowingLinks| will create it.
1718 rv
= (aSource
->*aMethod
)(destDir
, destName
);
1719 if (NS_FAILED(rv
)) {
1720 return Err(IOError(rv
, "Could not %s `%s' to `%s'", aMethodName
,
1721 aSource
->HumanReadablePath().get(),
1722 aDest
->HumanReadablePath().get()));
1728 Result
<Ok
, IOUtils::IOError
> IOUtils::RemoveSync(nsIFile
* aFile
,
1731 bool aRetryReadonly
) {
1732 MOZ_ASSERT(!NS_IsMainThread());
1734 // Prevent an unused variable warning.
1735 (void)aRetryReadonly
;
1737 nsresult rv
= aFile
->Remove(aRecursive
);
1738 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1741 if (NS_FAILED(rv
)) {
1742 if (IsFileNotFound(rv
)) {
1743 return Err(IOError(rv
, "Could not remove `%s': file does not exist",
1744 aFile
->HumanReadablePath().get()));
1746 if (rv
== NS_ERROR_FILE_DIR_NOT_EMPTY
) {
1747 return Err(IOError(rv
,
1748 "Could not remove `%s': the directory is not empty",
1749 aFile
->HumanReadablePath().get()));
1754 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
&& aRetryReadonly
) {
1756 SetWindowsAttributesSync(aFile
, 0, FILE_ATTRIBUTE_READONLY
);
1758 return Err(IOError::WithCause(
1760 "Could not remove `%s': could not clear readonly attribute",
1761 aFile
->HumanReadablePath().get()));
1763 return RemoveSync(aFile
, aIgnoreAbsent
, aRecursive
,
1764 /* aRetryReadonly = */ false);
1770 IOError(rv
, "Could not remove `%s'", aFile
->HumanReadablePath().get()));
1776 Result
<Ok
, IOUtils::IOError
> IOUtils::MakeDirectorySync(nsIFile
* aFile
,
1777 bool aCreateAncestors
,
1778 bool aIgnoreExisting
,
1780 MOZ_ASSERT(!NS_IsMainThread());
1782 nsCOMPtr
<nsIFile
> parent
;
1783 IOUTILS_TRY_WITH_CONTEXT(
1784 aFile
->GetParent(getter_AddRefs(parent
)),
1785 "Could not make directory `%s': could not get parent directory",
1786 aFile
->HumanReadablePath().get());
1788 // If we don't have a parent directory, we were called with a
1789 // root directory. If the directory doesn't already exist (e.g., asking
1790 // for a drive on Windows that does not exist), we will not be able to
1793 // Calling `nsLocalFile::Create()` on Windows can fail with
1794 // `NS_ERROR_ACCESS_DENIED` trying to create a root directory, but we
1795 // would rather the call succeed, so return early if the directory exists.
1797 // Otherwise, we fall through to `nsiFile::Create()` and let it fail there
1799 bool exists
= false;
1800 IOUTILS_TRY_WITH_CONTEXT(
1801 aFile
->Exists(&exists
),
1802 "Could not make directory `%s': could not stat file or directory",
1803 aFile
->HumanReadablePath().get());
1811 aFile
->Create(nsIFile::DIRECTORY_TYPE
, aMode
, !aCreateAncestors
);
1812 if (NS_FAILED(rv
)) {
1813 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1814 // NB: We may report a success only if the target is an existing
1815 // directory. We don't want to silence errors that occur if the target is
1816 // an existing file, since trying to create a directory where a regular
1817 // file exists may be indicative of a logic error.
1819 IOUTILS_TRY_WITH_CONTEXT(
1820 aFile
->IsDirectory(&isDirectory
),
1821 "Could not make directory `%s': could not stat file or directory",
1822 aFile
->HumanReadablePath().get());
1825 return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY
,
1826 "Could not create directory `%s': file exists and "
1827 "is not a directory",
1828 aFile
->HumanReadablePath().get()));
1830 // The directory exists.
1831 // The caller may suppress this error.
1832 if (aIgnoreExisting
) {
1835 // Otherwise, forward it.
1837 rv
, "Could not create directory `%s': directory already exists",
1838 aFile
->HumanReadablePath().get()));
1840 return Err(IOError(rv
, "Could not create directory `%s'",
1841 aFile
->HumanReadablePath().get()));
1846 Result
<IOUtils::InternalFileInfo
, IOUtils::IOError
> IOUtils::StatSync(
1848 MOZ_ASSERT(!NS_IsMainThread());
1850 InternalFileInfo info
;
1851 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(info
.mPath
));
1853 bool isRegular
= false;
1854 // IsFile will stat and cache info in the file object. If the file doesn't
1855 // exist, or there is an access error, we'll discover it here.
1856 // Any subsequent errors are unexpected and will just be forwarded.
1857 nsresult rv
= aFile
->IsFile(&isRegular
);
1858 if (NS_FAILED(rv
)) {
1859 if (IsFileNotFound(rv
)) {
1860 return Err(IOError(rv
, "Could not stat `%s': file does not exist",
1861 aFile
->HumanReadablePath().get()));
1864 IOError(rv
, "Could not stat `%s'", aFile
->HumanReadablePath().get()));
1867 // Now we can populate the info object by querying the file.
1868 info
.mType
= FileType::Regular
;
1871 IOUTILS_TRY_WITH_CONTEXT(aFile
->IsDirectory(&isDir
), "Could not stat `%s'",
1872 aFile
->HumanReadablePath().get());
1873 info
.mType
= isDir
? FileType::Directory
: FileType::Other
;
1877 if (info
.mType
== FileType::Regular
) {
1878 IOUTILS_TRY_WITH_CONTEXT(aFile
->GetFileSize(&size
), "Could not stat `%s'",
1879 aFile
->HumanReadablePath().get());
1883 PRTime creationTime
= 0;
1884 if (nsresult rv
= aFile
->GetCreationTime(&creationTime
); NS_SUCCEEDED(rv
)) {
1885 info
.mCreationTime
.emplace(static_cast<int64_t>(creationTime
));
1886 } else if (NS_FAILED(rv
) && rv
!= NS_ERROR_NOT_IMPLEMENTED
) {
1887 // This field is only supported on some platforms.
1889 IOError(rv
, "Could not stat `%s'", aFile
->HumanReadablePath().get()));
1892 PRTime lastAccessed
= 0;
1893 IOUTILS_TRY_WITH_CONTEXT(aFile
->GetLastAccessedTime(&lastAccessed
),
1894 "Could not stat `%s'",
1895 aFile
->HumanReadablePath().get());
1897 info
.mLastAccessed
= static_cast<int64_t>(lastAccessed
);
1899 PRTime lastModified
= 0;
1900 IOUTILS_TRY_WITH_CONTEXT(aFile
->GetLastModifiedTime(&lastModified
),
1901 "Could not stat `%s'",
1902 aFile
->HumanReadablePath().get());
1904 info
.mLastModified
= static_cast<int64_t>(lastModified
);
1906 IOUTILS_TRY_WITH_CONTEXT(aFile
->GetPermissions(&info
.mPermissions
),
1907 "Could not stat `%s'",
1908 aFile
->HumanReadablePath().get());
1914 Result
<int64_t, IOUtils::IOError
> IOUtils::SetTimeSync(
1915 nsIFile
* aFile
, IOUtils::SetTimeFn aSetTimeFn
, int64_t aNewTime
) {
1916 MOZ_ASSERT(!NS_IsMainThread());
1918 // nsIFile::SetLastModifiedTime will *not* do what is expected when passed 0
1919 // as an argument. Rather than setting the time to 0, it will recalculate the
1920 // system time and set it to that value instead. We explicit forbid this,
1921 // because this side effect is surprising.
1923 // If it ever becomes possible to set a file time to 0, this check should be
1924 // removed, though this use case seems rare.
1925 if (aNewTime
== 0) {
1927 NS_ERROR_ILLEGAL_VALUE
,
1928 "Refusing to set modification time of `%s' to 0: to use the current "
1929 "system time, call `setModificationTime' with no arguments",
1930 aFile
->HumanReadablePath().get()));
1933 nsresult rv
= (aFile
->*aSetTimeFn
)(aNewTime
);
1935 if (NS_FAILED(rv
)) {
1936 if (IsFileNotFound(rv
)) {
1938 rv
, "Could not set modification time of `%s': file does not exist",
1939 aFile
->HumanReadablePath().get()));
1941 return Err(IOError(rv
, "Could not set modification time of `%s'",
1942 aFile
->HumanReadablePath().get()));
1948 Result
<nsTArray
<nsString
>, IOUtils::IOError
> IOUtils::GetChildrenSync(
1949 nsIFile
* aFile
, bool aIgnoreAbsent
) {
1950 MOZ_ASSERT(!NS_IsMainThread());
1952 nsTArray
<nsString
> children
;
1953 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
1954 nsresult rv
= aFile
->GetDirectoryEntries(getter_AddRefs(iter
));
1955 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1958 if (NS_FAILED(rv
)) {
1959 if (IsFileNotFound(rv
)) {
1961 rv
, "Could not get children of `%s': directory does not exist",
1962 aFile
->HumanReadablePath().get()));
1964 if (IsNotDirectory(rv
)) {
1966 IOError(rv
, "Could not get children of `%s': file is not a directory",
1967 aFile
->HumanReadablePath().get()));
1969 return Err(IOError(rv
, "Could not get children of `%s'",
1970 aFile
->HumanReadablePath().get()));
1973 bool hasMoreElements
= false;
1974 IOUTILS_TRY_WITH_CONTEXT(
1975 iter
->HasMoreElements(&hasMoreElements
),
1976 "Could not get children of `%s': could not iterate children",
1977 aFile
->HumanReadablePath().get());
1979 while (hasMoreElements
) {
1980 nsCOMPtr
<nsIFile
> child
;
1981 IOUTILS_TRY_WITH_CONTEXT(
1982 iter
->GetNextFile(getter_AddRefs(child
)),
1983 "Could not get children of `%s': could not retrieve child file",
1984 aFile
->HumanReadablePath().get());
1988 MOZ_ALWAYS_SUCCEEDS(child
->GetPath(path
));
1989 children
.AppendElement(path
);
1992 IOUTILS_TRY_WITH_CONTEXT(
1993 iter
->HasMoreElements(&hasMoreElements
),
1994 "Could not get children of `%s': could not iterate children",
1995 aFile
->HumanReadablePath().get());
2002 Result
<Ok
, IOUtils::IOError
> IOUtils::SetPermissionsSync(
2003 nsIFile
* aFile
, const uint32_t aPermissions
) {
2004 MOZ_ASSERT(!NS_IsMainThread());
2006 IOUTILS_TRY_WITH_CONTEXT(aFile
->SetPermissions(aPermissions
),
2007 "Could not set permissions on `%s'",
2008 aFile
->HumanReadablePath().get());
2014 Result
<bool, IOUtils::IOError
> IOUtils::ExistsSync(nsIFile
* aFile
) {
2015 MOZ_ASSERT(!NS_IsMainThread());
2017 bool exists
= false;
2018 IOUTILS_TRY_WITH_CONTEXT(aFile
->Exists(&exists
), "Could not stat `%s'",
2019 aFile
->HumanReadablePath().get());
2025 Result
<nsString
, IOUtils::IOError
> IOUtils::CreateUniqueSync(
2026 nsIFile
* aFile
, const uint32_t aFileType
, const uint32_t aPermissions
) {
2027 MOZ_ASSERT(!NS_IsMainThread());
2029 if (nsresult rv
= aFile
->CreateUnique(aFileType
, aPermissions
);
2031 nsCOMPtr
<nsIFile
> aParent
= nullptr;
2032 MOZ_ALWAYS_SUCCEEDS(aFile
->GetParent(getter_AddRefs(aParent
)));
2033 MOZ_RELEASE_ASSERT(aParent
);
2035 IOError(rv
, "Could not create unique %s in `%s'",
2036 aFileType
== nsIFile::NORMAL_FILE_TYPE
? "file" : "directory",
2037 aParent
->HumanReadablePath().get()));
2041 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(path
));
2047 Result
<nsCString
, IOUtils::IOError
> IOUtils::ComputeHexDigestSync(
2048 nsIFile
* aFile
, const HashAlgorithm aAlgorithm
) {
2049 static constexpr size_t BUFFER_SIZE
= 8192;
2052 switch (aAlgorithm
) {
2053 case HashAlgorithm::Sha256
:
2054 alg
= SEC_OID_SHA256
;
2057 case HashAlgorithm::Sha384
:
2058 alg
= SEC_OID_SHA384
;
2061 case HashAlgorithm::Sha512
:
2062 alg
= SEC_OID_SHA512
;
2066 MOZ_RELEASE_ASSERT(false, "Unexpected HashAlgorithm");
2070 if (nsresult rv
= digest
.Begin(alg
); NS_FAILED(rv
)) {
2071 return Err(IOError(rv
, "Could not hash `%s': could not create digest",
2072 aFile
->HumanReadablePath().get()));
2075 RefPtr
<nsIInputStream
> stream
;
2076 if (nsresult rv
= NS_NewLocalFileInputStream(getter_AddRefs(stream
), aFile
);
2078 return Err(IOError(rv
, "Could not hash `%s': could not open for reading",
2079 aFile
->HumanReadablePath().get()));
2082 char buffer
[BUFFER_SIZE
];
2085 if (nsresult rv
= stream
->Read(buffer
, BUFFER_SIZE
, &read
); NS_FAILED(rv
)) {
2086 return Err(IOError(rv
,
2087 "Could not hash `%s': encountered an unexpected error "
2088 "while reading file",
2089 aFile
->HumanReadablePath().get()));
2096 digest
.Update(reinterpret_cast<unsigned char*>(buffer
), read
);
2098 return Err(IOError(rv
, "Could not hash `%s': could not update digest",
2099 aFile
->HumanReadablePath().get()));
2103 AutoTArray
<uint8_t, SHA512_LENGTH
> rawDigest
;
2104 if (nsresult rv
= digest
.End(rawDigest
); NS_FAILED(rv
)) {
2105 return Err(IOError(rv
, "Could not hash `%s': could not compute digest",
2106 aFile
->HumanReadablePath().get()));
2109 nsCString hexDigest
;
2110 if (!hexDigest
.SetCapacity(2 * rawDigest
.Length(), fallible
)) {
2111 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
,
2112 "Could not hash `%s': out of memory",
2113 aFile
->HumanReadablePath().get()));
2116 const char HEX
[] = "0123456789abcdef";
2117 for (uint8_t b
: rawDigest
) {
2118 hexDigest
.Append(HEX
[(b
>> 4) & 0xF]);
2119 hexDigest
.Append(HEX
[b
& 0xF]);
2127 Result
<uint32_t, IOUtils::IOError
> IOUtils::GetWindowsAttributesSync(
2129 MOZ_ASSERT(!NS_IsMainThread());
2133 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
2136 if (nsresult rv
= file
->GetWindowsFileAttributes(&attrs
); NS_FAILED(rv
)) {
2137 return Err(IOError(rv
, "Could not get Windows file attributes for `%s'",
2138 aFile
->HumanReadablePath().get()));
2143 Result
<Ok
, IOUtils::IOError
> IOUtils::SetWindowsAttributesSync(
2144 nsIFile
* aFile
, const uint32_t aSetAttrs
, const uint32_t aClearAttrs
) {
2145 MOZ_ASSERT(!NS_IsMainThread());
2147 nsCOMPtr
<nsILocalFileWin
> file
= do_QueryInterface(aFile
);
2150 if (nsresult rv
= file
->SetWindowsFileAttributes(aSetAttrs
, aClearAttrs
);
2152 return Err(IOError(rv
, "Could not set Windows file attributes for `%s'",
2153 aFile
->HumanReadablePath().get()));
2159 #elif defined(XP_MACOSX)
2162 Result
<bool, IOUtils::IOError
> IOUtils::HasMacXAttrSync(
2163 nsIFile
* aFile
, const nsCString
& aAttr
) {
2164 MOZ_ASSERT(!NS_IsMainThread());
2166 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2169 bool hasAttr
= false;
2170 if (nsresult rv
= file
->HasXAttr(aAttr
, &hasAttr
); NS_FAILED(rv
)) {
2171 return Err(IOError(rv
, "Could not read extended attribute `%s' from `%s'",
2172 aAttr
.get(), aFile
->HumanReadablePath().get()));
2179 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::GetMacXAttrSync(
2180 nsIFile
* aFile
, const nsCString
& aAttr
) {
2181 MOZ_ASSERT(!NS_IsMainThread());
2183 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2186 nsTArray
<uint8_t> value
;
2187 if (nsresult rv
= file
->GetXAttr(aAttr
, value
); NS_FAILED(rv
)) {
2188 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2189 return Err(IOError(rv
,
2190 "Could not get extended attribute `%s' from `%s': the "
2191 "file does not have the attribute",
2192 aAttr
.get(), aFile
->HumanReadablePath().get()));
2195 return Err(IOError(rv
, "Could not read extended attribute `%s' from `%s'",
2196 aAttr
.get(), aFile
->HumanReadablePath().get()));
2203 Result
<Ok
, IOUtils::IOError
> IOUtils::SetMacXAttrSync(
2204 nsIFile
* aFile
, const nsCString
& aAttr
, const nsTArray
<uint8_t>& aValue
) {
2205 MOZ_ASSERT(!NS_IsMainThread());
2207 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2210 if (nsresult rv
= file
->SetXAttr(aAttr
, aValue
); NS_FAILED(rv
)) {
2211 return Err(IOError(rv
, "Could not set extended attribute `%s' on `%s'",
2212 aAttr
.get(), aFile
->HumanReadablePath().get()));
2219 Result
<Ok
, IOUtils::IOError
> IOUtils::DelMacXAttrSync(nsIFile
* aFile
,
2220 const nsCString
& aAttr
) {
2221 MOZ_ASSERT(!NS_IsMainThread());
2223 nsCOMPtr
<nsILocalFileMac
> file
= do_QueryInterface(aFile
);
2226 if (nsresult rv
= file
->DelXAttr(aAttr
); NS_FAILED(rv
)) {
2227 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2228 return Err(IOError(rv
,
2229 "Could not delete extended attribute `%s' from "
2230 "`%s': the file does not have the attribute",
2231 aAttr
.get(), aFile
->HumanReadablePath().get()));
2234 return Err(IOError(rv
, "Could not delete extended attribute `%s' from `%s'",
2235 aAttr
.get(), aFile
->HumanReadablePath().get()));
2244 void IOUtils::GetProfileBeforeChange(GlobalObject
& aGlobal
,
2245 JS::MutableHandle
<JS::Value
> aClient
,
2247 return GetShutdownClient(aGlobal
, aClient
, aRv
,
2248 ShutdownPhase::ProfileBeforeChange
);
2252 void IOUtils::GetSendTelemetry(GlobalObject
& aGlobal
,
2253 JS::MutableHandle
<JS::Value
> aClient
,
2255 return GetShutdownClient(aGlobal
, aClient
, aRv
, ShutdownPhase::SendTelemetry
);
2259 * Assert that the given phase has a shutdown client exposed by IOUtils
2261 * There is no shutdown client exposed for XpcomWillShutdown.
2263 static void AssertHasShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2264 MOZ_RELEASE_ASSERT(aPhase
>= IOUtils::ShutdownPhase::ProfileBeforeChange
&&
2265 aPhase
< IOUtils::ShutdownPhase::XpcomWillShutdown
);
2269 void IOUtils::GetShutdownClient(GlobalObject
& aGlobal
,
2270 JS::MutableHandle
<JS::Value
> aClient
,
2272 const IOUtils::ShutdownPhase aPhase
) {
2273 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
2274 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2275 AssertHasShutdownClient(aPhase
);
2277 if (auto state
= GetState()) {
2278 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
!=
2279 ShutdownBlockerStatus::Uninitialized
);
2281 if (state
.ref()->mBlockerStatus
== ShutdownBlockerStatus::Failed
) {
2282 aRv
.ThrowAbortError("IOUtils: could not register shutdown blockers");
2286 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
==
2287 ShutdownBlockerStatus::Initialized
);
2288 auto result
= state
.ref()->mEventQueue
->GetShutdownClient(aPhase
);
2289 if (result
.isErr()) {
2290 aRv
.ThrowAbortError("IOUtils: could not get shutdown client");
2294 RefPtr
<nsIAsyncShutdownClient
> client
= result
.unwrap();
2295 MOZ_RELEASE_ASSERT(client
);
2296 if (nsresult rv
= client
->GetJsclient(aClient
); NS_FAILED(rv
)) {
2297 aRv
.ThrowAbortError("IOUtils: Could not get shutdown jsclient");
2302 aRv
.ThrowAbortError(
2303 "IOUtils: profileBeforeChange phase has already finished");
2307 Maybe
<IOUtils::StateMutex::AutoLock
> IOUtils::GetState() {
2308 auto state
= sState
.Lock();
2309 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2313 if (state
->mQueueStatus
== EventQueueStatus::Uninitialized
) {
2314 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2315 state
->mEventQueue
= new EventQueue();
2316 state
->mQueueStatus
= EventQueueStatus::Initialized
;
2318 MOZ_RELEASE_ASSERT(state
->mBlockerStatus
==
2319 ShutdownBlockerStatus::Uninitialized
);
2322 if (NS_IsMainThread() &&
2323 state
->mBlockerStatus
== ShutdownBlockerStatus::Uninitialized
) {
2324 state
->SetShutdownHooks();
2327 return Some(std::move(state
));
2330 IOUtils::EventQueue::EventQueue() {
2331 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
2332 "IOUtils::EventQueue", getter_AddRefs(mBackgroundEventTarget
)));
2334 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2337 void IOUtils::State::SetShutdownHooks() {
2338 if (mBlockerStatus
!= ShutdownBlockerStatus::Uninitialized
) {
2342 if (NS_WARN_IF(NS_FAILED(mEventQueue
->SetShutdownHooks()))) {
2343 mBlockerStatus
= ShutdownBlockerStatus::Failed
;
2345 mBlockerStatus
= ShutdownBlockerStatus::Initialized
;
2348 if (mBlockerStatus
!= ShutdownBlockerStatus::Initialized
) {
2349 NS_WARNING("IOUtils: could not register shutdown blockers.");
2353 nsresult
IOUtils::EventQueue::SetShutdownHooks() {
2354 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2356 constexpr static auto STACK
= u
"IOUtils::EventQueue::SetShutdownHooks"_ns
;
2357 constexpr static auto FILE = NS_LITERAL_STRING_FROM_CSTRING(__FILE__
);
2359 nsCOMPtr
<nsIAsyncShutdownService
> svc
= services::GetAsyncShutdownService();
2361 return NS_ERROR_NOT_AVAILABLE
;
2364 nsCOMPtr
<nsIAsyncShutdownBlocker
> profileBeforeChangeBlocker
;
2366 // Create a shutdown blocker for the profile-before-change phase.
2368 profileBeforeChangeBlocker
=
2369 new IOUtilsShutdownBlocker(ShutdownPhase::ProfileBeforeChange
);
2371 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2372 MOZ_TRY(svc
->GetProfileBeforeChange(getter_AddRefs(globalClient
)));
2373 MOZ_RELEASE_ASSERT(globalClient
);
2375 MOZ_TRY(globalClient
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
,
2379 // Create the shutdown barrier for profile-before-change so that consumers can
2380 // register shutdown blockers.
2382 // The blocker we just created will wait for all clients registered on this
2383 // barrier to finish.
2385 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2387 // It is okay for this to fail. The created shutdown blocker won't await
2388 // anything and shutdown will proceed.
2389 MOZ_TRY(svc
->MakeBarrier(
2390 u
"IOUtils: waiting for profileBeforeChange IO to complete"_ns
,
2391 getter_AddRefs(barrier
)));
2392 MOZ_RELEASE_ASSERT(barrier
);
2394 mBarriers
[ShutdownPhase::ProfileBeforeChange
] = std::move(barrier
);
2397 // Create a shutdown blocker for the profile-before-change-telemetry phase.
2398 nsCOMPtr
<nsIAsyncShutdownBlocker
> sendTelemetryBlocker
;
2400 sendTelemetryBlocker
=
2401 new IOUtilsShutdownBlocker(ShutdownPhase::SendTelemetry
);
2403 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2404 MOZ_TRY(svc
->GetSendTelemetry(getter_AddRefs(globalClient
)));
2405 MOZ_RELEASE_ASSERT(globalClient
);
2408 globalClient
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
, STACK
));
2411 // Create the shutdown barrier for profile-before-change-telemetry so that
2412 // consumers can register shutdown blockers.
2414 // The blocker we just created will wait for all clients registered on this
2415 // barrier to finish.
2417 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2419 MOZ_TRY(svc
->MakeBarrier(
2420 u
"IOUtils: waiting for sendTelemetry IO to complete"_ns
,
2421 getter_AddRefs(barrier
)));
2422 MOZ_RELEASE_ASSERT(barrier
);
2424 // Add a blocker on the previous shutdown phase.
2425 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2426 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2429 client
->AddBlocker(profileBeforeChangeBlocker
, FILE, __LINE__
, STACK
));
2431 mBarriers
[ShutdownPhase::SendTelemetry
] = std::move(barrier
);
2434 // Create a shutdown blocker for the xpcom-will-shutdown phase.
2436 nsCOMPtr
<nsIAsyncShutdownClient
> globalClient
;
2437 MOZ_TRY(svc
->GetXpcomWillShutdown(getter_AddRefs(globalClient
)));
2438 MOZ_RELEASE_ASSERT(globalClient
);
2440 nsCOMPtr
<nsIAsyncShutdownBlocker
> blocker
=
2441 new IOUtilsShutdownBlocker(ShutdownPhase::XpcomWillShutdown
);
2442 MOZ_TRY(globalClient
->AddBlocker(
2443 blocker
, FILE, __LINE__
, u
"IOUtils::EventQueue::SetShutdownHooks"_ns
));
2446 // Create a shutdown barrier for the xpcom-will-shutdown phase.
2448 // The blocker we just created will wait for all clients registered on this
2449 // barrier to finish.
2451 // The only client registered on this barrier should be a blocker for the
2452 // previous phase. This is to ensure that all shutdown IO happens when
2453 // shutdown phases do not happen (e.g., in xpcshell tests where
2454 // profile-before-change does not occur).
2456 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2458 MOZ_TRY(svc
->MakeBarrier(
2459 u
"IOUtils: waiting for xpcomWillShutdown IO to complete"_ns
,
2460 getter_AddRefs(barrier
)));
2461 MOZ_RELEASE_ASSERT(barrier
);
2463 // Add a blocker on the previous shutdown phase.
2464 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2465 MOZ_TRY(barrier
->GetClient(getter_AddRefs(client
)));
2467 client
->AddBlocker(sendTelemetryBlocker
, FILE, __LINE__
,
2468 u
"IOUtils::EventQueue::SetShutdownHooks"_ns
);
2470 mBarriers
[ShutdownPhase::XpcomWillShutdown
] = std::move(barrier
);
2476 template <typename OkT
, typename Fn
>
2477 RefPtr
<IOUtils::IOPromise
<OkT
>> IOUtils::EventQueue::Dispatch(Fn aFunc
) {
2478 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
2481 MakeRefPtr
<typename
IOUtils::IOPromise
<OkT
>::Private
>(__func__
);
2482 mBackgroundEventTarget
->Dispatch(
2483 NS_NewRunnableFunction("IOUtils::EventQueue::Dispatch",
2484 [promise
, func
= std::move(aFunc
)] {
2485 Result
<OkT
, IOError
> result
= func();
2486 if (result
.isErr()) {
2487 promise
->Reject(result
.unwrapErr(), __func__
);
2489 promise
->Resolve(result
.unwrap(), __func__
);
2492 NS_DISPATCH_EVENT_MAY_BLOCK
);
2496 Result
<already_AddRefed
<nsIAsyncShutdownBarrier
>, nsresult
>
2497 IOUtils::EventQueue::GetShutdownBarrier(const IOUtils::ShutdownPhase aPhase
) {
2498 if (!mBarriers
[aPhase
]) {
2499 return Err(NS_ERROR_NOT_AVAILABLE
);
2502 return do_AddRef(mBarriers
[aPhase
]);
2505 Result
<already_AddRefed
<nsIAsyncShutdownClient
>, nsresult
>
2506 IOUtils::EventQueue::GetShutdownClient(const IOUtils::ShutdownPhase aPhase
) {
2507 AssertHasShutdownClient(aPhase
);
2509 if (!mBarriers
[aPhase
]) {
2510 return Err(NS_ERROR_NOT_AVAILABLE
);
2513 nsCOMPtr
<nsIAsyncShutdownClient
> client
;
2514 MOZ_TRY(mBarriers
[aPhase
]->GetClient(getter_AddRefs(client
)));
2516 return do_AddRef(client
);
2520 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::MozLZ4::Compress(
2521 Span
<const uint8_t> aUncompressed
) {
2522 nsTArray
<uint8_t> result
;
2523 size_t worstCaseSize
=
2524 Compression::LZ4::maxCompressedSize(aUncompressed
.Length()) + HEADER_SIZE
;
2525 if (!result
.SetCapacity(worstCaseSize
, fallible
)) {
2526 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
,
2527 "could not allocate buffer to compress data"_ns
));
2529 result
.AppendElements(Span(MAGIC_NUMBER
.data(), MAGIC_NUMBER
.size()));
2530 std::array
<uint8_t, sizeof(uint32_t)> contentSizeBytes
{};
2531 LittleEndian::writeUint32(contentSizeBytes
.data(), aUncompressed
.Length());
2532 result
.AppendElements(Span(contentSizeBytes
.data(), contentSizeBytes
.size()));
2534 if (aUncompressed
.Length() == 0) {
2535 // Don't try to compress an empty buffer.
2536 // Just return the correctly formed header.
2537 result
.SetLength(HEADER_SIZE
);
2541 size_t compressed
= Compression::LZ4::compress(
2542 reinterpret_cast<const char*>(aUncompressed
.Elements()),
2543 aUncompressed
.Length(),
2544 reinterpret_cast<char*>(result
.Elements()) + HEADER_SIZE
);
2546 return Err(IOError(NS_ERROR_UNEXPECTED
, "could not compress data"_ns
));
2548 result
.SetLength(HEADER_SIZE
+ compressed
);
2553 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::MozLZ4::Decompress(
2554 Span
<const uint8_t> aFileContents
, IOUtils::BufferKind aBufferKind
) {
2555 if (aFileContents
.LengthBytes() < HEADER_SIZE
) {
2556 return Err(IOError(NS_ERROR_FILE_CORRUPTED
,
2557 "could not decompress file: buffer is too small"_ns
));
2559 auto header
= aFileContents
.To(HEADER_SIZE
);
2560 if (!std::equal(std::begin(MAGIC_NUMBER
), std::end(MAGIC_NUMBER
),
2561 std::begin(header
))) {
2564 for (; i
< header
.Length() - 1; ++i
) {
2565 magicStr
.AppendPrintf("%02X ", header
.at(i
));
2567 magicStr
.AppendPrintf("%02X", header
.at(i
));
2569 return Err(IOError(NS_ERROR_FILE_CORRUPTED
,
2570 "could not decompress file: invalid LZ4 header: wrong "
2571 "magic number: `%s'",
2574 size_t numBytes
= sizeof(uint32_t);
2575 Span
<const uint8_t> sizeBytes
= header
.Last(numBytes
);
2576 uint32_t expectedDecompressedSize
=
2577 LittleEndian::readUint32(sizeBytes
.data());
2578 if (expectedDecompressedSize
== 0) {
2579 return JsBuffer::CreateEmpty(aBufferKind
);
2581 auto contents
= aFileContents
.From(HEADER_SIZE
);
2582 auto result
= JsBuffer::Create(aBufferKind
, expectedDecompressedSize
);
2583 if (result
.isErr()) {
2584 return Err(IOError::WithCause(
2586 "could not decompress file: could not allocate buffer"_ns
));
2589 JsBuffer decompressed
= result
.unwrap();
2590 size_t actualSize
= 0;
2591 if (!Compression::LZ4::decompress(
2592 reinterpret_cast<const char*>(contents
.Elements()), contents
.Length(),
2593 reinterpret_cast<char*>(decompressed
.Elements()),
2594 expectedDecompressedSize
, &actualSize
)) {
2596 IOError(NS_ERROR_FILE_CORRUPTED
,
2597 "could not decompress file: the file may be corrupt"_ns
));
2599 decompressed
.SetLength(actualSize
);
2600 return decompressed
;
2603 NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker
, nsIAsyncShutdownBlocker
,
2604 nsIAsyncShutdownCompletionCallback
);
2606 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetName(nsAString
& aName
) {
2607 aName
= u
"IOUtils Blocker ("_ns
;
2608 aName
.Append(PHASE_NAMES
[mPhase
]);
2614 NS_IMETHODIMP
IOUtilsShutdownBlocker::BlockShutdown(
2615 nsIAsyncShutdownClient
* aBarrierClient
) {
2616 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2617 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2619 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2621 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
2624 auto state
= IOUtils::sState
.Lock();
2625 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
2626 // If the previous blockers have already run, then the event queue is
2627 // already torn down and we have nothing to do.
2629 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2630 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
2632 Unused
<< NS_WARN_IF(NS_FAILED(aBarrierClient
->RemoveBlocker(this)));
2633 mParentClient
= nullptr;
2638 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
2640 mParentClient
= aBarrierClient
;
2642 barrier
= state
->mEventQueue
->GetShutdownBarrier(mPhase
).unwrapOr(nullptr);
2645 // We cannot barrier->Wait() while holding the mutex because it will lead to
2647 if (!barrier
|| NS_WARN_IF(NS_FAILED(barrier
->Wait(this)))) {
2648 // If we don't have a barrier, we still need to flush the IOUtils event
2649 // queue and disable task submission.
2651 // Likewise, if waiting on the barrier failed, we are going to make our best
2652 // attempt to clean up.
2659 NS_IMETHODIMP
IOUtilsShutdownBlocker::Done() {
2660 using EventQueueStatus
= IOUtils::EventQueueStatus
;
2661 using ShutdownPhase
= IOUtils::ShutdownPhase
;
2663 MOZ_RELEASE_ASSERT(NS_IsMainThread());
2665 bool didFlush
= false;
2668 auto state
= IOUtils::sState
.Lock();
2670 if (state
->mEventQueue
) {
2671 MOZ_RELEASE_ASSERT(state
->mQueueStatus
== EventQueueStatus::Initialized
);
2673 // This method is called once we have served all shutdown clients. Now we
2674 // flush the remaining IO queue. This ensures any straggling IO that was
2675 // not part of the shutdown blocker finishes before we move to the next
2677 state
->mEventQueue
->Dispatch
<Ok
>([]() { return Ok
{}; })
2678 ->Then(GetMainThreadSerialEventTarget(), __func__
,
2679 [self
= RefPtr(this)]() { self
->OnFlush(); });
2681 // And if we're the last shutdown phase to allow IO, disable the event
2682 // queue to disallow further IO requests.
2683 if (mPhase
>= LAST_IO_PHASE
) {
2684 state
->mQueueStatus
= EventQueueStatus::Shutdown
;
2691 // If we have already shut down the event loop, then call OnFlush to stop
2692 // blocking our parent shutdown client.
2694 MOZ_RELEASE_ASSERT(mPhase
== ShutdownPhase::XpcomWillShutdown
);
2701 void IOUtilsShutdownBlocker::OnFlush() {
2702 if (mParentClient
) {
2703 (void)NS_WARN_IF(NS_FAILED(mParentClient
->RemoveBlocker(this)));
2704 mParentClient
= nullptr;
2706 // If we are past the last shutdown phase that allows IO,
2707 // we can shutdown the event queue here because no additional IO requests
2708 // will be allowed (see |Done()|).
2709 if (mPhase
>= LAST_IO_PHASE
) {
2710 auto state
= IOUtils::sState
.Lock();
2711 if (state
->mEventQueue
) {
2712 state
->mEventQueue
= nullptr;
2718 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetState(nsIPropertyBag
** aState
) {
2722 Result
<IOUtils::InternalWriteOpts
, IOUtils::IOError
>
2723 IOUtils::InternalWriteOpts::FromBinding(const WriteOptions
& aOptions
) {
2724 InternalWriteOpts opts
;
2725 opts
.mFlush
= aOptions
.mFlush
;
2726 opts
.mMode
= aOptions
.mMode
;
2728 if (aOptions
.mBackupFile
.WasPassed()) {
2729 opts
.mBackupFile
= new nsLocalFile();
2730 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mBackupFile
,
2731 aOptions
.mBackupFile
.Value());
2733 return Err(IOUtils::IOError(
2734 rv
, "Could not parse path of backupFile `%s'",
2735 NS_ConvertUTF16toUTF8(aOptions
.mBackupFile
.Value()).get()));
2739 if (aOptions
.mTmpPath
.WasPassed()) {
2740 opts
.mTmpFile
= new nsLocalFile();
2741 if (nsresult rv
= PathUtils::InitFileWithPath(opts
.mTmpFile
,
2742 aOptions
.mTmpPath
.Value());
2744 return Err(IOUtils::IOError(
2745 rv
, "Could not parse path of temp file `%s'",
2746 NS_ConvertUTF16toUTF8(aOptions
.mTmpPath
.Value()).get()));
2750 opts
.mCompress
= aOptions
.mCompress
;
2755 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::JsBuffer::Create(
2756 IOUtils::BufferKind aBufferKind
, size_t aCapacity
) {
2757 JsBuffer
buffer(aBufferKind
, aCapacity
);
2758 if (aCapacity
!= 0 && !buffer
.mBuffer
) {
2759 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
, "Could not allocate buffer"_ns
));
2765 IOUtils::JsBuffer
IOUtils::JsBuffer::CreateEmpty(
2766 IOUtils::BufferKind aBufferKind
) {
2767 JsBuffer
buffer(aBufferKind
, 0);
2768 MOZ_RELEASE_ASSERT(buffer
.mBuffer
== nullptr);
2772 IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind
, size_t aCapacity
)
2773 : mBufferKind(aBufferKind
), mCapacity(aCapacity
), mLength(0) {
2775 if (aBufferKind
== BufferKind::String
) {
2776 mBuffer
= JS::UniqueChars(
2777 js_pod_arena_malloc
<char>(js::StringBufferArena
, mCapacity
));
2779 MOZ_RELEASE_ASSERT(aBufferKind
== BufferKind::Uint8Array
);
2780 mBuffer
= JS::UniqueChars(
2781 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, mCapacity
));
2786 IOUtils::JsBuffer::JsBuffer(IOUtils::JsBuffer
&& aOther
) noexcept
2787 : mBufferKind(aOther
.mBufferKind
),
2788 mCapacity(aOther
.mCapacity
),
2789 mLength(aOther
.mLength
),
2790 mBuffer(std::move(aOther
.mBuffer
)) {
2791 aOther
.mCapacity
= 0;
2795 IOUtils::JsBuffer
& IOUtils::JsBuffer::operator=(
2796 IOUtils::JsBuffer
&& aOther
) noexcept
{
2797 mBufferKind
= aOther
.mBufferKind
;
2798 mCapacity
= aOther
.mCapacity
;
2799 mLength
= aOther
.mLength
;
2800 mBuffer
= std::move(aOther
.mBuffer
);
2802 // Invalidate aOther.
2803 aOther
.mCapacity
= 0;
2810 JSString
* IOUtils::JsBuffer::IntoString(JSContext
* aCx
, JsBuffer aBuffer
) {
2811 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::String
);
2813 if (!aBuffer
.mCapacity
) {
2814 return JS_GetEmptyString(aCx
);
2817 if (IsAscii(aBuffer
.BeginReading())) {
2818 // If the string is just plain ASCII, then we can hand the buffer off to
2819 // JavaScript as a Latin1 string (since ASCII is a subset of Latin1).
2820 JS::UniqueLatin1Chars
asLatin1(
2821 reinterpret_cast<JS::Latin1Char
*>(aBuffer
.mBuffer
.release()));
2822 return JS_NewLatin1String(aCx
, std::move(asLatin1
), aBuffer
.mLength
);
2825 const char* ptr
= aBuffer
.mBuffer
.get();
2826 size_t length
= aBuffer
.mLength
;
2828 // Strip off a leading UTF-8 byte order marker (BOM) if found.
2829 if (length
>= 3 && Substring(ptr
, 3) == "\xEF\xBB\xBF"_ns
) {
2834 // If the string is encodable as Latin1, we need to deflate the string to a
2835 // Latin1 string to account for UTF-8 characters that are encoded as more than
2838 // Otherwise, the string contains characters outside Latin1 so we have to
2839 // inflate to UTF-16.
2840 return JS_NewStringCopyUTF8N(aCx
, JS::UTF8Chars(ptr
, length
));
2844 JSObject
* IOUtils::JsBuffer::IntoUint8Array(JSContext
* aCx
, JsBuffer aBuffer
) {
2845 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::Uint8Array
);
2847 if (!aBuffer
.mCapacity
) {
2848 return JS_NewUint8Array(aCx
, 0);
2851 MOZ_RELEASE_ASSERT(aBuffer
.mBuffer
);
2852 JS::Rooted
<JSObject
*> arrayBuffer(
2853 aCx
, JS::NewArrayBufferWithContents(aCx
, aBuffer
.mLength
,
2854 std::move(aBuffer
.mBuffer
)));
2857 // aBuffer will be destructed at end of scope, but its destructor does not
2858 // take into account |mCapacity| or |mLength|, so it is OK for them to be
2859 // non-zero here with a null |mBuffer|.
2863 return JS_NewUint8ArrayWithBuffer(aCx
, arrayBuffer
, 0, aBuffer
.mLength
);
2866 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, IOUtils::JsBuffer
&& aBuffer
,
2867 JS::MutableHandle
<JS::Value
> aValue
) {
2868 if (aBuffer
.mBufferKind
== IOUtils::BufferKind::String
) {
2869 JSString
* str
= IOUtils::JsBuffer::IntoString(aCx
, std::move(aBuffer
));
2874 aValue
.setString(str
);
2878 JSObject
* array
= IOUtils::JsBuffer::IntoUint8Array(aCx
, std::move(aBuffer
));
2883 aValue
.setObject(*array
);
2889 NS_IMPL_CYCLE_COLLECTING_ADDREF(SyncReadFile
)
2890 NS_IMPL_CYCLE_COLLECTING_RELEASE(SyncReadFile
)
2892 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SyncReadFile
)
2893 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
2894 NS_INTERFACE_MAP_ENTRY(nsISupports
)
2895 NS_INTERFACE_MAP_END
2897 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SyncReadFile
, mParent
)
2899 SyncReadFile::SyncReadFile(nsISupports
* aParent
,
2900 RefPtr
<nsFileRandomAccessStream
>&& aStream
,
2902 : mParent(aParent
), mStream(std::move(aStream
)), mSize(aSize
) {
2903 MOZ_RELEASE_ASSERT(mSize
>= 0);
2906 SyncReadFile::~SyncReadFile() = default;
2908 JSObject
* SyncReadFile::WrapObject(JSContext
* aCx
,
2909 JS::Handle
<JSObject
*> aGivenProto
) {
2910 return SyncReadFile_Binding::Wrap(aCx
, this, aGivenProto
);
2913 void SyncReadFile::ReadBytesInto(const Uint8Array
& aDestArray
,
2914 const int64_t aOffset
, ErrorResult
& aRv
) {
2916 return aRv
.ThrowOperationError("SyncReadFile is closed");
2919 aDestArray
.ProcessFixedData([&](const Span
<uint8_t>& aData
) {
2920 auto rangeEnd
= CheckedInt64(aOffset
) + aData
.Length();
2921 if (!rangeEnd
.isValid()) {
2922 return aRv
.ThrowOperationError("Requested range overflows i64");
2925 if (rangeEnd
.value() > mSize
) {
2926 return aRv
.ThrowOperationError(
2927 "Requested range overflows SyncReadFile size");
2930 size_t readLen
{aData
.Length()};
2935 if (nsresult rv
= mStream
->Seek(PR_SEEK_SET
, aOffset
); NS_FAILED(rv
)) {
2936 return aRv
.ThrowOperationError(FormatErrorMessage(
2937 rv
, "Could not seek to position %" PRId64
, aOffset
));
2940 Span
<char> toRead
= AsWritableChars(aData
);
2942 size_t totalRead
= 0;
2943 while (totalRead
!= readLen
) {
2944 // Read no more than INT32_MAX on each call to mStream->Read,
2945 // otherwise it returns an error.
2946 uint32_t bytesToReadThisChunk
=
2947 std::min(readLen
- totalRead
, size_t(INT32_MAX
));
2949 uint32_t bytesRead
= 0;
2950 if (nsresult rv
= mStream
->Read(toRead
.Elements(), bytesToReadThisChunk
,
2953 return aRv
.ThrowOperationError(FormatErrorMessage(
2955 "Encountered an unexpected error while reading file stream"_ns
));
2957 if (bytesRead
== 0) {
2958 return aRv
.ThrowOperationError(
2959 "Reading stopped before the entire array was filled");
2961 totalRead
+= bytesRead
;
2962 toRead
= toRead
.From(bytesRead
);
2967 void SyncReadFile::Close() { mStream
= nullptr; }
2972 static nsCString
FromUnixString(const IOUtils::UnixString
& aString
) {
2973 if (aString
.IsUTF8String()) {
2974 return aString
.GetAsUTF8String();
2976 if (aString
.IsUint8Array()) {
2978 Unused
<< aString
.GetAsUint8Array().AppendDataTo(data
);
2981 MOZ_CRASH("unreachable");
2987 uint32_t IOUtils::LaunchProcess(GlobalObject
& aGlobal
,
2988 const Sequence
<UnixString
>& aArgv
,
2989 const LaunchOptions
& aOptions
,
2991 // The binding is worker-only, so should always be off-main-thread.
2992 MOZ_ASSERT(!NS_IsMainThread());
2994 // This generally won't work in child processes due to sandboxing.
2995 AssertParentProcessWithCallerLocation(aGlobal
);
2997 std::vector
<std::string
> argv
;
2998 base::LaunchOptions options
;
3000 for (const auto& arg
: aArgv
) {
3001 argv
.push_back(FromUnixString(arg
).get());
3004 size_t envLen
= aOptions
.mEnvironment
.Length();
3005 base::EnvironmentArray
envp(new char*[envLen
+ 1]);
3006 for (size_t i
= 0; i
< envLen
; ++i
) {
3007 // EnvironmentArray is a UniquePtr instance which will `free`
3009 envp
[i
] = strdup(FromUnixString(aOptions
.mEnvironment
[i
]).get());
3011 envp
[envLen
] = nullptr;
3012 options
.full_env
= std::move(envp
);
3014 if (aOptions
.mWorkdir
.WasPassed()) {
3015 options
.workdir
= FromUnixString(aOptions
.mWorkdir
.Value()).get();
3018 if (aOptions
.mFdMap
.WasPassed()) {
3019 for (const auto& fdItem
: aOptions
.mFdMap
.Value()) {
3020 options
.fds_to_remap
.push_back({fdItem
.mSrc
, fdItem
.mDst
});
3025 options
.disclaim
= aOptions
.mDisclaim
;
3028 base::ProcessHandle pid
;
3029 static_assert(sizeof(pid
) <= sizeof(uint32_t),
3030 "WebIDL long should be large enough for a pid");
3031 Result
<Ok
, mozilla::ipc::LaunchError
> err
=
3032 base::LaunchApp(argv
, std::move(options
), &pid
);
3034 aRv
.Throw(NS_ERROR_FAILURE
);
3038 MOZ_ASSERT(pid
>= 0);
3039 return static_cast<uint32_t>(pid
);
3043 } // namespace mozilla::dom
3045 #undef REJECT_IF_INIT_PATH_FAILED
3046 #undef IOUTILS_TRY_WITH_CONTEXT