Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  IOUtils.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "IOUtils.h"

#include <cstdint>

#include "ErrorList.h"
#include "TypedArray.h"
#include "js/ArrayBuffer.h"
#include "js/ColumnNumber.h"  // JS::ColumnNumberOneOrigin
#include "js/JSON.h"
#include "js/Utility.h"
#include "js/experimental/TypedData.h"
#include "jsfriendapi.h"
#include "mozilla/Assertions.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Compression.h"
#include "mozilla/Encoding.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Maybe.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/Span.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Try.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/ipc/LaunchError.h"
#include "PathUtils.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "nsFileStreams.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsIInputStream.h"
#include "nsISupports.h"
#include "nsLocalFile.h"
#include "nsNetUtil.h"
#include "nsNSSComponent.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsThreadManager.h"
#include "nsXULAppAPI.h"
#include "prerror.h"
#include "prio.h"
#include "prtime.h"
#include "prtypes.h"
#include "ScopedNSSTypes.h"
#include "secoidt.h"

#if defined(XP_UNIX) && !defined(ANDROID)
#  include "nsSystemInfo.h"
#endif

#if defined(XP_WIN)
#  include "nsILocalFileWin.h"
#elif defined(XP_MACOSX)
#  include "nsILocalFileMac.h"
#endif

#ifdef XP_UNIX
#  include "base/process_util.h"
#endif

