Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / dom / system / IOUtils.cpp
blob4b989c6c0cf618305a14cfa884b5e8320a046092
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/. */
7 #include "IOUtils.h"
9 #include <cstdint>
11 #include "ErrorList.h"
12 #include "TypedArray.h"
13 #include "js/ArrayBuffer.h"
14 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
15 #include "js/JSON.h"
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"
43 #include "nsCOMPtr.h"
44 #include "nsError.h"
45 #include "nsFileStreams.h"
46 #include "nsIDirectoryEnumerator.h"
47 #include "nsIFile.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"
56 #include "nsString.h"
57 #include "nsStringFwd.h"
58 #include "nsTArray.h"
59 #include "nsThreadManager.h"
60 #include "nsXULAppAPI.h"
61 #include "prerror.h"
62 #include "prio.h"
63 #include "prtime.h"
64 #include "prtypes.h"
65 #include "ScopedNSSTypes.h"
66 #include "secoidt.h"
68 #if defined(XP_UNIX) && !defined(ANDROID)
69 # include "nsSystemInfo.h"
70 #endif
72 #if defined(XP_WIN)
73 # include "nsILocalFileWin.h"
74 #elif defined(XP_MACOSX)
75 # include "nsILocalFileMac.h"
76 #endif
78 #ifdef XP_UNIX
79 # include "base/process_util.h"
80 #endif
82 #define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise, _msg, ...) \
83 do { \
84 if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
85 NS_FAILED(_rv)) { \
86 (_promise)->MaybeRejectWithOperationError(FormatErrorMessage( \
87 _rv, _msg ": could not parse path", ##__VA_ARGS__)); \
88 return; \
89 } \
90 } while (0)
92 #define IOUTILS_TRY_WITH_CONTEXT(_expr, _fmt, ...) \
93 do { \
94 if (nsresult _rv = (_expr); NS_FAILED(_rv)) { \
95 return Err(IOUtils::IOError(_rv, _fmt, ##__VA_ARGS__)); \
96 } \
97 } while (0)
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
110 * found.
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);
135 nsCString msg;
137 va_list ap;
138 va_start(ap, aFmt);
139 msg.AppendVprintf(aFmt, ap);
140 va_end(ap);
142 msg.AppendPrintf(" (%s)", errorName.get());
144 return msg;
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());
155 return msg;
158 [[nodiscard]] inline bool ToJSValue(
159 JSContext* aCx, const IOUtils::InternalFileInfo& aInternalFileInfo,
160 JS::MutableHandle<JS::Value> aValue) {
161 FileInfo info;
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);
184 } else {
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:
194 [[fallthrough]];
195 case NS_ERROR_FILE_NOT_FOUND:
196 [[fallthrough]];
197 case NS_ERROR_FILE_INVALID_PATH:
198 [[fallthrough]];
199 case NS_ERROR_NOT_AVAILABLE:
200 aPromise->MaybeRejectWithNotFoundError(errMsg);
201 break;
203 case NS_ERROR_FILE_IS_LOCKED:
204 [[fallthrough]];
205 case NS_ERROR_FILE_ACCESS_DENIED:
206 aPromise->MaybeRejectWithNotAllowedError(errMsg);
207 break;
209 case NS_ERROR_FILE_TOO_BIG:
210 [[fallthrough]];
211 case NS_ERROR_FILE_NO_DEVICE_SPACE:
212 [[fallthrough]];
213 case NS_ERROR_FILE_DEVICE_FAILURE:
214 [[fallthrough]];
215 case NS_ERROR_FILE_FS_CORRUPTED:
216 [[fallthrough]];
217 case NS_ERROR_FILE_CORRUPTED:
218 aPromise->MaybeRejectWithNotReadableError(errMsg);
219 break;
221 case NS_ERROR_FILE_ALREADY_EXISTS:
222 aPromise->MaybeRejectWithNoModificationAllowedError(errMsg);
223 break;
225 case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
226 [[fallthrough]];
227 case NS_ERROR_FILE_NAME_TOO_LONG:
228 [[fallthrough]];
229 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
230 [[fallthrough]];
231 case NS_ERROR_FILE_DIR_NOT_EMPTY:
232 aPromise->MaybeRejectWithOperationError(errMsg);
233 break;
235 case NS_ERROR_FILE_READ_ONLY:
236 aPromise->MaybeRejectWithReadOnlyError(errMsg);
237 break;
239 case NS_ERROR_FILE_NOT_DIRECTORY:
240 [[fallthrough]];
241 case NS_ERROR_FILE_DESTINATION_NOT_DIR:
242 [[fallthrough]];
243 case NS_ERROR_FILE_IS_DIRECTORY:
244 [[fallthrough]];
245 case NS_ERROR_FILE_UNKNOWN_TYPE:
246 aPromise->MaybeRejectWithInvalidAccessError(errMsg);
247 break;
249 case NS_ERROR_ILLEGAL_INPUT:
250 [[fallthrough]];
251 case NS_ERROR_ILLEGAL_VALUE:
252 aPromise->MaybeRejectWithDataError(errMsg);
253 break;
255 case NS_ERROR_ABORT:
256 aPromise->MaybeRejectWithAbortError(errMsg);
257 break;
259 default:
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,
269 nsCString& reason) {
270 if (MOZ_LIKELY(XRE_IsParentProcess())) {
271 return true;
274 AutoJSAPI jsapi;
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;
282 uint32_t lineNo = 0;
283 JS::ColumnNumberOneOrigin colNo;
285 NS_ENSURE_TRUE(
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());
292 return false;
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
303 /* static */
304 IOUtils::StateMutex IOUtils::sState{"IOUtils::sState"};
306 /* static */
307 template <typename Fn>
308 already_AddRefed<Promise> IOUtils::WithPromiseAndState(GlobalObject& aGlobal,
309 ErrorResult& aError,
310 Fn aFn) {
311 AssertParentProcessWithCallerLocation(aGlobal);
313 RefPtr<Promise> promise = CreateJSPromise(aGlobal, aError);
314 if (!promise) {
315 return nullptr;
318 if (auto state = GetState()) {
319 aFn(promise, state.ref());
320 } else {
321 RejectShuttingDown(promise);
323 return promise.forget();
326 /* static */
327 template <typename OkT, typename Fn>
328 void IOUtils::DispatchAndResolve(IOUtils::EventQueue* aQueue, Promise* aPromise,
329 Fn aFunc) {
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(),
335 __func__);
338 if (RefPtr<IOPromise<OkT>> p = aQueue->Dispatch<OkT, Fn>(std::move(aFunc))) {
339 p->Then(
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);
350 /* static */
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) {
364 RejectJSPromise(
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()));
369 return;
372 if (aOptions.mMaxBytes.Value() == 0) {
373 // Resolve with an empty buffer.
374 nsTArray<uint8_t> arr(0);
375 promise->MaybeResolve(TypedArrayCreator<Uint8Array>(arr));
376 return;
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);
391 /* static */
392 RefPtr<SyncReadFile> IOUtils::OpenFileForSyncReading(GlobalObject& aGlobal,
393 const nsAString& aPath,
394 ErrorResult& aRv) {
395 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
397 // This API is only exposed to workers, so we should not be on the main
398 // thread here.
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()));
405 return nullptr;
408 RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
409 if (nsresult rv =
410 stream->Init(file, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
411 NS_FAILED(rv)) {
412 aRv.ThrowOperationError(
413 FormatErrorMessage(rv, "Could not open the file at %s",
414 NS_ConvertUTF16toUTF8(aPath).get()));
415 return nullptr;
418 int64_t size = 0;
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()));
423 return nullptr;
426 return new SyncReadFile(aGlobal.GetAsSupports(), std::move(stream), size);
429 /* static */
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);
448 /* static */
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__);
467 state->mEventQueue
468 ->template Dispatch<JsBuffer>(
469 [file, decompress = aOptions.mDecompress]() {
470 return ReadUTF8Sync(file, decompress);
472 ->Then(
473 GetCurrentSerialEventTarget(), __func__,
474 [workerRef, promise = RefPtr{promise},
475 file](JsBuffer&& aBuffer) {
476 AutoJSAPI jsapi;
477 if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
478 RejectJSPromise(
479 promise,
480 IOError(
481 NS_ERROR_DOM_UNKNOWN_ERR,
482 "Could not read `%s': could not initialize JS API",
483 file->HumanReadablePath().get()));
484 return;
486 JSContext* cx = jsapi.cx();
488 JS::Rooted<JSString*> jsonStr(
490 IOUtils::JsBuffer::IntoString(cx, std::move(aBuffer)));
491 if (!jsonStr) {
492 RejectJSPromise(
493 promise,
494 IOError(
495 NS_ERROR_OUT_OF_MEMORY,
496 "Could not read `%s': failed to allocate buffer",
497 file->HumanReadablePath().get()));
498 return;
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);
507 } else {
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()));
515 return;
518 promise->MaybeResolve(val);
520 [workerRef, promise = RefPtr{promise}](const IOError& aErr) {
521 RejectJSPromise(promise, aErr);
526 /* static */
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()));
544 return;
547 auto result = InternalWriteOpts::FromBinding(aOptions);
548 if (result.isErr()) {
549 RejectJSPromise(
550 promise,
551 IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
552 file->HumanReadablePath().get()));
553 return;
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); });
563 /* static */
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()) {
578 RejectJSPromise(
579 promise,
580 IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
581 file->HumanReadablePath().get()));
582 return;
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);
594 /* static */
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()) {
609 RejectJSPromise(
610 promise,
611 IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
612 file->HumanReadablePath().get()));
613 return;
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()));
624 return;
627 JSContext* cx = aGlobal.Context();
628 JS::Rooted<JS::Value> rootedValue(cx, aValue);
629 nsString string;
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);
636 } else {
637 RejectJSPromise(promise,
638 IOError(NS_ERROR_DOM_UNKNOWN_ERR,
639 "Could not serialize object to JSON"_ns));
641 return;
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)) {
650 return Err(IOError(
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);
660 /* static */
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);
689 /* static */
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);
711 /* static */
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,
728 ignoreExisting,
729 permissions);
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); });
749 /* static */
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);
779 /* static */
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,
784 "access", aError);
787 /* static */
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);
795 /* static */
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);
819 /* static */
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);
838 /* static */
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)
847 if (aHonorUmask) {
848 aPermissions &= ~nsSystemInfo::gUserUmask;
850 #endif
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);
865 /* static */
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); });
882 /* static */
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);
892 /* static */
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);
900 /* static */
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)) {
916 RejectJSPromise(
917 promise,
918 IOError(
920 "Could not create unique %s: could not append prefix `%s' to "
921 "parent `%s'",
922 aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory",
923 NS_ConvertUTF16toUTF8(aPrefix).get(),
924 file->HumanReadablePath().get()));
925 return;
928 DispatchAndResolve<nsString>(
929 state->mEventQueue, promise,
930 [file = std::move(file), aPermissions, aFileType]() {
931 return CreateUniqueSync(file, aFileType, aPermissions);
936 /* static */
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));
947 return;
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,
957 aAlgorithm);
962 #if defined(XP_WIN)
964 /* static */
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 "
973 "`%s'",
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__);
984 state->mEventQueue
985 ->template Dispatch<uint32_t>([file = std::move(file)]() {
986 return GetWindowsAttributesSync(file);
988 ->Then(
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);
1005 /* static */
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;
1023 } else {
1024 clearAttrs |= FILE_ATTRIBUTE_READONLY;
1028 if (aAttrs.mHidden.WasPassed()) {
1029 if (aAttrs.mHidden.Value()) {
1030 setAttrs |= FILE_ATTRIBUTE_HIDDEN;
1031 } else {
1032 clearAttrs |= FILE_ATTRIBUTE_HIDDEN;
1036 if (aAttrs.mSystem.WasPassed()) {
1037 if (aAttrs.mSystem.Value()) {
1038 setAttrs |= FILE_ATTRIBUTE_SYSTEM;
1039 } else {
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)
1054 /* static */
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);
1076 /* static */
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);
1098 /* static */
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)) {
1116 RejectJSPromise(
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()));
1122 return;
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);
1133 /* static */
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);
1155 #endif
1157 /* static */
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));
1167 return;
1170 nsCOMPtr<nsIFile> parent;
1171 if (nsresult rv = file->GetParent(getter_AddRefs(parent));
1172 NS_FAILED(rv)) {
1173 RejectJSPromise(promise, IOError(rv,
1174 "Could not get nsIFile for `%s': "
1175 "could not get parent directory",
1176 file->HumanReadablePath().get()));
1177 return;
1180 state->mEventQueue
1181 ->template Dispatch<Ok>([parent = std::move(parent)]() {
1182 return MakeDirectorySync(parent, /* aCreateAncestors = */ true,
1183 /* aIgnoreExisting = */ true, 0755);
1185 ->Then(
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);
1196 /* static */
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));
1206 return;
1209 state->mEventQueue
1210 ->template Dispatch<Ok>([dir]() {
1211 return MakeDirectorySync(dir, /* aCreateAncestors = */ true,
1212 /* aIgnoreExisting = */ true, 0755);
1214 ->Then(
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);
1225 /* static */
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()) {
1231 return nullptr;
1233 MOZ_ASSERT(promise);
1234 return do_AddRef(promise);
1237 /* static */
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)) {
1247 return Err(
1248 IOError(NS_ERROR_ILLEGAL_INPUT,
1249 "Could not read `%s': requested offset is too large (%" PRIu64
1250 " > %" PRId64 ")",
1251 aFile->HumanReadablePath().get(), aOffset, INT64_MAX));
1254 const int64_t offset = static_cast<int64_t>(aOffset);
1256 RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
1257 if (nsresult rv =
1258 stream->Init(aFile, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
1259 NS_FAILED(rv)) {
1260 if (IsFileNotFound(rv)) {
1261 return Err(IOError(rv, "Could not open `%s': file does not exist",
1262 aFile->HumanReadablePath().get()));
1264 return Err(
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)) {
1277 return Err(
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) {
1286 bufSize = 0;
1287 } else {
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,
1293 streamSize));
1296 bufSize = static_cast<uint32_t>(streamSize - offset);
1298 } else {
1299 bufSize = aMaxBytes.value();
1302 if (offset > 0) {
1303 if (nsresult rv = stream->Seek(PR_SEEK_SET, offset); NS_FAILED(rv)) {
1304 return Err(IOError(
1305 rv, "Could not read `%s': could not seek to position %" PRId64,
1306 aFile->HumanReadablePath().get(), offset));
1310 JsBuffer buffer = JsBuffer::CreateEmpty(aBufferKind);
1312 if (bufSize > 0) {
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;
1329 if (nsresult rv =
1330 stream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead);
1331 NS_FAILED(rv)) {
1332 return Err(
1333 IOError(rv, "Could not read `%s': encountered an unexpected error",
1334 aFile->HumanReadablePath().get()));
1336 if (bytesRead == 0) {
1337 break;
1339 totalRead += bytesRead;
1340 toRead = toRead.From(bytesRead);
1343 buffer.SetLength(totalRead);
1346 // Decompress the file contents, if required.
1347 if (aDecompress) {
1348 auto result =
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()));
1354 return result;
1357 return std::move(buffer);
1360 /* static */
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()));
1375 return buffer;
1378 /* static */
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);
1414 result.isErr()) {
1415 return Err(IOError::WithCause(
1416 result.unwrapErr(),
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.
1424 nsIFile* writeFile;
1426 if (tempFile) {
1427 writeFile = tempFile;
1428 } else {
1429 writeFile = aFile;
1432 int32_t flags = PR_WRONLY;
1434 switch (aOptions.mMode) {
1435 case WriteMode::Overwrite:
1436 flags |= PR_TRUNCATE | PR_CREATE_FILE;
1437 break;
1439 case WriteMode::Append:
1440 flags |= PR_APPEND;
1441 break;
1443 case WriteMode::AppendOrCreate:
1444 flags |= PR_APPEND | PR_CREATE_FILE;
1445 break;
1447 case WriteMode::Create:
1448 flags |= PR_CREATE_FILE | PR_EXCL;
1449 break;
1451 default:
1452 MOZ_CRASH("IOUtils: unknown write mode");
1455 if (aOptions.mFlush) {
1456 flags |= PR_SYNC;
1459 // Try to perform the write and ensure that the file is closed before
1460 // continuing.
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());
1476 } else {
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
1484 // denied error.
1485 if (rv == nsresult::NS_ERROR_FILE_IS_DIRECTORY) {
1486 rv = NS_ERROR_FILE_ACCESS_DENIED;
1488 return Err(IOError(
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;
1504 if (nsresult rv =
1505 stream->Write(pendingBytes.Elements(), chunkSize, &bytesWritten);
1506 NS_FAILED(rv)) {
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.
1519 if (tempFile) {
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) {
1530 bool isDir = false;
1531 if (nsresult rv = aFile->IsDirectory(&isDir);
1532 NS_FAILED(rv) && !IsFileNotFound(rv)) {
1533 return Err(IOError(
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.
1545 if (isDir) {
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);
1553 result.isErr()) {
1554 return Err(IOError::WithCause(
1555 result.unwrapErr(),
1556 "Could not write to `%s': could not move overwite with temporary "
1557 "file",
1558 aFile->HumanReadablePath().get()));
1562 return totalWritten;
1565 /* static */
1566 Result<Ok, IOUtils::IOError> IOUtils::MoveSync(nsIFile* aSourceFile,
1567 nsIFile* aDestFile,
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());
1580 if (!srcExists) {
1581 return Err(
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);
1592 /* static */
1593 Result<Ok, IOUtils::IOError> IOUtils::CopySync(nsIFile* aSourceFile,
1594 nsIFile* aDestFile,
1595 bool aNoOverwrite,
1596 bool aRecursive) {
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.
1601 bool srcExists;
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());
1608 if (!srcExists) {
1609 return Err(
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
1617 // true.
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 "
1628 "false\n",
1629 aSourceFile->HumanReadablePath().get(),
1630 aDestFile->HumanReadablePath().get()));
1633 return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks, "copy", aSourceFile,
1634 aDestFile, aNoOverwrite);
1637 /* static */
1638 template <typename CopyOrMoveFn>
1639 Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod,
1640 const char* aMethodName,
1641 nsIFile* aSource,
1642 nsIFile* aDest,
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()));
1658 return Ok();
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()));
1669 destExists = false;
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());
1693 if (srcIsDir) {
1694 return Err(IOError(
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()));
1724 return Ok();
1727 /* static */
1728 Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(nsIFile* aFile,
1729 bool aIgnoreAbsent,
1730 bool aRecursive,
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)) {
1739 return Ok();
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()));
1752 #ifdef XP_WIN
1754 if (rv == NS_ERROR_FILE_ACCESS_DENIED && aRetryReadonly) {
1755 if (auto result =
1756 SetWindowsAttributesSync(aFile, 0, FILE_ATTRIBUTE_READONLY);
1757 result.isErr()) {
1758 return Err(IOError::WithCause(
1759 result.unwrapErr(),
1760 "Could not remove `%s': could not clear readonly attribute",
1761 aFile->HumanReadablePath().get()));
1763 return RemoveSync(aFile, aIgnoreAbsent, aRecursive,
1764 /* aRetryReadonly = */ false);
1767 #endif
1769 return Err(
1770 IOError(rv, "Could not remove `%s'", aFile->HumanReadablePath().get()));
1772 return Ok();
1775 /* static */
1776 Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile,
1777 bool aCreateAncestors,
1778 bool aIgnoreExisting,
1779 int32_t aMode) {
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());
1787 if (!parent) {
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
1791 // create it.
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
1798 // instead.
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());
1805 if (exists) {
1806 return Ok();
1810 nsresult rv =
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.
1818 bool isDirectory;
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());
1824 if (!isDirectory) {
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) {
1833 return Ok();
1835 // Otherwise, forward it.
1836 return Err(IOError(
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()));
1843 return Ok();
1846 Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync(
1847 nsIFile* aFile) {
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()));
1863 return Err(
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;
1869 if (!isRegular) {
1870 bool isDir = false;
1871 IOUTILS_TRY_WITH_CONTEXT(aFile->IsDirectory(&isDir), "Could not stat `%s'",
1872 aFile->HumanReadablePath().get());
1873 info.mType = isDir ? FileType::Directory : FileType::Other;
1876 int64_t size = -1;
1877 if (info.mType == FileType::Regular) {
1878 IOUTILS_TRY_WITH_CONTEXT(aFile->GetFileSize(&size), "Could not stat `%s'",
1879 aFile->HumanReadablePath().get());
1881 info.mSize = size;
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.
1888 return Err(
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());
1910 return info;
1913 /* static */
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) {
1926 return Err(IOError(
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)) {
1937 return Err(IOError(
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()));
1944 return aNewTime;
1947 /* static */
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)) {
1956 return children;
1958 if (NS_FAILED(rv)) {
1959 if (IsFileNotFound(rv)) {
1960 return Err(IOError(
1961 rv, "Could not get children of `%s': directory does not exist",
1962 aFile->HumanReadablePath().get()));
1964 if (IsNotDirectory(rv)) {
1965 return Err(
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());
1986 if (child) {
1987 nsString path;
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());
1998 return children;
2001 /* static */
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());
2010 return Ok{};
2013 /* static */
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());
2021 return exists;
2024 /* static */
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);
2030 NS_FAILED(rv)) {
2031 nsCOMPtr<nsIFile> aParent = nullptr;
2032 MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(aParent)));
2033 MOZ_RELEASE_ASSERT(aParent);
2034 return Err(
2035 IOError(rv, "Could not create unique %s in `%s'",
2036 aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory",
2037 aParent->HumanReadablePath().get()));
2040 nsString path;
2041 MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(path));
2043 return path;
2046 /* static */
2047 Result<nsCString, IOUtils::IOError> IOUtils::ComputeHexDigestSync(
2048 nsIFile* aFile, const HashAlgorithm aAlgorithm) {
2049 static constexpr size_t BUFFER_SIZE = 8192;
2051 SECOidTag alg;
2052 switch (aAlgorithm) {
2053 case HashAlgorithm::Sha256:
2054 alg = SEC_OID_SHA256;
2055 break;
2057 case HashAlgorithm::Sha384:
2058 alg = SEC_OID_SHA384;
2059 break;
2061 case HashAlgorithm::Sha512:
2062 alg = SEC_OID_SHA512;
2063 break;
2065 default:
2066 MOZ_RELEASE_ASSERT(false, "Unexpected HashAlgorithm");
2069 Digest digest;
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);
2077 NS_FAILED(rv)) {
2078 return Err(IOError(rv, "Could not hash `%s': could not open for reading",
2079 aFile->HumanReadablePath().get()));
2082 char buffer[BUFFER_SIZE];
2083 uint32_t read = 0;
2084 for (;;) {
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()));
2091 if (read == 0) {
2092 break;
2095 if (nsresult rv =
2096 digest.Update(reinterpret_cast<unsigned char*>(buffer), read);
2097 NS_FAILED(rv)) {
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]);
2122 return hexDigest;
2125 #if defined(XP_WIN)
2127 Result<uint32_t, IOUtils::IOError> IOUtils::GetWindowsAttributesSync(
2128 nsIFile* aFile) {
2129 MOZ_ASSERT(!NS_IsMainThread());
2131 uint32_t attrs = 0;
2133 nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(aFile);
2134 MOZ_ASSERT(file);
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()));
2140 return attrs;
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);
2148 MOZ_ASSERT(file);
2150 if (nsresult rv = file->SetWindowsFileAttributes(aSetAttrs, aClearAttrs);
2151 NS_FAILED(rv)) {
2152 return Err(IOError(rv, "Could not set Windows file attributes for `%s'",
2153 aFile->HumanReadablePath().get()));
2156 return Ok{};
2159 #elif defined(XP_MACOSX)
2161 /* static */
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);
2167 MOZ_ASSERT(file);
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()));
2175 return hasAttr;
2178 /* static */
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);
2184 MOZ_ASSERT(file);
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()));
2199 return value;
2202 /* static */
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);
2208 MOZ_ASSERT(file);
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()));
2215 return Ok{};
2218 /* static */
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);
2224 MOZ_ASSERT(file);
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()));
2238 return Ok{};
2241 #endif
2243 /* static */
2244 void IOUtils::GetProfileBeforeChange(GlobalObject& aGlobal,
2245 JS::MutableHandle<JS::Value> aClient,
2246 ErrorResult& aRv) {
2247 return GetShutdownClient(aGlobal, aClient, aRv,
2248 ShutdownPhase::ProfileBeforeChange);
2251 /* static */
2252 void IOUtils::GetSendTelemetry(GlobalObject& aGlobal,
2253 JS::MutableHandle<JS::Value> aClient,
2254 ErrorResult& aRv) {
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);
2268 /* static */
2269 void IOUtils::GetShutdownClient(GlobalObject& aGlobal,
2270 JS::MutableHandle<JS::Value> aClient,
2271 ErrorResult& aRv,
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");
2283 return;
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");
2291 return;
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");
2299 return;
2302 aRv.ThrowAbortError(
2303 "IOUtils: profileBeforeChange phase has already finished");
2306 /* sstatic */
2307 Maybe<IOUtils::StateMutex::AutoLock> IOUtils::GetState() {
2308 auto state = sState.Lock();
2309 if (state->mQueueStatus == EventQueueStatus::Shutdown) {
2310 return Nothing{};
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) {
2339 return;
2342 if (NS_WARN_IF(NS_FAILED(mEventQueue->SetShutdownHooks()))) {
2343 mBlockerStatus = ShutdownBlockerStatus::Failed;
2344 } else {
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();
2360 if (!svc) {
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__,
2376 STACK));
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);
2407 MOZ_TRY(
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)));
2428 MOZ_TRY(
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);
2473 return NS_OK;
2476 template <typename OkT, typename Fn>
2477 RefPtr<IOUtils::IOPromise<OkT>> IOUtils::EventQueue::Dispatch(Fn aFunc) {
2478 MOZ_RELEASE_ASSERT(mBackgroundEventTarget);
2480 auto promise =
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__);
2488 } else {
2489 promise->Resolve(result.unwrap(), __func__);
2492 NS_DISPATCH_EVENT_MAY_BLOCK);
2493 return promise;
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);
2519 /* static */
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);
2538 return result;
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);
2545 if (!compressed) {
2546 return Err(IOError(NS_ERROR_UNEXPECTED, "could not compress data"_ns));
2548 result.SetLength(HEADER_SIZE + compressed);
2549 return result;
2552 /* static */
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))) {
2562 nsCString magicStr;
2563 uint32_t i = 0;
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'",
2572 magicStr.get()));
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(
2585 result.unwrapErr(),
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)) {
2595 return Err(
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]);
2609 aName.Append(')');
2611 return NS_OK;
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;
2635 return NS_OK;
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
2646 // deadlock.
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.
2653 Unused << Done();
2656 return NS_OK;
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
2676 // phase.
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;
2687 didFlush = true;
2691 // If we have already shut down the event loop, then call OnFlush to stop
2692 // blocking our parent shutdown client.
2693 if (!didFlush) {
2694 MOZ_RELEASE_ASSERT(mPhase == ShutdownPhase::XpcomWillShutdown);
2695 OnFlush();
2698 return NS_OK;
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) {
2719 return NS_OK;
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());
2732 NS_FAILED(rv)) {
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());
2743 NS_FAILED(rv)) {
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;
2751 return opts;
2754 /* static */
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));
2761 return buffer;
2764 /* static */
2765 IOUtils::JsBuffer IOUtils::JsBuffer::CreateEmpty(
2766 IOUtils::BufferKind aBufferKind) {
2767 JsBuffer buffer(aBufferKind, 0);
2768 MOZ_RELEASE_ASSERT(buffer.mBuffer == nullptr);
2769 return buffer;
2772 IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind, size_t aCapacity)
2773 : mBufferKind(aBufferKind), mCapacity(aCapacity), mLength(0) {
2774 if (mCapacity) {
2775 if (aBufferKind == BufferKind::String) {
2776 mBuffer = JS::UniqueChars(
2777 js_pod_arena_malloc<char>(js::StringBufferArena, mCapacity));
2778 } else {
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;
2792 aOther.mLength = 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;
2804 aOther.mLength = 0;
2806 return *this;
2809 /* static */
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) {
2830 ptr += 3;
2831 length -= 3;
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
2836 // a single byte.
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));
2843 /* static */
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)));
2856 if (!arrayBuffer) {
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|.
2860 return nullptr;
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));
2870 if (!str) {
2871 return false;
2874 aValue.setString(str);
2875 return true;
2878 JSObject* array = IOUtils::JsBuffer::IntoUint8Array(aCx, std::move(aBuffer));
2879 if (!array) {
2880 return false;
2883 aValue.setObject(*array);
2884 return true;
2887 // SyncReadFile
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,
2901 int64_t aSize)
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) {
2915 if (!mStream) {
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()};
2931 if (readLen == 0) {
2932 return;
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,
2951 &bytesRead);
2952 NS_FAILED(rv)) {
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; }
2969 #ifdef XP_UNIX
2970 namespace {
2972 static nsCString FromUnixString(const IOUtils::UnixString& aString) {
2973 if (aString.IsUTF8String()) {
2974 return aString.GetAsUTF8String();
2976 if (aString.IsUint8Array()) {
2977 nsCString data;
2978 Unused << aString.GetAsUint8Array().AppendDataTo(data);
2979 return data;
2981 MOZ_CRASH("unreachable");
2984 } // namespace
2986 // static
2987 uint32_t IOUtils::LaunchProcess(GlobalObject& aGlobal,
2988 const Sequence<UnixString>& aArgv,
2989 const LaunchOptions& aOptions,
2990 ErrorResult& aRv) {
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`
3008 // these strings.
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});
3024 # ifdef XP_MACOSX
3025 options.disclaim = aOptions.mDisclaim;
3026 # endif
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);
3033 if (err.isErr()) {
3034 aRv.Throw(NS_ERROR_FAILURE);
3035 return 0;
3038 MOZ_ASSERT(pid >= 0);
3039 return static_cast<uint32_t>(pid);
3041 #endif // XP_UNIX
3043 } // namespace mozilla::dom
3045 #undef REJECT_IF_INIT_PATH_FAILED
3046 #undef IOUTILS_TRY_WITH_CONTEXT