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