#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise, _msg, ...) \
  do {                                                                \
    if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
        NS_FAILED(_rv)) {                                             \
      (_promise)->MaybeRejectWithOperationError(FormatErrorMessage(   \
          _rv, _msg ": could not parse path"##__VA_ARGS__));        \
      return;                                                         \
    }                                                                 \
  } while (0)

#define IOUTILS_TRY_WITH_CONTEXT(_expr, _fmt, ...)            \
  do {                                                        \
    if (nsresult _rv = (_expr); NS_FAILED(_rv)) {             \
      return Err(IOUtils::IOError(_rv, _fmt, ##__VA_ARGS__)); \
    }                                                         \
  } while (0)

static constexpr auto SHUTDOWN_ERROR =
    "IOUtils: Shutting down and refusing additional I/O tasks"_ns;

namespace mozilla::dom {

// static helper functions

/**
 * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
 * report I/O errors inconsistently. For convenience, this function will attempt
 * to match a |nsresult| against known results which imply a file cannot be
 * found.
 *
 * @see nsLocalFileWin.cpp
 * @see nsLocalFileUnix.cpp
 */

static bool IsFileNotFound(nsresult aResult) {
  return aResult == NS_ERROR_FILE_NOT_FOUND;
}
/**
 * Like |IsFileNotFound|, but checks for known results that suggest a file
 * is not a directory.
 */

static bool IsNotDirectory(nsresult aResult) {
  return aResult == NS_ERROR_FILE_DESTINATION_NOT_DIR ||
         aResult == NS_ERROR_FILE_NOT_DIRECTORY;
}

/**
 * Formats an error message and appends the error name to the end.
 */

static nsCString MOZ_FORMAT_PRINTF(2, 3)
    FormatErrorMessage(nsresult aError, const charconst aFmt, ...) {
  nsAutoCString errorName;
  GetErrorName(aError, errorName);

  nsCString msg;

  va_list ap;
  va_start(ap, aFmt);
  msg.AppendVprintf(aFmt, ap);
  va_end(ap);

  msg.AppendPrintf(" (%s)", errorName.get());

  return msg;
}

static nsCString FormatErrorMessage(nsresult aError,
                                    const nsCString& aMessage) {
  nsAutoCString errorName;
  GetErrorName(aError, errorName);

  nsCString msg(aMessage);
  msg.AppendPrintf(" (%s)", errorName.get());

  return msg;
}

[[nodiscard]] inline bool ToJSValue(
    JSContext* aCx, const IOUtils::InternalFileInfo& aInternalFileInfo,
    JS::MutableHandle<JS::Value> aValue) {
  FileInfo info;
  info.mPath.Construct(aInternalFileInfo.mPath);
  info.mType.Construct(aInternalFileInfo.mType);
  info.mSize.Construct(aInternalFileInfo.mSize);

  if (aInternalFileInfo.mCreationTime.isSome()) {
    info.mCreationTime.Construct(aInternalFileInfo.mCreationTime.ref());
  }
  info.mLastAccessed.Construct(aInternalFileInfo.mLastAccessed);
  info.mLastModified.Construct(aInternalFileInfo.mLastModified);

  info.mPermissions.Construct(aInternalFileInfo.mPermissions);

  return ToJSValue(aCx, info, aValue);
}

template <typename T>
static void ResolveJSPromise(Promise* aPromise, T&& aValue) {
  if constexpr (std::is_same_v<T, Ok>) {
    aPromise->MaybeResolveWithUndefined();
  } else if constexpr (std::is_same_v<T, nsTArray<uint8_t>>) {
    TypedArrayCreator<Uint8Array> array(aValue);
    aPromise->MaybeResolve(array);
  } else {
    aPromise->MaybeResolve(std::forward<T>(aValue));
  }
}

static void RejectJSPromise(Promise* aPromise, const IOUtils::IOError& aError) {
  const auto errMsg = FormatErrorMessage(aError.Code(), aError.Message());

  switch (aError.Code()) {
    case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK:
      [[fallthrough]];
    case NS_ERROR_FILE_NOT_FOUND:
      [[fallthrough]];
    case NS_ERROR_FILE_INVALID_PATH:
      [[fallthrough]];
    case NS_ERROR_NOT_AVAILABLE:
      aPromise->MaybeRejectWithNotFoundError(errMsg);
      break;

    case NS_ERROR_FILE_IS_LOCKED:
      [[fallthrough]];
    case NS_ERROR_FILE_ACCESS_DENIED:
      aPromise->MaybeRejectWithNotAllowedError(errMsg);
      break;

    case NS_ERROR_FILE_TOO_BIG:
      [[fallthrough]];
    case NS_ERROR_FILE_NO_DEVICE_SPACE:
      [[fallthrough]];
    case NS_ERROR_FILE_DEVICE_FAILURE:
      [[fallthrough]];
    case NS_ERROR_FILE_FS_CORRUPTED:
      [[fallthrough]];
    case NS_ERROR_FILE_CORRUPTED:
      aPromise->MaybeRejectWithNotReadableError(errMsg);
      break;

    case NS_ERROR_FILE_ALREADY_EXISTS:
      aPromise->MaybeRejectWithNoModificationAllowedError(errMsg);
      break;

    case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
      [[fallthrough]];
    case NS_ERROR_FILE_NAME_TOO_LONG:
      [[fallthrough]];
    case NS_ERROR_FILE_UNRECOGNIZED_PATH:
      [[fallthrough]];
    case NS_ERROR_FILE_DIR_NOT_EMPTY:
      aPromise->MaybeRejectWithOperationError(errMsg);
      break;

    case NS_ERROR_FILE_READ_ONLY:
      aPromise->MaybeRejectWithReadOnlyError(errMsg);
      break;

    case NS_ERROR_FILE_NOT_DIRECTORY:
      [[fallthrough]];
    case NS_ERROR_FILE_DESTINATION_NOT_DIR:
      [[fallthrough]];
    case NS_ERROR_FILE_IS_DIRECTORY:
      [[fallthrough]];
    case NS_ERROR_FILE_UNKNOWN_TYPE:
      aPromise->MaybeRejectWithInvalidAccessError(errMsg);
      break;

    case NS_ERROR_ILLEGAL_INPUT:
      [[fallthrough]];
    case NS_ERROR_ILLEGAL_VALUE:
      aPromise->MaybeRejectWithDataError(errMsg);
      break;

    case NS_ERROR_ABORT:
      aPromise->MaybeRejectWithAbortError(errMsg);
      break;

    default:
      aPromise->MaybeRejectWithUnknownError(errMsg);
  }
}

static void RejectShuttingDown(Promise* aPromise) {
  RejectJSPromise(aPromise, IOUtils::IOError(NS_ERROR_ABORT, SHUTDOWN_ERROR));
}

static bool AssertParentProcessWithCallerLocationImpl(GlobalObject& aGlobal,
                                                      nsCString& reason) {
  if (MOZ_LIKELY(XRE_IsParentProcess())) {
    return true;
  }

  AutoJSAPI jsapi;
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  MOZ_ALWAYS_TRUE(global);
  MOZ_ALWAYS_TRUE(jsapi.Init(global));

  JSContext* cx = jsapi.cx();

  JS::AutoFilename scriptFilename;
  uint32_t lineNo = 0;
  JS::ColumnNumberOneOrigin colNo;

  NS_ENSURE_TRUE(
      JS::DescribeScriptedCaller(&scriptFilename, cx, &lineNo, &colNo), false);

  NS_ENSURE_TRUE(scriptFilename.get(), false);

  reason.AppendPrintf(" Called from %s:%d:%d.", scriptFilename.get(), lineNo,
                      colNo.oneOriginValue());
  return false;
}

static void AssertParentProcessWithCallerLocation(GlobalObject& aGlobal) {
  nsCString reason = "IOUtils can only be used in the parent process."_ns;
  if (!AssertParentProcessWithCallerLocationImpl(aGlobal, reason)) {
    MOZ_CRASH_UNSAFE_PRINTF("%s", reason.get());
  }
}

// IOUtils implementation
/* static */
MOZ_RUNINIT IOUtils::StateMutex IOUtils::sState{"IOUtils::sState"};

/* static */
template <typename Fn>
already_AddRefed<Promise> IOUtils::WithPromiseAndState(GlobalObject& aGlobal,
                                                       ErrorResult& aError,
                                                       Fn aFn) {
  AssertParentProcessWithCallerLocation(aGlobal);

  RefPtr<Promise> promise = CreateJSPromise(aGlobal, aError);
  if (!promise) {
    return nullptr;
  }

  if (auto state = GetState()) {
    aFn(promise, state.ref());
  } else {
    RejectShuttingDown(promise);
  }
  return promise.forget();
}

/* static */
template <typename OkT, typename Fn>
void IOUtils::DispatchAndResolve(IOUtils::EventQueue* aQueue, Promise* aPromise,
                                 Fn aFunc) {
  RefPtr<StrongWorkerRef> workerRef;
  if (!NS_IsMainThread()) {
    // We need to manually keep the worker alive until the promise returned by
    // Dispatch() resolves or rejects.
    workerRef = StrongWorkerRef::CreateForcibly(GetCurrentThreadWorkerPrivate(),
                                                __func__);
  }

  if (RefPtr<IOPromise<OkT>> p = aQueue->Dispatch<OkT, Fn>(std::move(aFunc))) {
    p->Then(
        GetCurrentSerialEventTarget(), __func__,
        [workerRef, promise = RefPtr(aPromise)](OkT&& ok) {
          ResolveJSPromise(promise, std::forward<OkT>(ok));
        },
        [workerRef, promise = RefPtr(aPromise)](const IOError& err) {
          RejectJSPromise(promise, err);
        });
  }
}

/* static */
already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
                                        const nsAString& aPath,
                                        const ReadOptions& aOptions,
                                        ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        Maybe<uint32_t> toRead = Nothing();
        if (!aOptions.mMaxBytes.IsNull()) {
          if (aOptions.mDecompress) {
            RejectJSPromise(
                promise, IOError(NS_ERROR_ILLEGAL_INPUT,
                                 "Could not read `%s': the `maxBytes' and "
                                 "`decompress' options are mutually exclusive",
                                 file->HumanReadablePath().get()));
            return;
          }

          if (aOptions.mMaxBytes.Value() == 0) {
            // Resolve with an empty buffer.
            nsTArray<uint8_t> arr(0);
            promise->MaybeResolve(TypedArrayCreator<Uint8Array>(arr));
            return;
          }
          toRead.emplace(aOptions.mMaxBytes.Value());
        }

        DispatchAndResolve<JsBuffer>(
            state->mEventQueue, promise,
            [file = std::move(file), offset = aOptions.mOffset, toRead,
             decompress = aOptions.mDecompress]() {
              return ReadSync(file, offset, toRead, decompress,
                              BufferKind::Uint8Array);
            });
      });
}

/* static */
RefPtr<SyncReadFile> IOUtils::OpenFileForSyncReading(GlobalObject& aGlobal,
                                                     const nsAString& aPath,
                                                     ErrorResult& aRv) {
  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());

  // This API is only exposed to workers, so we should not be on the main
  // thread here.
  MOZ_RELEASE_ASSERT(!NS_IsMainThread());

  nsCOMPtr<nsIFile> file = new nsLocalFile();
  if (nsresult rv = PathUtils::InitFileWithPath(file, aPath); NS_FAILED(rv)) {
    aRv.ThrowOperationError(FormatErrorMessage(
        rv, "Could not parse path (%s)", NS_ConvertUTF16toUTF8(aPath).get()));
    return nullptr;
  }

  RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
  if (nsresult rv =
          stream->Init(file, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
      NS_FAILED(rv)) {
    aRv.ThrowOperationError(
        FormatErrorMessage(rv, "Could not open the file at %s",
                           NS_ConvertUTF16toUTF8(aPath).get()));
    return nullptr;
  }

  int64_t size = 0;
  if (nsresult rv = stream->GetSize(&size); NS_FAILED(rv)) {
    aRv.ThrowOperationError(FormatErrorMessage(
        rv, "Could not get the stream size for the file at %s",
        NS_ConvertUTF16toUTF8(aPath).get()));
    return nullptr;
  }

  return new SyncReadFile(aGlobal.GetAsSupports(), std::move(stream), size);
}

/* static */
already_AddRefed<Promise> IOUtils::ReadUTF8(GlobalObject& aGlobal,
                                            const nsAString& aPath,
                                            const ReadUTF8Options& aOptions,
                                            ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<JsBuffer>(
            state->mEventQueue, promise,
            [file = std::move(file), decompress = aOptions.mDecompress]() {
              return ReadUTF8Sync(file, decompress);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal,
                                            const nsAString& aPath,
                                            const ReadUTF8Options& aOptions,
                                            ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not read `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        RefPtr<StrongWorkerRef> workerRef;
        if (!NS_IsMainThread()) {
          // We need to manually keep the worker alive until the promise
          // returned by Dispatch() resolves or rejects.
          workerRef = StrongWorkerRef::CreateForcibly(
              GetCurrentThreadWorkerPrivate(), __func__);
        }

        state->mEventQueue
            ->template Dispatch<JsBuffer>(
                [file, decompress = aOptions.mDecompress]() {
                  return ReadUTF8Sync(file, decompress);
                })
            ->Then(
                GetCurrentSerialEventTarget(), __func__,
                [workerRef, promise = RefPtr{promise},
                 file](JsBuffer&& aBuffer) {
                  AutoJSAPI jsapi;
                  if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
                    RejectJSPromise(
                        promise,
                        IOError(
                            NS_ERROR_DOM_UNKNOWN_ERR,
                            "Could not read `%s': could not initialize JS API",
                            file->HumanReadablePath().get()));
                    return;
                  }
                  JSContext* cx = jsapi.cx();

                  JS::Rooted<JSString*> jsonStr(
                      cx,
                      IOUtils::JsBuffer::IntoString(cx, std::move(aBuffer)));
                  if (!jsonStr) {
                    RejectJSPromise(
                        promise,
                        IOError(
                            NS_ERROR_OUT_OF_MEMORY,
                            "Could not read `%s': failed to allocate buffer",
                            file->HumanReadablePath().get()));
                    return;
                  }

                  JS::Rooted<JS::Value> val(cx);
                  if (!JS_ParseJSON(cx, jsonStr, &val)) {
                    JS::Rooted<JS::Value> exn(cx);
                    if (JS_GetPendingException(cx, &exn)) {
                      JS_ClearPendingException(cx);
                      promise->MaybeReject(exn);
                    } else {
                      RejectJSPromise(promise,
                                      IOError(NS_ERROR_DOM_UNKNOWN_ERR,
                                              "Could not read `%s': ParseJSON "
                                              "threw an uncatchable exception",
                                              file->HumanReadablePath().get()));
                    }

                    return;
                  }

                  promise->MaybeResolve(val);
                },
                [workerRef, promise = RefPtr{promise}](const IOError& aErr) {
                  RejectJSPromise(promise, aErr);
                });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal,
                                         const nsAString& aPath,
                                         const Uint8Array& aData,
                                         const WriteOptions& aOptions,
                                         ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not write to `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        Maybe<Buffer<uint8_t>> buf = aData.CreateFromData<Buffer<uint8_t>>();
        if (buf.isNothing()) {
          promise->MaybeRejectWithOperationError(nsPrintfCString(
              "Could not write to `%s': could not allocate buffer",
              file->HumanReadablePath().get()));
          return;
        }

        auto result = InternalWriteOpts::FromBinding(aOptions);
        if (result.isErr()) {
          RejectJSPromise(
              promise,
              IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
                                 file->HumanReadablePath().get()));
          return;
        }

        DispatchAndResolve<uint32_t>(
            state->mEventQueue, promise,
            [file = std::move(file), buf = buf.extract(),
             opts = result.unwrap()]() { return WriteSync(file, buf, opts); });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::WriteUTF8(GlobalObject& aGlobal,
                                             const nsAString& aPath,
                                             const nsACString& aString,
                                             const WriteOptions& aOptions,
                                             ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not write to `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        auto result = InternalWriteOpts::FromBinding(aOptions);
        if (result.isErr()) {
          RejectJSPromise(
              promise,
              IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
                                 file->HumanReadablePath().get()));
          return;
        }

        DispatchAndResolve<uint32_t>(
            state->mEventQueue, promise,
            [file = std::move(file), str = nsCString(aString),
             opts = result.unwrap()]() {
              return WriteSync(file, AsBytes(Span(str)), opts);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal,
                                             const nsAString& aPath,
                                             JS::Handle<JS::Value> aValue,
                                             const WriteOptions& aOptions,
                                             ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not write to `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        auto result = InternalWriteOpts::FromBinding(aOptions);
        if (result.isErr()) {
          RejectJSPromise(
              promise,
              IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
                                 file->HumanReadablePath().get()));
          return;
        }

        auto opts = result.unwrap();

        if (opts.mMode == WriteMode::Append ||
            opts.mMode == WriteMode::AppendOrCreate) {
          promise->MaybeRejectWithNotSupportedError(
              nsPrintfCString("Could not write to `%s': IOUtils.writeJSON does "
                              "not support appending to files.",
                              file->HumanReadablePath().get()));
          return;
        }

        JSContext* cx = aGlobal.Context();
        JS::Rooted<JS::Value> rootedValue(cx, aValue);
        nsString string;
        if (!nsContentUtils::StringifyJSON(cx, aValue, string,
                                           UndefinedIsNullStringLiteral)) {
          JS::Rooted<JS::Value> exn(cx, JS::UndefinedValue());
          if (JS_GetPendingException(cx, &exn)) {
            JS_ClearPendingException(cx);
            promise->MaybeReject(exn);
          } else {
            RejectJSPromise(promise,
                            IOError(NS_ERROR_DOM_UNKNOWN_ERR,
                                    "Could not serialize object to JSON"_ns));
          }
          return;
        }

        DispatchAndResolve<uint32_t>(
            state->mEventQueue, promise,
            [file = std::move(file), string = std::move(string),
             opts = std::move(opts)]() -> Result<uint32_t, IOError> {
              nsAutoCString utf8Str;
              if (!CopyUTF16toUTF8(string, utf8Str, fallible)) {
                return Err(IOError(
                    NS_ERROR_OUT_OF_MEMORY,
                    "Failed to write to `%s': could not allocate buffer",
                    file->HumanReadablePath().get()));
              }
              return WriteSync(file, AsBytes(Span(utf8Str)), opts);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::Move(GlobalObject& aGlobal,
                                        const nsAString& aSourcePath,
                                        const nsAString& aDestPath,
                                        const MoveOptions& aOptions,
                                        ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> sourceFile = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise,
                                   "Could not move `%s' to `%s'",
                                   NS_ConvertUTF16toUTF8(aSourcePath).get(),
                                   NS_ConvertUTF16toUTF8(aDestPath).get());

        nsCOMPtr<nsIFile> destFile = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise,
                                   "Could not move `%s' to `%s'",
                                   NS_ConvertUTF16toUTF8(aSourcePath).get(),
                                   NS_ConvertUTF16toUTF8(aDestPath).get());

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [sourceFile = std::move(sourceFile), destFile = std::move(destFile),
             noOverwrite = aOptions.mNoOverwrite]() {
              return MoveSync(sourceFile, destFile, noOverwrite);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::Remove(GlobalObject& aGlobal,
                                          const nsAString& aPath,
                                          const RemoveOptions& aOptions,
                                          ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not remove `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [file = std::move(file), ignoreAbsent = aOptions.mIgnoreAbsent,
             recursive = aOptions.mRecursive,
             retryReadonly = aOptions.mRetryReadonly]() {
              return RemoveSync(file, ignoreAbsent, recursive, retryReadonly);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::MakeDirectory(
    GlobalObject& aGlobal, const nsAString& aPath,
    const MakeDirectoryOptions& aOptions, ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not make directory `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<Ok>(state->mEventQueue, promise,
                               [file = std::move(file),
                                createAncestors = aOptions.mCreateAncestors,
                                ignoreExisting = aOptions.mIgnoreExisting,
                                permissions = aOptions.mPermissions]() {
                                 return MakeDirectorySync(file, createAncestors,
                                                          ignoreExisting,
                                                          permissions);
                               });
      });
}

