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