already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal,
                                        const nsAString& aPath,
                                        ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not stat `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<InternalFileInfo>(
            state->mEventQueue, promise,
            [file = std::move(file)]() { return StatSync(file); });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::Copy(GlobalObject& aGlobal,
                                        const nsAString& aSourcePath,
                                        const nsAString& aDestPath,
                                        const CopyOptions& aOptions,
                                        ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> sourceFile = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise,
                                   "Could not copy `%s' to `%s'",
                                   NS_ConvertUTF16toUTF8(aSourcePath).get(),
                                   NS_ConvertUTF16toUTF8(aDestPath).get());

        nsCOMPtr<nsIFile> destFile = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise,
                                   "Could not copy `%s' to `%s'",
                                   NS_ConvertUTF16toUTF8(aSourcePath).get(),
                                   NS_ConvertUTF16toUTF8(aDestPath).get());

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [sourceFile = std::move(sourceFile), destFile = std::move(destFile),
             noOverwrite = aOptions.mNoOverwrite,
             recursive = aOptions.mRecursive]() {
              return CopySync(sourceFile, destFile, noOverwrite, recursive);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::SetAccessTime(
    GlobalObject& aGlobal, const nsAString& aPath,
    const Optional<int64_t>& aAccess, ErrorResult& aError) {
  return SetTime(aGlobal, aPath, aAccess, &nsIFile::SetLastAccessedTime,
                 "access", aError);
}

/* static */
already_AddRefed<Promise> IOUtils::SetModificationTime(
    GlobalObject& aGlobal, const nsAString& aPath,
    const Optional<int64_t>& aModification, ErrorResult& aError) {
  return SetTime(aGlobal, aPath, aModification, &nsIFile::SetLastModifiedTime,
                 "modification", aError);
}

/* static */
already_AddRefed<Promise> IOUtils::SetTime(GlobalObject& aGlobal,
                                           const nsAString& aPath,
                                           const Optional<int64_t>& aNewTime,
                                           IOUtils::SetTimeFn aSetTimeFn,
                                           const charconst aTimeKind,
                                           ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not set %s time on `%s'", aTimeKind,
                                   NS_ConvertUTF16toUTF8(aPath).get());

        int64_t newTime = aNewTime.WasPassed() ? aNewTime.Value()
                                               : PR_Now() / PR_USEC_PER_MSEC;
        DispatchAndResolve<int64_t>(
            state->mEventQueue, promise,
            [file = std::move(file), aSetTimeFn, newTime]() {
              return SetTimeSync(file, aSetTimeFn, newTime);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::HasChildren(
    GlobalObject& aGlobal, const nsAString& aPath,
    const HasChildrenOptions& aOptions, ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not check children of `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<bool>(
            state->mEventQueue, promise,
            [file = std::move(file), ignoreAbsent = aOptions.mIgnoreAbsent]() {
              return HasChildrenSync(file, ignoreAbsent);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::GetChildren(
    GlobalObject& aGlobal, const nsAString& aPath,
    const GetChildrenOptions& aOptions, ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not get children of `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<nsTArray<nsString>>(
            state->mEventQueue, promise,
            [file = std::move(file), ignoreAbsent = aOptions.mIgnoreAbsent]() {
              return GetChildrenSync(file, ignoreAbsent);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::SetPermissions(GlobalObject& aGlobal,
                                                  const nsAString& aPath,
                                                  uint32_t aPermissions,
                                                  const bool aHonorUmask,
                                                  ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
#if defined(XP_UNIX) && !defined(ANDROID)
        if (aHonorUmask) {
          aPermissions &= ~nsSystemInfo::gUserUmask;
        }
#endif

        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not set permissions on `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [file = std::move(file), permissions = aPermissions]() {
              return SetPermissionsSync(file, permissions);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::Exists(GlobalObject& aGlobal,
                                          const nsAString& aPath,
                                          ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not determine if `%s' exists",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<bool>(
            state->mEventQueue, promise,
            [file = std::move(file)]() { return ExistsSync(file); });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::CreateUniqueFile(GlobalObject& aGlobal,
                                                    const nsAString& aParent,
                                                    const nsAString& aPrefix,
                                                    const uint32_t aPermissions,
                                                    ErrorResult& aError) {
  return CreateUnique(aGlobal, aParent, aPrefix, nsIFile::NORMAL_FILE_TYPE,
                      aPermissions, aError);
}

/* static */
already_AddRefed<Promise> IOUtils::CreateUniqueDirectory(
    GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
    const uint32_t aPermissions, ErrorResult& aError) {
  return CreateUnique(aGlobal, aParent, aPrefix, nsIFile::DIRECTORY_TYPE,
                      aPermissions, aError);
}

/* static */
already_AddRefed<Promise> IOUtils::CreateUnique(GlobalObject& aGlobal,
                                                const nsAString& aParent,
                                                const nsAString& aPrefix,
                                                const uint32_t aFileType,
                                                const uint32_t aPermissions,
                                                ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aParent, promise, "Could not create unique %s in `%s'",
            aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory",
            NS_ConvertUTF16toUTF8(aParent).get());

        if (nsresult rv = file->Append(aPrefix); NS_FAILED(rv)) {
          RejectJSPromise(
              promise,
              IOError(
                  rv,
                  "Could not create unique %s: could not append prefix `%s' to "
                  "parent `%s'",
                  aFileType == nsIFile::NORMAL_FILE_TYPE ? "file" : "directory",
                  NS_ConvertUTF16toUTF8(aPrefix).get(),
                  file->HumanReadablePath().get()));
          return;
        }

        DispatchAndResolve<nsString>(
            state->mEventQueue, promise,
            [file = std::move(file), aPermissions, aFileType]() {
              return CreateUniqueSync(file, aFileType, aPermissions);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::ComputeHexDigest(
    GlobalObject& aGlobal, const nsAString& aPath,
    const HashAlgorithm aAlgorithm, ErrorResult& aError) {
  const bool nssInitialized = EnsureNSSInitializedChromeOrContent();

  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        if (!nssInitialized) {
          RejectJSPromise(promise, IOError(NS_ERROR_UNEXPECTED,
                                           "Could not initialize NSS"_ns));
          return;
        }

        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not hash `%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<nsCString>(state->mEventQueue, promise,
                                      [file = std::move(file), aAlgorithm]() {
                                        return ComputeHexDigestSync(file,
                                                                    aAlgorithm);
                                      });
      });
}

#if defined(XP_WIN)

/* static */
already_AddRefed<Promise> IOUtils::GetWindowsAttributes(GlobalObject& aGlobal,
                                                        const nsAString& aPath,
                                                        ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
                                   "Could not get Windows file attributes of "
                                   "`%s'",
                                   NS_ConvertUTF16toUTF8(aPath).get());

        RefPtr<StrongWorkerRef> workerRef;
        if (!NS_IsMainThread()) {
          // We need to manually keep the worker alive until the promise
          // returned by Dispatch() resolves or rejects.
          workerRef = StrongWorkerRef::CreateForcibly(
              GetCurrentThreadWorkerPrivate(), __func__);
        }

        state->mEventQueue
            ->template Dispatch<uint32_t>([file = std::move(file)]() {
              return GetWindowsAttributesSync(file);
            })
            ->Then(
                GetCurrentSerialEventTarget(), __func__,
                [workerRef, promise = RefPtr{promise}](const uint32_t aAttrs) {
                  WindowsFileAttributes attrs;

                  attrs.mReadOnly.Construct(aAttrs & FILE_ATTRIBUTE_READONLY);
                  attrs.mHidden.Construct(aAttrs & FILE_ATTRIBUTE_HIDDEN);
                  attrs.mSystem.Construct(aAttrs & FILE_ATTRIBUTE_SYSTEM);

                  promise->MaybeResolve(attrs);
                },
                [workerRef, promise = RefPtr{promise}](const IOError& aErr) {
                  RejectJSPromise(promise, aErr);
                });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::SetWindowsAttributes(
    GlobalObject& aGlobal, const nsAString& aPath,
    const WindowsFileAttributes& aAttrs, ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aPath, promise,
            "Could not set Windows file attributes on `%s'",
            NS_ConvertUTF16toUTF8(aPath).get());

        uint32_t setAttrs = 0;
        uint32_t clearAttrs = 0;

        if (aAttrs.mReadOnly.WasPassed()) {
          if (aAttrs.mReadOnly.Value()) {
            setAttrs |= FILE_ATTRIBUTE_READONLY;
          } else {
            clearAttrs |= FILE_ATTRIBUTE_READONLY;
          }
        }

        if (aAttrs.mHidden.WasPassed()) {
          if (aAttrs.mHidden.Value()) {
            setAttrs |= FILE_ATTRIBUTE_HIDDEN;
          } else {
            clearAttrs |= FILE_ATTRIBUTE_HIDDEN;
          }
        }

        if (aAttrs.mSystem.WasPassed()) {
          if (aAttrs.mSystem.Value()) {
            setAttrs |= FILE_ATTRIBUTE_SYSTEM;
          } else {
            clearAttrs |= FILE_ATTRIBUTE_SYSTEM;
          }
        }

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [file = std::move(file), setAttrs, clearAttrs]() {
              return SetWindowsAttributesSync(file, setAttrs, clearAttrs);
            });
      });
}

#elif defined(XP_MACOSX)

/* static */
already_AddRefed<Promise> IOUtils::HasMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aPath, promise,
            "Could not read the extended attribute `%s' from `%s'",
            PromiseFlatCString(aAttr).get(),
            NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<bool>(
            state->mEventQueue, promise,
            [file = std::move(file), attr = nsCString(aAttr)]() {
              return HasMacXAttrSync(file, attr);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::GetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aPath, promise,
            "Could not read extended attribute `%s' from `%s'",
            PromiseFlatCString(aAttr).get(),
            NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<nsTArray<uint8_t>>(
            state->mEventQueue, promise,
            [file = std::move(file), attr = nsCString(aAttr)]() {
              return GetMacXAttrSync(file, attr);
            });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::SetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               const Uint8Array& aValue,
                                               ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aPath, promise,
            "Could not set the extended attribute `%s' on `%s'",
            PromiseFlatCString(aAttr).get(),
            NS_ConvertUTF16toUTF8(aPath).get());

        nsTArray<uint8_t> value;

        if (!aValue.AppendDataTo(value)) {
          RejectJSPromise(
              promise, IOError(NS_ERROR_OUT_OF_MEMORY,
                               "Could not set extended attribute `%s' on `%s': "
                               "could not allocate buffer",
                               PromiseFlatCString(aAttr).get(),
                               file->HumanReadablePath().get()));
          return;
        }

        DispatchAndResolve<Ok>(state->mEventQueue, promise,
                               [file = std::move(file), attr = nsCString(aAttr),
                                value = std::move(value)] {
                                 return SetMacXAttrSync(file, attr, value);
                               });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::DelMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        nsCOMPtr<nsIFile> file = new nsLocalFile();
        REJECT_IF_INIT_PATH_FAILED(
            file, aPath, promise,
            "Could not delete extended attribute `%s' on `%s'",
            PromiseFlatCString(aAttr).get(),
            NS_ConvertUTF16toUTF8(aPath).get());

        DispatchAndResolve<Ok>(
            state->mEventQueue, promise,
            [file = std::move(file), attr = nsCString(aAttr)] {
              return DelMacXAttrSync(file, attr);
            });
      });
}

#endif

/* static */
already_AddRefed<Promise> IOUtils::GetFile(
    GlobalObject& aGlobal, const Sequence<nsString>& aComponents,
    ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        ErrorResult joinErr;
        nsCOMPtr<nsIFile> file = PathUtils::Join(aComponents, joinErr);
        if (joinErr.Failed()) {
          promise->MaybeReject(std::move(joinErr));
          return;
        }

        nsCOMPtr<nsIFile> parent;
        if (nsresult rv = file->GetParent(getter_AddRefs(parent));
            NS_FAILED(rv)) {
          RejectJSPromise(promise, IOError(rv,
                                           "Could not get nsIFile for `%s': "
                                           "could not get parent directory",
                                           file->HumanReadablePath().get()));
          return;
        }

        state->mEventQueue
            ->template Dispatch<Ok>([parent = std::move(parent)]() {
              return MakeDirectorySync(parent, /* aCreateAncestors = */ true,
                                       /* aIgnoreExisting = */ true, 0755);
            })
            ->Then(
                GetCurrentSerialEventTarget(), __func__,
                [file = std::move(file), promise = RefPtr(promise)](const Ok&) {
                  promise->MaybeResolve(file);
                },
                [promise = RefPtr(promise)](const IOError& err) {
                  RejectJSPromise(promise, err);
                });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::GetDirectory(
    GlobalObject& aGlobal, const Sequence<nsString>& aComponents,
    ErrorResult& aError) {
  return WithPromiseAndState(
      aGlobal, aError, [&](Promise* promise, auto& state) {
        ErrorResult joinErr;
        nsCOMPtr<nsIFile> dir = PathUtils::Join(aComponents, joinErr);
        if (joinErr.Failed()) {
          promise->MaybeReject(std::move(joinErr));
          return;
        }

        state->mEventQueue
            ->template Dispatch<Ok>([dir]() {
              return MakeDirectorySync(dir, /* aCreateAncestors = */ true,
                                       /* aIgnoreExisting = */ true, 0755);
            })
            ->Then(
                GetCurrentSerialEventTarget(), __func__,
                [dir, promise = RefPtr(promise)](const Ok&) {
                  promise->MaybeResolve(dir);
                },
                [promise = RefPtr(promise)](const IOError& err) {
                  RejectJSPromise(promise, err);
                });
      });
}

/* static */
already_AddRefed<Promise> IOUtils::CreateJSPromise(GlobalObject& aGlobal,
                                                   ErrorResult& aError) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  RefPtr<Promise> promise = Promise::Create(global, aError);
  if (aError.Failed()) {
    return nullptr;
  }
  MOZ_ASSERT(promise);
  return do_AddRef(promise);
}

/* static */
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync(
    nsIFile* aFile, const uint64_t aOffset, const Maybe<uint32_t> aMaxBytes,
    const bool aDecompress, IOUtils::BufferKind aBufferKind) {
  MOZ_ASSERT(!NS_IsMainThread());
  // This is checked in IOUtils::Read.
  MOZ_ASSERT(aMaxBytes.isNothing() || !aDecompress,
             "maxBytes and decompress are mutually exclusive");

  if (aOffset > static_cast<uint64_t>(INT64_MAX)) {
    return Err(
        IOError(NS_ERROR_ILLEGAL_INPUT,
                "Could not read `%s': requested offset is too large (%" PRIu64
                " > %" PRId64 ")",
                aFile->HumanReadablePath().get(), aOffset, INT64_MAX));
  }

  const int64_t offset = static_cast<int64_t>(aOffset);

  RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
  if (nsresult rv =
          stream->Init(aFile, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
      NS_FAILED(rv)) {
    if (IsFileNotFound(rv)) {
      return Err(IOError(rv, "Could not open `%s': file does not exist",
                         aFile->HumanReadablePath().get()));
    }
    return Err(
        IOError(rv, "Could not open `%s'", aFile->HumanReadablePath().get()));
  }

  uint32_t bufSize = 0;

  if (aMaxBytes.isNothing()) {
    // Limitation: We cannot read more than the maximum size of a TypedArray
    //            (UINT32_MAX bytes). Reject if we have been requested to
    //            perform too large of a read.

    int64_t rawStreamSize = -1;
    if (nsresult rv = stream->GetSize(&rawStreamSize); NS_FAILED(rv)) {
      return Err(
          IOError(NS_ERROR_FILE_ACCESS_DENIED,
                  "Could not open `%s': could not stat file or directory",
                  aFile->HumanReadablePath().get()));
    }
    MOZ_RELEASE_ASSERT(rawStreamSize >= 0);

    uint64_t streamSize = static_cast<uint64_t>(rawStreamSize);
    if (aOffset >= streamSize) {
      bufSize = 0;
    } else {
      if (streamSize - offset > static_cast<int64_t>(UINT32_MAX)) {
        return Err(IOError(NS_ERROR_FILE_TOO_BIG,
                           "Could not read `%s' with offset %" PRIu64
                           ": file is too large (%" PRIu64 " bytes)",
                           aFile->HumanReadablePath().get(), offset,
                           streamSize));
      }

      bufSize = static_cast<uint32_t>(streamSize - offset);
    }
  } else {
    bufSize = aMaxBytes.value();
  }

  if (offset > 0) {
    if (nsresult rv = stream->Seek(PR_SEEK_SET, offset); NS_FAILED(rv)) {
      return Err(IOError(
          rv, "Could not read `%s': could not seek to position %" PRId64,
          aFile->HumanReadablePath().get(), offset));
    }
  }

  JsBuffer buffer = JsBuffer::CreateEmpty(aBufferKind);

  if (bufSize > 0) {
    auto result = JsBuffer::Create(aBufferKind, bufSize);
    if (result.isErr()) {
      return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'",
                                    aFile->HumanReadablePath().get()));
    }
    buffer = result.unwrap();
    Span<char> toRead = buffer.BeginWriting();

    // Read the file from disk.
    uint32_t totalRead = 0;
    while (totalRead != bufSize) {
      // Read no more than INT32_MAX on each call to stream->Read, otherwise it
      // returns an error.
      uint32_t bytesToReadThisChunk =
          std::min<uint32_t>(bufSize - totalRead, INT32_MAX);
      uint32_t bytesRead = 0;
      if (nsresult rv =
              stream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead);
          NS_FAILED(rv)) {
        return Err(
            IOError(rv, "Could not read `%s': encountered an unexpected error",
                    aFile->HumanReadablePath().get()));
      }
      if (bytesRead == 0) {
        break;
      }
      totalRead += bytesRead;
      toRead = toRead.From(bytesRead);
    }

    buffer.SetLength(totalRead);
  }

  // Decompress the file contents, if required.
  if (aDecompress) {
    auto result =
        MozLZ4::Decompress(AsBytes(buffer.BeginReading()), aBufferKind);
    if (result.isErr()) {
      return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'",
                                    aFile->HumanReadablePath().get()));
    }
    return result;
  }

  return std::move(buffer);
}

/* static */
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadUTF8Sync(
    nsIFile* aFile, bool aDecompress) {
  auto result = ReadSync(aFile, 0, Nothing{}, aDecompress, BufferKind::String);
  if (result.isErr()) {
    return result.propagateErr();
  }

  JsBuffer buffer = result.unwrap();
  if (!IsUtf8(buffer.BeginReading())) {
    return Err(IOError(NS_ERROR_FILE_CORRUPTED,
                       "Could not read `%s': file is not UTF-8 encoded",
                       aFile->HumanReadablePath().get()));
  }

  return buffer;
}

/* static */
Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync(
    nsIFile* aFile, const Span<const uint8_t>& aByteArray,
    const IOUtils::InternalWriteOpts& aOptions) {
  MOZ_ASSERT(!NS_IsMainThread());

  nsIFile* backupFile = aOptions.mBackupFile;
  nsIFile* tempFile = aOptions.mTmpFile;

  bool exists = false;
  IOUTILS_TRY_WITH_CONTEXT(
      aFile->Exists(&exists),
      "Could not write to `%s': could not stat file or directory",
      aFile->HumanReadablePath().get());

  if (exists && aOptions.mMode == WriteMode::Create) {
    return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS,
                       "Could not write to `%s': refusing to overwrite file, "
                       "`mode' is not \"overwrite\"",
                       aFile->HumanReadablePath().get()));
  }

  // If backupFile was specified, perform the backup as a move.
  if (exists && backupFile) {
    // We copy `destFile` here to a new `nsIFile` because
    // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we
    // did not do this, we would end up having `destFile` point to the same
    // location as `backupFile`. Then, when we went to write to `destFile`, we
    // would end up overwriting `backupFile` and never actually write to the
    // file we were supposed to.
    nsCOMPtr<nsIFile> toMove;
    MOZ_ALWAYS_SUCCEEDS(aFile->Clone(getter_AddRefs(toMove)));

    bool noOverwrite = aOptions.mMode == WriteMode::Create;

    if (auto result = MoveSync(toMove, backupFile, noOverwrite);
        result.isErr()) {
      return Err(IOError::WithCause(
          result.unwrapErr(),
          "Could not write to `%s': failed to back up source file",
          aFile->HumanReadablePath().get()));
    }
  }

  // If tempFile was specified, we will write to there first, then perform a
  // move to ensure the file ends up at the final requested destination.
  nsIFile* writeFile;

  if (tempFile) {
    writeFile = tempFile;
  } else {
    writeFile = aFile;
  }

  int32_t flags = PR_WRONLY;

  switch (aOptions.mMode) {
    case WriteMode::Overwrite:
      flags |= PR_TRUNCATE | PR_CREATE_FILE;
      break;

    case WriteMode::Append:
      flags |= PR_APPEND;
      break;

    case WriteMode::AppendOrCreate:
      flags |= PR_APPEND | PR_CREATE_FILE;
      break;

    case WriteMode::Create:
      flags |= PR_CREATE_FILE | PR_EXCL;
      break;

    default:
      MOZ_CRASH("IOUtils: unknown write mode");
  }

  if (aOptions.mFlush) {
    flags |= PR_SYNC;
  }

  // Try to perform the write and ensure that the file is closed before
  // continuing.
  uint32_t totalWritten = 0;
  {
    // Compress the byte array if required.
    nsTArray<uint8_t> compressed;
    Span<const char> bytes;
    if (aOptions.mCompress) {
      auto result = MozLZ4::Compress(aByteArray);
      if (result.isErr()) {
        return Err(IOError::WithCause(result.unwrapErr(),
                                      "Could not write to `%s'",
                                      writeFile->HumanReadablePath().get()));
      }
      compressed = result.unwrap();
      bytes = Span(reinterpret_cast<const char*>(compressed.Elements()),
                   compressed.Length());
    } else {
      bytes = Span(reinterpret_cast<const char*>(aByteArray.Elements()),
                   aByteArray.Length());
    }

    RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
    if (nsresult rv = stream->Init(writeFile, flags, 0666, 0); NS_FAILED(rv)) {
      // Normalize platform-specific errors for opening a directory to an access
      // denied error.
      if (rv == nsresult::NS_ERROR_FILE_IS_DIRECTORY) {
        rv = NS_ERROR_FILE_ACCESS_DENIED;
      }
      return Err(IOError(
          rv, "Could not write to `%s': failed to open file for writing",
          writeFile->HumanReadablePath().get()));
    }

    // nsFileRandomAccessStream::Write uses PR_Write under the hood, which
    // accepts a *int32_t* for the chunk size.
    uint32_t chunkSize = INT32_MAX;
    Span<const char> pendingBytes = bytes;

    while (pendingBytes.Length() > 0) {
      if (pendingBytes.Length() < chunkSize) {
        chunkSize = pendingBytes.Length();
      }

      uint32_t bytesWritten = 0;
      if (nsresult rv =
              stream->Write(pendingBytes.Elements(), chunkSize, &bytesWritten);
          NS_FAILED(rv)) {
        return Err(IOError(rv,
                           "Could not write to `%s': failed to write chunk; "
                           "the file may be corrupt",
                           writeFile->HumanReadablePath().get()));
      }
      pendingBytes = pendingBytes.From(bytesWritten);
      totalWritten += bytesWritten;
    }
  }

  // If tempFile was passed, check destFile against writeFile and, if they
  // differ, the operation is finished by performing a move.
  if (tempFile) {
    nsAutoStringN<256> destPath;
    nsAutoStringN<256> writePath;

    MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(destPath));
    MOZ_ALWAYS_SUCCEEDS(writeFile->GetPath(writePath));

    // nsIFile::MoveToFollowingLinks will only update the path of the file if
    // the move succeeds.
    if (destPath != writePath) {
      if (aOptions.mTmpFile) {
        bool isDir = false;
        if (nsresult rv = aFile->IsDirectory(&isDir);
            NS_FAILED(rv) && !IsFileNotFound(rv)) {
          return Err(IOError(
              rv, "Could not write to `%s': could not stat file or directory",
              aFile->HumanReadablePath().get()));
        }

        // If we attempt to write to a directory *without* a temp file, we get a
        // permission error.
        //
        // However, if we are writing to a temp file first, when we copy the
        // temp file over the destination file, we actually end up copying it
        // inside the directory, which is not what we want. In this case, we are
        // just going to bail out early.
        if (isDir) {
          return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED,
                             "Could not write to `%s': file is a directory",
                             aFile->HumanReadablePath().get()));
        }
      }

      if (auto result = MoveSync(writeFile, aFile, /* aNoOverwrite = */ false);
          result.isErr()) {
        return Err(IOError::WithCause(
            result.unwrapErr(),
            "Could not write to `%s': could not move overwite with temporary "
            "file",
            aFile->HumanReadablePath().get()));
      }
    }
  }
  return totalWritten;
}

/* static */
Result<Ok, IOUtils::IOError> IOUtils::MoveSync(nsIFile* aSourceFile,
                                               nsIFile* aDestFile,
                                               bool aNoOverwrite) {
  MOZ_ASSERT(!NS_IsMainThread());

  // Ensure the source file exists before continuing. If it doesn't exist,
  // subsequent operations can fail in different ways on different platforms.
  bool srcExists = false;
  IOUTILS_TRY_WITH_CONTEXT(
      aSourceFile->Exists(&srcExists),
      "Could not move `%s' to `%s': could not stat source file or directory",
      aSourceFile->HumanReadablePath().get(),
      aDestFile->HumanReadablePath().get());

  if (!srcExists) {
    return Err(
        IOError(NS_ERROR_FILE_NOT_FOUND,
                "Could not move `%s' to `%s': source file does not exist",
                aSourceFile->HumanReadablePath().get(),
                aDestFile->HumanReadablePath().get()));
  }

  return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks, "move", aSourceFile,
                        aDestFile, aNoOverwrite);
}

/* static */
Result<Ok, IOUtils::IOError> IOUtils::CopySync(nsIFile* aSourceFile,
                                               nsIFile* aDestFile,
                                               bool aNoOverwrite,
                                               bool aRecursive) {
  MOZ_ASSERT(!NS_IsMainThread());

  // Ensure the source file exists before continuing. If it doesn't exist,
  // subsequent operations can fail in different ways on different platforms.
  bool srcExists;
  IOUTILS_TRY_WITH_CONTEXT(
      aSourceFile->Exists(&srcExists),
      "Could not copy `%s' to `%s': could not stat source file or directory",
      aSourceFile->HumanReadablePath().get(),
      aDestFile->HumanReadablePath().get());

  if (!srcExists) {
    return Err(
        IOError(NS_ERROR_FILE_NOT_FOUND,
                "Could not copy `%s' to `%s': source file does not exist",
                aSourceFile->HumanReadablePath().get(),
                aDestFile->HumanReadablePath().get()));
  }

  // If source is a directory, fail immediately unless the recursive option is
  // true.
  bool srcIsDir = false;
  IOUTILS_TRY_WITH_CONTEXT(
      aSourceFile->IsDirectory(&srcIsDir),
      "Could not copy `%s' to `%s': could not stat source file or directory",
      aSourceFile->HumanReadablePath().get(),
      aDestFile->HumanReadablePath().get());

  if (srcIsDir && !aRecursive) {
    return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED,
                       "Refused to copy directory `%s' to `%s': `recursive' is "
                       "false\n",
                       aSourceFile->HumanReadablePath().get(),
                       aDestFile->HumanReadablePath().get()));
  }

  return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks, "copy", aSourceFile,
                        aDestFile, aNoOverwrite);
}

/* static */
template <typename CopyOrMoveFn>
Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod,
                                                     const char* aMethodName,
                                                     nsIFile* aSource,
                                                     nsIFile* aDest,
                                                     bool aNoOverwrite) {
  MOZ_ASSERT(!NS_IsMainThread());

  // Case 1: Destination is an existing directory. Copy/move source into dest.
  bool destIsDir = false;
  bool destExists = true;

  nsresult rv = aDest->IsDirectory(&destIsDir);
  if (NS_SUCCEEDED(rv) && destIsDir) {
    rv = (aSource->*aMethod)(aDest, u""_ns);
    if (NS_FAILED(rv)) {
      return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName,
                         aSource->HumanReadablePath().get(),
                         aDest->HumanReadablePath().get()));
    }
    return Ok();
  }

  if (NS_FAILED(rv)) {
    if (!IsFileNotFound(rv)) {
      // It's ok if the dest file doesn't exist. Case 2 handles this below.
      // Bail out early for any other kind of error though.
      return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName,
                         aSource->HumanReadablePath().get(),
                         aDest->HumanReadablePath().get()));
    }
    destExists = false;
  }

  // Case 2: Destination is a file which may or may not exist.
  //         Try to copy or rename the source to the destination.
  //         If the destination exists and the source is not a regular file,
  //         then this may fail.
  if (aNoOverwrite && destExists) {
    return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS,
                       "Could not %s `%s' to `%s': destination file exists and "
                       "`noOverwrite' is true",
                       aMethodName, aSource->HumanReadablePath().get(),
                       aDest->HumanReadablePath().get()));
  }
  if (destExists && !destIsDir) {
    // If the source file is a directory, but the target is a file, abort early.
    // Different implementations of |CopyTo| and |MoveTo| seem to handle this
    // error case differently (or not at all), so we explicitly handle it here.
    bool srcIsDir = false;
    IOUTILS_TRY_WITH_CONTEXT(
        aSource->IsDirectory(&srcIsDir),
        "Could not %s `%s' to `%s': could not stat source file or directory",
        aMethodName, aSource->HumanReadablePath().get(),
        aDest->HumanReadablePath().get());
    if (srcIsDir) {
      return Err(IOError(
          NS_ERROR_FILE_DESTINATION_NOT_DIR,
          "Could not %s directory `%s' to `%s': destination is not a directory",
          aMethodName, aSource->HumanReadablePath().get(),
          aDest->HumanReadablePath().get()));
    }
  }

  // We would have already thrown if the path was zero-length.
  nsAutoString destName;
  MOZ_ALWAYS_SUCCEEDS(aDest->GetLeafName(destName));

--> --------------------

--> maximum size reached

--> --------------------

98%


¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge