2020-07-03 01:32:03 +03:00
|
|
|
/* -*- 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/. */
|
|
|
|
|
2020-11-10 19:04:28 +03:00
|
|
|
#include "IOUtils.h"
|
|
|
|
|
2020-08-28 18:57:44 +03:00
|
|
|
#include <cstdint>
|
|
|
|
|
2020-08-28 18:49:58 +03:00
|
|
|
#include "ErrorList.h"
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
#include "js/ArrayBuffer.h"
|
2021-01-18 22:52:32 +03:00
|
|
|
#include "js/JSON.h"
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
#include "js/Utility.h"
|
|
|
|
#include "js/experimental/TypedData.h"
|
|
|
|
#include "jsfriendapi.h"
|
2021-03-23 07:26:50 +03:00
|
|
|
#include "mozilla/AutoRestore.h"
|
2020-08-28 18:57:44 +03:00
|
|
|
#include "mozilla/Compression.h"
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
#include "mozilla/Encoding.h"
|
2020-08-28 18:57:44 +03:00
|
|
|
#include "mozilla/EndianUtils.h"
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
#include "mozilla/ErrorNames.h"
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
#include "mozilla/Maybe.h"
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
#include "mozilla/ResultExtensions.h"
|
2020-11-23 19:21:38 +03:00
|
|
|
#include "mozilla/Services.h"
|
2020-08-26 18:37:25 +03:00
|
|
|
#include "mozilla/Span.h"
|
2020-11-23 19:21:38 +03:00
|
|
|
#include "mozilla/StaticPtr.h"
|
2020-07-15 19:04:51 +03:00
|
|
|
#include "mozilla/TextUtils.h"
|
2021-01-16 00:28:18 +03:00
|
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include "mozilla/Utf8.h"
|
2021-01-18 22:52:32 +03:00
|
|
|
#include "mozilla/dom/IOUtilsBinding.h"
|
|
|
|
#include "mozilla/dom/Promise.h"
|
2020-11-10 19:04:28 +03:00
|
|
|
#include "nsCOMPtr.h"
|
2020-08-28 18:49:58 +03:00
|
|
|
#include "nsError.h"
|
2020-11-10 04:59:38 +03:00
|
|
|
#include "nsFileStreams.h"
|
2020-08-28 18:49:58 +03:00
|
|
|
#include "nsIDirectoryEnumerator.h"
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
#include "nsIFile.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nsIGlobalObject.h"
|
2021-03-23 07:26:50 +03:00
|
|
|
#include "nsISupports.h"
|
2020-11-10 19:04:28 +03:00
|
|
|
#include "nsLocalFile.h"
|
|
|
|
#include "nsPrintfCString.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nsReadableUtils.h"
|
2020-08-10 19:00:32 +03:00
|
|
|
#include "nsString.h"
|
|
|
|
#include "nsStringFwd.h"
|
2020-11-10 19:04:28 +03:00
|
|
|
#include "nsTArray.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nsThreadManager.h"
|
2020-11-23 19:21:38 +03:00
|
|
|
#include "nsXULAppAPI.h"
|
2020-09-11 01:43:30 +03:00
|
|
|
#include "prerror.h"
|
|
|
|
#include "prio.h"
|
|
|
|
#include "prtime.h"
|
|
|
|
#include "prtypes.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
|
2021-01-30 03:50:47 +03:00
|
|
|
#ifndef ANDROID
|
|
|
|
# include "nsSystemInfo.h"
|
|
|
|
#endif
|
|
|
|
|
2020-11-10 19:04:28 +03:00
|
|
|
#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise) \
|
|
|
|
do { \
|
|
|
|
if (nsresult _rv = (_file)->InitWithPath((_path)); NS_FAILED(_rv)) { \
|
|
|
|
(_promise)->MaybeRejectWithOperationError( \
|
|
|
|
FormatErrorMessage(_rv, "Could not parse path (%s)", \
|
|
|
|
NS_ConvertUTF16toUTF8(_path).get())); \
|
|
|
|
return (_promise).forget(); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
static constexpr auto SHUTDOWN_ERROR =
|
|
|
|
"IOUtils: Shutting down and refusing additional I/O tasks"_ns;
|
|
|
|
|
2020-12-09 08:48:51 +03:00
|
|
|
namespace mozilla::dom {
|
2020-07-03 01:32:03 +03:00
|
|
|
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
// 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) {
|
2020-08-28 18:49:58 +03:00
|
|
|
return aResult == NS_ERROR_FILE_NOT_FOUND ||
|
|
|
|
aResult == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* 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;
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
|
2020-07-15 19:04:17 +03:00
|
|
|
/**
|
|
|
|
* Formats an error message and appends the error name to the end.
|
|
|
|
*/
|
|
|
|
template <typename... Args>
|
|
|
|
static nsCString FormatErrorMessage(nsresult aError, const char* const aMessage,
|
|
|
|
Args... aArgs) {
|
|
|
|
nsPrintfCString msg(aMessage, aArgs...);
|
|
|
|
|
|
|
|
if (const char* errName = GetStaticErrorName(aError)) {
|
|
|
|
msg.AppendPrintf(": %s", errName);
|
|
|
|
} else {
|
|
|
|
// In the exceptional case where there is no error name, print the literal
|
|
|
|
// integer value of the nsresult as an upper case hex value so it can be
|
|
|
|
// located easily in searchfox.
|
|
|
|
msg.AppendPrintf(": 0x%" PRIX32, static_cast<uint32_t>(aError));
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::move(msg);
|
|
|
|
}
|
|
|
|
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
static nsCString FormatErrorMessage(nsresult aError,
|
|
|
|
const char* const aMessage) {
|
|
|
|
const char* errName = GetStaticErrorName(aError);
|
|
|
|
if (errName) {
|
|
|
|
return nsPrintfCString("%s: %s", aMessage, errName);
|
|
|
|
}
|
|
|
|
// In the exceptional case where there is no error name, print the literal
|
|
|
|
// integer value of the nsresult as an upper case hex value so it can be
|
|
|
|
// located easily in searchfox.
|
|
|
|
return nsPrintfCString("%s: 0x%" PRIX32, aMessage,
|
|
|
|
static_cast<uint32_t>(aError));
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:19:25 +03:00
|
|
|
[[nodiscard]] inline bool ToJSValue(
|
2020-07-30 17:02:43 +03:00
|
|
|
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);
|
|
|
|
info.mLastModified.Construct(aInternalFileInfo.mLastModified);
|
2020-12-03 08:37:33 +03:00
|
|
|
|
|
|
|
if (aInternalFileInfo.mCreationTime.isSome()) {
|
|
|
|
info.mCreationTime.Construct(aInternalFileInfo.mCreationTime.ref());
|
|
|
|
}
|
2020-12-03 08:37:45 +03:00
|
|
|
|
|
|
|
info.mPermissions.Construct(aInternalFileInfo.mPermissions);
|
|
|
|
|
2020-07-30 17:02:43 +03:00
|
|
|
return ToJSValue(aCx, info, aValue);
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
template <typename T>
|
|
|
|
static void ResolveJSPromise(Promise* aPromise, T&& aValue) {
|
|
|
|
if constexpr (std::is_same_v<T, Ok>) {
|
|
|
|
aPromise->MaybeResolveWithUndefined();
|
|
|
|
} else {
|
|
|
|
aPromise->MaybeResolve(std::forward<T>(aValue));
|
|
|
|
}
|
|
|
|
}
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
static void RejectJSPromise(Promise* aPromise, const IOUtils::IOError& aError) {
|
|
|
|
const auto& errMsg = aError.Message();
|
2021-01-18 22:52:29 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
switch (aError.Code()) {
|
2021-05-06 17:00:06 +03:00
|
|
|
case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK:
|
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_INVALID_PATH
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
2021-05-06 17:00:06 +03:00
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_INVALID_PATH
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
2021-05-06 17:00:06 +03:00
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_INVALID_PATH
|
|
|
|
case NS_ERROR_FILE_INVALID_PATH:
|
2021-03-23 07:26:50 +03:00
|
|
|
aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("File not found"_ns));
|
|
|
|
break;
|
2021-05-06 17:00:06 +03:00
|
|
|
case NS_ERROR_FILE_IS_LOCKED:
|
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_ACCESS_DENIED:
|
|
|
|
aPromise->MaybeRejectWithNotAllowedError(
|
|
|
|
errMsg.refOr("Access was denied to the target file"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_TOO_BIG:
|
|
|
|
aPromise->MaybeRejectWithNotReadableError(
|
|
|
|
errMsg.refOr("Target file is too big"_ns));
|
|
|
|
break;
|
2021-05-06 17:00:06 +03:00
|
|
|
case NS_ERROR_FILE_NO_DEVICE_SPACE:
|
|
|
|
aPromise->MaybeRejectWithNotReadableError(
|
|
|
|
errMsg.refOr("Target device is full"_ns));
|
|
|
|
break;
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_ALREADY_EXISTS:
|
|
|
|
aPromise->MaybeRejectWithNoModificationAllowedError(
|
|
|
|
errMsg.refOr("Target file already exists"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Failed to copy or move the target file"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_READ_ONLY:
|
|
|
|
aPromise->MaybeRejectWithReadOnlyError(
|
|
|
|
errMsg.refOr("Target file is read only"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_NOT_DIRECTORY:
|
2021-05-06 17:00:06 +03:00
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_DESTINATION_NOT_DIR
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_DESTINATION_NOT_DIR:
|
|
|
|
aPromise->MaybeRejectWithInvalidAccessError(
|
|
|
|
errMsg.refOr("Target file is not a directory"_ns));
|
|
|
|
break;
|
2021-05-06 17:00:06 +03:00
|
|
|
case NS_ERROR_FILE_IS_DIRECTORY:
|
|
|
|
aPromise->MaybeRejectWithInvalidAccessError(
|
|
|
|
errMsg.refOr("Target file is a directory"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_UNKNOWN_TYPE:
|
|
|
|
aPromise->MaybeRejectWithInvalidAccessError(
|
|
|
|
errMsg.refOr("Target file is of unknown type"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_NAME_TOO_LONG:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Target file path is too long"_ns));
|
|
|
|
break;
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Target file path is not recognized"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_DIR_NOT_EMPTY:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Target directory is not empty"_ns));
|
|
|
|
break;
|
2021-05-06 17:00:06 +03:00
|
|
|
case NS_ERROR_FILE_DEVICE_FAILURE:
|
|
|
|
[[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED
|
|
|
|
case NS_ERROR_FILE_FS_CORRUPTED:
|
|
|
|
aPromise->MaybeRejectWithNotReadableError(
|
|
|
|
errMsg.refOr("Target file system may be corrupt or unavailable"_ns));
|
|
|
|
break;
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_FILE_CORRUPTED:
|
|
|
|
aPromise->MaybeRejectWithNotReadableError(
|
|
|
|
errMsg.refOr("Target file could not be read and may be corrupt"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_ILLEGAL_INPUT:
|
2021-05-06 17:00:06 +03:00
|
|
|
[[fallthrough]]; // NS_ERROR_ILLEGAL_VALUE
|
2021-03-23 07:26:50 +03:00
|
|
|
case NS_ERROR_ILLEGAL_VALUE:
|
|
|
|
aPromise->MaybeRejectWithDataError(
|
|
|
|
errMsg.refOr("Argument is not allowed"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_ABORT:
|
|
|
|
aPromise->MaybeRejectWithAbortError(errMsg.refOr("Operation aborted"_ns));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
aPromise->MaybeRejectWithUnknownError(
|
|
|
|
errMsg.refOr(FormatErrorMessage(aError.Code(), "Unexpected error")));
|
2021-02-22 21:25:05 +03:00
|
|
|
}
|
2021-03-23 07:26:50 +03:00
|
|
|
}
|
2021-02-22 21:25:06 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
static void RejectShuttingDown(Promise* aPromise) {
|
|
|
|
RejectJSPromise(aPromise,
|
|
|
|
IOUtils::IOError(NS_ERROR_ABORT).WithMessage(SHUTDOWN_ERROR));
|
2021-02-22 21:25:06 +03:00
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
// IOUtils implementation
|
|
|
|
/* static */
|
|
|
|
IOUtils::StateMutex IOUtils::sState{"IOUtils::sState"};
|
|
|
|
|
2021-02-22 21:25:06 +03:00
|
|
|
/* static */
|
2021-02-22 21:56:05 +03:00
|
|
|
template <typename OkT, typename Fn>
|
2021-03-23 07:26:50 +03:00
|
|
|
void IOUtils::DispatchAndResolve(IOUtils::EventQueue* aQueue, Promise* aPromise,
|
|
|
|
Fn aFunc) {
|
|
|
|
if (RefPtr<IOPromise<OkT>> p = aQueue->Dispatch<OkT, Fn>(std::move(aFunc))) {
|
|
|
|
p->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise = RefPtr(aPromise)](OkT&& ok) {
|
|
|
|
ResolveJSPromise(promise, std::forward<OkT>(ok));
|
|
|
|
},
|
|
|
|
[promise = RefPtr(aPromise)](const IOError& err) {
|
|
|
|
RejectJSPromise(promise, err);
|
|
|
|
});
|
|
|
|
}
|
2021-02-22 21:25:06 +03:00
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
2020-08-28 18:57:44 +03:00
|
|
|
const ReadOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-07-03 01:32:03 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2020-11-10 19:04:28 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
Maybe<uint32_t> toRead = Nothing();
|
|
|
|
if (!aOptions.mMaxBytes.IsNull()) {
|
|
|
|
if (aOptions.mMaxBytes.Value() == 0) {
|
|
|
|
// Resolve with an empty buffer.
|
|
|
|
nsTArray<uint8_t> arr(0);
|
|
|
|
promise->MaybeResolve(TypedArrayCreator<Uint8Array>(arr));
|
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
toRead.emplace(aOptions.mMaxBytes.Value());
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<JsBuffer>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
2021-04-29 02:27:42 +03:00
|
|
|
[file = std::move(file), offset = aOptions.mOffset, toRead,
|
|
|
|
decompress = aOptions.mDecompress]() {
|
|
|
|
return ReadSync(file, offset, toRead, decompress,
|
|
|
|
BufferKind::Uint8Array);
|
2021-03-23 07:26:50 +03:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
2020-08-26 18:37:25 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::ReadUTF8(GlobalObject& aGlobal,
|
2020-08-28 18:57:44 +03:00
|
|
|
const nsAString& aPath,
|
|
|
|
const ReadUTF8Options& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-08-26 18:37:25 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-08-26 18:37:25 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<JsBuffer>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), decompress = aOptions.mDecompress]() {
|
|
|
|
return ReadUTF8Sync(file, decompress);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-08-26 18:37:25 +03:00
|
|
|
}
|
|
|
|
|
2021-01-18 22:52:32 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::ReadJSON(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
|
|
|
const ReadUTF8Options& aOptions) {
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
|
|
|
|
|
|
|
state.ref()
|
|
|
|
->mEventQueue
|
|
|
|
->Dispatch<JsBuffer>([file, decompress = aOptions.mDecompress]() {
|
|
|
|
return ReadUTF8Sync(file, decompress);
|
|
|
|
})
|
|
|
|
->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise, file](JsBuffer&& aBuffer) {
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
|
|
|
|
promise->MaybeRejectWithUnknownError(
|
|
|
|
"Could not initialize JS API");
|
|
|
|
return;
|
2021-02-22 21:56:05 +03:00
|
|
|
}
|
2021-03-23 07:26:50 +03:00
|
|
|
JSContext* cx = jsapi.cx();
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
JS::Rooted<JSString*> jsonStr(
|
|
|
|
cx, IOUtils::JsBuffer::IntoString(cx, std::move(aBuffer)));
|
|
|
|
if (!jsonStr) {
|
|
|
|
RejectJSPromise(promise, IOError(NS_ERROR_OUT_OF_MEMORY));
|
|
|
|
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)
|
|
|
|
.WithMessage(
|
|
|
|
"ParseJSON threw an uncatchable exception "
|
|
|
|
"while parsing file(%s)",
|
|
|
|
file->HumanReadablePath().get()));
|
|
|
|
}
|
2021-01-18 22:52:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
return;
|
|
|
|
}
|
2021-01-18 22:52:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
promise->MaybeResolve(val);
|
|
|
|
},
|
|
|
|
[promise](const IOError& aErr) { RejectJSPromise(promise, aErr); });
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2021-01-18 22:52:32 +03:00
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
2020-12-09 08:48:46 +03:00
|
|
|
already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
|
|
|
const Uint8Array& aData,
|
|
|
|
const WriteOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-07-03 01:32:03 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2020-11-10 19:04:28 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
aData.ComputeState();
|
|
|
|
auto buf = Buffer<uint8_t>::CopyFrom(Span(aData.Data(), aData.Length()));
|
|
|
|
if (buf.isNothing()) {
|
|
|
|
promise->MaybeRejectWithOperationError(
|
|
|
|
"Out of memory: Could not allocate buffer while writing to file");
|
|
|
|
return promise.forget();
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
auto opts = InternalWriteOpts::FromBinding(aOptions);
|
|
|
|
if (opts.isErr()) {
|
|
|
|
RejectJSPromise(promise, opts.unwrapErr());
|
|
|
|
return promise.forget();
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<uint32_t>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), buf = std::move(*buf),
|
|
|
|
opts = opts.unwrap()]() { return WriteSync(file, buf, opts); });
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
2020-08-26 18:37:25 +03:00
|
|
|
/* static */
|
2020-12-09 08:48:46 +03:00
|
|
|
already_AddRefed<Promise> IOUtils::WriteUTF8(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
2021-01-15 07:28:00 +03:00
|
|
|
const nsACString& aString,
|
2020-12-09 08:48:46 +03:00
|
|
|
const WriteOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-08-26 18:37:25 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-03-10 07:32:41 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
auto opts = InternalWriteOpts::FromBinding(aOptions);
|
|
|
|
if (opts.isErr()) {
|
|
|
|
RejectJSPromise(promise, opts.unwrapErr());
|
|
|
|
return promise.forget();
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<uint32_t>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), str = nsCString(aString),
|
|
|
|
opts = opts.unwrap()]() {
|
|
|
|
return WriteSync(file, AsBytes(Span(str)), opts);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-08-26 18:37:25 +03:00
|
|
|
}
|
|
|
|
|
2021-01-18 22:52:34 +03:00
|
|
|
static bool AppendJsonAsUtf8(const char16_t* aData, uint32_t aLen, void* aStr) {
|
|
|
|
nsCString* str = static_cast<nsCString*>(aStr);
|
|
|
|
return AppendUTF16toUTF8(Span<const char16_t>(aData, aLen), *str, fallible);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
|
|
|
JS::Handle<JS::Value> aValue,
|
|
|
|
const WriteOptions& aOptions) {
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-01-18 22:52:34 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
auto opts = InternalWriteOpts::FromBinding(aOptions);
|
|
|
|
if (opts.isErr()) {
|
|
|
|
RejectJSPromise(promise, opts.unwrapErr());
|
|
|
|
return promise.forget();
|
2021-03-11 02:44:09 +03:00
|
|
|
}
|
|
|
|
|
2021-04-30 01:38:02 +03:00
|
|
|
if (opts.inspect().mMode == WriteMode::Append) {
|
|
|
|
promise->MaybeRejectWithNotSupportedError(
|
|
|
|
"IOUtils.writeJSON does not support appending to files."_ns);
|
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
JSContext* cx = aGlobal.Context();
|
|
|
|
JS::Rooted<JS::Value> rootedValue(cx, aValue);
|
|
|
|
nsCString utf8Str;
|
|
|
|
|
|
|
|
if (!JS_Stringify(cx, &rootedValue, nullptr, JS::NullHandleValue,
|
|
|
|
AppendJsonAsUtf8, &utf8Str)) {
|
|
|
|
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)
|
|
|
|
.WithMessage("Could not serialize object to JSON"));
|
|
|
|
}
|
|
|
|
return promise.forget();
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<uint32_t>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), utf8Str = std::move(utf8Str),
|
|
|
|
opts = opts.unwrap()]() {
|
|
|
|
return WriteSync(file, AsBytes(Span(utf8Str)), opts);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2021-01-18 22:52:34 +03:00
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Move(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aSourcePath,
|
|
|
|
const nsAString& aDestPath,
|
|
|
|
const MoveOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> sourceFile = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise);
|
2020-11-10 04:58:47 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
nsCOMPtr<nsIFile> destFile = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<Ok>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[sourceFile = std::move(sourceFile), destFile = std::move(destFile),
|
|
|
|
noOverwrite = aOptions.mNoOverwrite]() {
|
|
|
|
return MoveSync(sourceFile, destFile, noOverwrite);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
|
2020-07-18 03:31:57 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Remove(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
|
|
|
const RemoveOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-07-18 03:31:57 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<Ok>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), ignoreAbsent = aOptions.mIgnoreAbsent,
|
|
|
|
recursive = aOptions.mRecursive]() {
|
|
|
|
return RemoveSync(file, ignoreAbsent, recursive);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-07-18 03:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:13:35 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::MakeDirectory(
|
|
|
|
GlobalObject& aGlobal, const nsAString& aPath,
|
|
|
|
const MakeDirectoryOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-07-21 18:13:35 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<Ok>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), createAncestors = aOptions.mCreateAncestors,
|
|
|
|
ignoreExisting = aOptions.mIgnoreExisting,
|
|
|
|
permissions = aOptions.mPermissions]() {
|
|
|
|
return MakeDirectorySync(file, createAncestors, ignoreExisting,
|
|
|
|
permissions);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-07-23 21:15:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-07-23 21:15:30 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<InternalFileInfo>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file)]() { return StatSync(file); });
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
|
2020-08-10 19:00:32 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Copy(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aSourcePath,
|
|
|
|
const nsAString& aDestPath,
|
|
|
|
const CopyOptions& aOptions) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-08-10 19:00:32 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-03-10 07:32:41 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> sourceFile = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(sourceFile, aSourcePath, promise);
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
nsCOMPtr<nsIFile> destFile = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(destFile, aDestPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<Ok>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[sourceFile = std::move(sourceFile), destFile = std::move(destFile),
|
|
|
|
noOverwrite = aOptions.mNoOverwrite,
|
|
|
|
recursive = aOptions.mRecursive]() {
|
|
|
|
return CopySync(sourceFile, destFile, noOverwrite, recursive);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-08-10 19:00:32 +03:00
|
|
|
}
|
|
|
|
|
2020-08-26 18:31:57 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Touch(
|
|
|
|
GlobalObject& aGlobal, const nsAString& aPath,
|
|
|
|
const Optional<int64_t>& aModification) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-08-26 18:31:57 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-08-26 18:31:57 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2020-08-26 18:31:57 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
Maybe<int64_t> newTime = Nothing();
|
|
|
|
if (aModification.WasPassed()) {
|
|
|
|
newTime = Some(aModification.Value());
|
|
|
|
}
|
|
|
|
DispatchAndResolve<int64_t>(state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), newTime]() {
|
|
|
|
return TouchSync(file, newTime);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
2020-08-26 18:31:57 +03:00
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-08-26 18:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-08-28 18:49:58 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::GetChildren(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath) {
|
2021-03-23 07:26:50 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-08-28 18:49:58 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-08-28 18:49:58 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<nsTArray<nsString>>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file)]() { return GetChildrenSync(file); });
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 07:10:30 +03:00
|
|
|
return promise.forget();
|
2020-08-28 18:49:58 +03:00
|
|
|
}
|
|
|
|
|
2020-12-03 08:37:45 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::SetPermissions(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
2021-01-30 03:50:47 +03:00
|
|
|
uint32_t aPermissions,
|
|
|
|
const bool aHonorUmask) {
|
2021-03-23 07:26:50 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-12-03 08:37:45 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-12-03 08:37:45 +03:00
|
|
|
|
2021-01-30 03:50:47 +03:00
|
|
|
#if defined(XP_UNIX) && !defined(ANDROID)
|
|
|
|
if (aHonorUmask) {
|
|
|
|
aPermissions &= ~nsSystemInfo::gUserUmask;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-02-22 21:56:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<Ok>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file), permissions = aPermissions]() {
|
|
|
|
return SetPermissionsSync(file, permissions);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
|
|
|
}
|
2020-12-03 08:37:45 +03:00
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
2020-12-03 08:38:03 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Exists(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath) {
|
2021-03-23 07:26:50 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
|
2020-12-03 08:38:03 +03:00
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2021-01-18 22:52:36 +03:00
|
|
|
if (!promise) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-12-03 08:38:03 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
if (auto state = GetState()) {
|
|
|
|
nsCOMPtr<nsIFile> file = new nsLocalFile();
|
|
|
|
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
|
2021-03-11 02:44:09 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
DispatchAndResolve<bool>(
|
|
|
|
state.ref()->mEventQueue, promise,
|
|
|
|
[file = std::move(file)]() { return ExistsSync(file); });
|
|
|
|
} else {
|
|
|
|
RejectShuttingDown(promise);
|
2021-03-11 02:44:09 +03:00
|
|
|
}
|
2021-03-23 07:26:50 +03:00
|
|
|
return promise.forget();
|
2021-03-11 02:44:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::CreateJSPromise(GlobalObject& aGlobal) {
|
|
|
|
ErrorResult er;
|
|
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
RefPtr<Promise> promise = Promise::Create(global, er);
|
|
|
|
if (er.Failed()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
MOZ_ASSERT(promise);
|
|
|
|
return do_AddRef(promise);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadSync(
|
2021-04-29 02:27:42 +03:00
|
|
|
nsIFile* aFile, const uint32_t aOffset, const Maybe<uint32_t> aMaxBytes,
|
|
|
|
const bool aDecompress, IOUtils::BufferKind aBufferKind) {
|
2020-07-03 01:32:03 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-08-28 18:57:44 +03:00
|
|
|
if (aMaxBytes.isSome() && aDecompress) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_ILLEGAL_INPUT)
|
|
|
|
.WithMessage(
|
|
|
|
"The `maxBytes` and `decompress` options are not compatible"));
|
|
|
|
}
|
|
|
|
|
2020-11-10 04:59:38 +03:00
|
|
|
RefPtr<nsFileStream> stream = new nsFileStream();
|
|
|
|
if (nsresult rv =
|
2020-12-03 07:10:30 +03:00
|
|
|
stream->Init(aFile, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
|
2020-11-10 19:04:28 +03:00
|
|
|
NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage("Could not open the file at %s",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
2020-11-10 04:59:38 +03:00
|
|
|
int64_t bufSize = 0;
|
|
|
|
|
2020-07-30 17:02:34 +03:00
|
|
|
if (aMaxBytes.isNothing()) {
|
|
|
|
// Limitation: We cannot read files that are larger than the max size of a
|
2020-07-30 17:02:50 +03:00
|
|
|
// TypedArray (UINT32_MAX bytes). Reject if the file is too
|
|
|
|
// big to be read.
|
2020-11-10 04:59:38 +03:00
|
|
|
|
|
|
|
int64_t streamSize = -1;
|
|
|
|
if (nsresult rv = stream->GetSize(&streamSize); NS_FAILED(rv)) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED)
|
|
|
|
.WithMessage("Could not get info for the file at %s",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
2020-11-10 04:59:38 +03:00
|
|
|
MOZ_RELEASE_ASSERT(streamSize >= 0);
|
|
|
|
|
|
|
|
if (streamSize > static_cast<int64_t>(UINT32_MAX)) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_TOO_BIG)
|
|
|
|
.WithMessage("Could not read the file at %s because it is too "
|
2020-11-10 04:59:38 +03:00
|
|
|
"large(size=%" PRId64 " bytes)",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get(), streamSize));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
2020-11-10 04:59:38 +03:00
|
|
|
bufSize = static_cast<uint32_t>(streamSize);
|
2021-04-29 02:27:42 +03:00
|
|
|
|
|
|
|
if (aOffset >= bufSize) {
|
|
|
|
bufSize = 0;
|
|
|
|
} else {
|
|
|
|
bufSize = bufSize - aOffset;
|
|
|
|
}
|
2020-07-30 17:02:34 +03:00
|
|
|
} else {
|
|
|
|
bufSize = aMaxBytes.value();
|
|
|
|
}
|
|
|
|
|
2021-04-29 02:27:42 +03:00
|
|
|
if (aOffset > 0) {
|
|
|
|
if (nsresult rv = stream->Seek(PR_SEEK_SET, aOffset); NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
|
|
|
"Could not seek to position %" PRId64 " in file %s", aOffset,
|
|
|
|
aFile->HumanReadablePath().get()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
JsBuffer buffer = JsBuffer::CreateEmpty(aBufferKind);
|
|
|
|
|
2021-01-30 03:33:21 +03:00
|
|
|
if (bufSize > 0) {
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
auto result = JsBuffer::Create(aBufferKind, bufSize);
|
|
|
|
if (result.isErr()) {
|
|
|
|
return result.propagateErr();
|
|
|
|
}
|
|
|
|
buffer = result.unwrap();
|
2021-01-30 03:33:21 +03:00
|
|
|
Span<char> toRead = buffer.BeginWriting();
|
|
|
|
|
|
|
|
// Read the file from disk.
|
|
|
|
uint32_t totalRead = 0;
|
|
|
|
while (totalRead != bufSize) {
|
|
|
|
uint32_t bytesRead = 0;
|
|
|
|
if (nsresult rv =
|
|
|
|
stream->Read(toRead.Elements(), bufSize - totalRead, &bytesRead);
|
|
|
|
NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
|
|
|
"Encountered an unexpected error while reading file(%s)",
|
|
|
|
aFile->HumanReadablePath().get()));
|
|
|
|
}
|
|
|
|
if (bytesRead == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
totalRead += bytesRead;
|
|
|
|
toRead = toRead.From(bytesRead);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
|
2021-01-30 03:33:21 +03:00
|
|
|
buffer.SetLength(totalRead);
|
|
|
|
}
|
2020-08-28 18:57:44 +03:00
|
|
|
|
|
|
|
// Decompress the file contents, if required.
|
|
|
|
if (aDecompress) {
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
return MozLZ4::Decompress(AsBytes(buffer.BeginReading()), aBufferKind);
|
2020-08-28 18:57:44 +03:00
|
|
|
}
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
|
2020-07-30 17:02:34 +03:00
|
|
|
return std::move(buffer);
|
2020-07-30 00:51:48 +03:00
|
|
|
}
|
|
|
|
|
2020-08-26 18:37:25 +03:00
|
|
|
/* static */
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadUTF8Sync(
|
|
|
|
nsIFile* aFile, bool aDecompress) {
|
2021-04-29 02:27:42 +03:00
|
|
|
auto result = ReadSync(aFile, 0, Nothing{}, aDecompress, BufferKind::String);
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
if (result.isErr()) {
|
|
|
|
return result.propagateErr();
|
2020-12-03 07:10:30 +03:00
|
|
|
}
|
|
|
|
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
JsBuffer buffer = result.unwrap();
|
|
|
|
if (!IsUtf8(buffer.BeginReading())) {
|
2020-12-03 07:10:30 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_CORRUPTED)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not read file(%s) because it is not UTF-8 encoded",
|
|
|
|
aFile->HumanReadablePath().get()));
|
|
|
|
}
|
|
|
|
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
return buffer;
|
2020-08-26 18:37:25 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 00:51:48 +03:00
|
|
|
/* static */
|
2020-12-09 08:48:46 +03:00
|
|
|
Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync(
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* aFile, const Span<const uint8_t>& aByteArray,
|
2020-12-09 08:48:46 +03:00
|
|
|
const IOUtils::InternalWriteOpts& aOptions) {
|
2020-07-30 17:02:36 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* backupFile = aOptions.mBackupFile;
|
|
|
|
nsIFile* tempFile = aOptions.mTmpFile;
|
2020-11-10 19:04:49 +03:00
|
|
|
|
2020-07-30 17:02:36 +03:00
|
|
|
bool exists = false;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aFile->Exists(&exists));
|
2020-07-30 17:02:36 +03:00
|
|
|
|
2021-04-30 01:38:02 +03:00
|
|
|
if (exists && aOptions.mMode == WriteMode::Create) {
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
return Err(IOError(NS_ERROR_DOM_TYPE_MISMATCH_ERR)
|
2020-07-30 17:02:50 +03:00
|
|
|
.WithMessage("Refusing to overwrite the file at %s\n"
|
2021-05-05 11:36:48 +03:00
|
|
|
"Specify `mode: \"overwrite\"` to allow "
|
2020-07-30 17:02:50 +03:00
|
|
|
"overwriting the destination",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If backupFile was specified, perform the backup as a move.
|
2020-11-10 19:04:49 +03:00
|
|
|
if (exists && backupFile) {
|
2020-11-10 04:58:47 +03:00
|
|
|
// 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;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_ALWAYS_SUCCEEDS(aFile->Clone(getter_AddRefs(toMove)));
|
2020-11-10 04:58:47 +03:00
|
|
|
|
2021-05-26 19:46:03 +03:00
|
|
|
bool noOverwrite = aOptions.mMode == WriteMode::Create;
|
2021-04-30 01:38:02 +03:00
|
|
|
|
|
|
|
if (MoveSync(toMove, backupFile, noOverwrite).isErr()) {
|
2020-11-10 19:04:49 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
|
|
|
.WithMessage("Failed to backup the source file(%s) to %s",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get(),
|
2020-11-10 19:04:49 +03:00
|
|
|
backupFile->HumanReadablePath().get()));
|
|
|
|
}
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
2020-11-10 19:04:49 +03:00
|
|
|
// 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.
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* writeFile;
|
2020-11-10 19:04:49 +03:00
|
|
|
|
|
|
|
if (tempFile) {
|
2020-12-03 07:10:30 +03:00
|
|
|
writeFile = tempFile;
|
2020-07-30 17:02:36 +03:00
|
|
|
} else {
|
2020-12-03 07:10:30 +03:00
|
|
|
writeFile = aFile;
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
2020-11-10 19:04:49 +03:00
|
|
|
|
2021-04-30 01:38:02 +03:00
|
|
|
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::Create:
|
|
|
|
flags |= PR_CREATE_FILE | PR_EXCL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
MOZ_CRASH("IOUtils: unknown write mode");
|
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:36 +03:00
|
|
|
if (aOptions.mFlush) {
|
|
|
|
flags |= PR_SYNC;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to perform the write and ensure that the file is closed before
|
|
|
|
// continuing.
|
2020-11-10 04:59:53 +03:00
|
|
|
uint32_t totalWritten = 0;
|
2020-07-30 17:02:36 +03:00
|
|
|
{
|
2020-08-28 18:57:44 +03:00
|
|
|
// Compress the byte array if required.
|
|
|
|
nsTArray<uint8_t> compressed;
|
2020-11-10 04:59:53 +03:00
|
|
|
Span<const char> bytes;
|
2020-08-28 18:57:44 +03:00
|
|
|
if (aOptions.mCompress) {
|
|
|
|
auto rv = MozLZ4::Compress(aByteArray);
|
|
|
|
if (rv.isErr()) {
|
|
|
|
return rv.propagateErr();
|
|
|
|
}
|
|
|
|
compressed = rv.unwrap();
|
2020-11-10 04:59:53 +03:00
|
|
|
bytes = Span(reinterpret_cast<const char*>(compressed.Elements()),
|
|
|
|
compressed.Length());
|
2020-08-28 18:57:44 +03:00
|
|
|
} else {
|
2020-11-10 04:59:53 +03:00
|
|
|
bytes = Span(reinterpret_cast<const char*>(aByteArray.Elements()),
|
|
|
|
aByteArray.Length());
|
2020-08-28 18:57:44 +03:00
|
|
|
}
|
|
|
|
|
2020-11-10 04:59:53 +03:00
|
|
|
RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
|
|
|
|
if (nsresult rv = stream->Init(writeFile, flags, 0666, 0); NS_FAILED(rv)) {
|
2021-04-28 21:37:49 +03:00
|
|
|
// 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;
|
|
|
|
}
|
2020-11-10 19:04:49 +03:00
|
|
|
return Err(
|
|
|
|
IOError(rv).WithMessage("Could not open the file at %s for writing",
|
|
|
|
writeFile->HumanReadablePath().get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
2020-11-10 04:59:53 +03:00
|
|
|
|
|
|
|
// nsFileStream::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).WithMessage(
|
|
|
|
"Could not write chunk (size = %" PRIu32
|
|
|
|
") to file %s. The file may be corrupt.",
|
|
|
|
chunkSize, writeFile->HumanReadablePath().get()));
|
|
|
|
}
|
|
|
|
pendingBytes = pendingBytes.From(bytesWritten);
|
|
|
|
totalWritten += bytesWritten;
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
// If tempFile was passed, check destFile against writeFile and, if they
|
|
|
|
// differ, the operation is finished by performing a move.
|
|
|
|
if (tempFile) {
|
2020-11-10 19:04:49 +03:00
|
|
|
nsAutoStringN<256> destPath;
|
|
|
|
nsAutoStringN<256> writePath;
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(destPath));
|
2020-11-10 19:04:49 +03:00
|
|
|
MOZ_ALWAYS_SUCCEEDS(writeFile->GetPath(writePath));
|
|
|
|
|
2020-11-10 04:58:47 +03:00
|
|
|
// nsIFile::MoveToFollowingLinks will only update the path of the file if
|
|
|
|
// the move succeeds.
|
2021-04-28 21:37:49 +03:00
|
|
|
if (destPath != writePath) {
|
|
|
|
if (aOptions.mTmpFile) {
|
|
|
|
bool isDir = false;
|
|
|
|
if (nsresult rv = aFile->IsDirectory(&isDir);
|
|
|
|
NS_FAILED(rv) && !IsFileNotFound(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage("Could not stat the file at %s",
|
|
|
|
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)
|
|
|
|
.WithMessage("Could not open the file at %s for writing",
|
|
|
|
aFile->HumanReadablePath().get()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MoveSync(writeFile, aFile, /* aNoOverwrite = */ false).isErr()) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not move temporary file(%s) to destination(%s)",
|
|
|
|
writeFile->HumanReadablePath().get(),
|
|
|
|
aFile->HumanReadablePath().get()));
|
|
|
|
}
|
2020-11-10 19:04:49 +03:00
|
|
|
}
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
2020-11-10 04:59:53 +03:00
|
|
|
return totalWritten;
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
/* static */
|
2020-12-03 07:10:30 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::MoveSync(nsIFile* aSourceFile,
|
|
|
|
nsIFile* aDestFile,
|
|
|
|
bool aNoOverwrite) {
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-08-10 19:00:32 +03:00
|
|
|
// Ensure the source file exists before continuing. If it doesn't exist,
|
|
|
|
// subsequent operations can fail in different ways on different platforms.
|
2020-11-10 04:58:47 +03:00
|
|
|
bool srcExists = false;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aSourceFile->Exists(&srcExists));
|
2020-07-30 17:02:50 +03:00
|
|
|
if (!srcExists) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_NOT_FOUND)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not move source file(%s) because it does not exist",
|
2020-12-03 07:10:30 +03:00
|
|
|
aSourceFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks, "move", aSourceFile,
|
|
|
|
aDestFile, aNoOverwrite);
|
2020-08-10 19:00:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-12-03 07:10:30 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::CopySync(nsIFile* aSourceFile,
|
|
|
|
nsIFile* aDestFile,
|
|
|
|
bool aNoOverwrite,
|
|
|
|
bool aRecursive) {
|
2020-08-10 19:00:32 +03:00
|
|
|
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;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aSourceFile->Exists(&srcExists));
|
2020-08-10 19:00:32 +03:00
|
|
|
if (!srcExists) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_NOT_FOUND)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not copy source file(%s) because it does not exist",
|
2020-12-03 07:10:30 +03:00
|
|
|
aSourceFile->HumanReadablePath().get()));
|
2020-08-10 19:00:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If source is a directory, fail immediately unless the recursive option is
|
|
|
|
// true.
|
|
|
|
bool srcIsDir = false;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aSourceFile->IsDirectory(&srcIsDir));
|
2020-08-10 19:00:32 +03:00
|
|
|
if (srcIsDir && !aRecursive) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
|
|
|
.WithMessage(
|
|
|
|
"Refused to copy source directory(%s) to the destination(%s)\n"
|
|
|
|
"Specify the `recursive: true` option to allow copying "
|
|
|
|
"directories",
|
2020-12-03 07:10:30 +03:00
|
|
|
aSourceFile->HumanReadablePath().get(),
|
|
|
|
aDestFile->HumanReadablePath().get()));
|
2020-08-10 19:00:32 +03:00
|
|
|
}
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks, "copy", aSourceFile,
|
|
|
|
aDestFile, aNoOverwrite);
|
2020-08-10 19:00:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
template <typename CopyOrMoveFn>
|
2020-11-10 04:58:47 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod,
|
|
|
|
const char* aMethodName,
|
|
|
|
nsIFile* aSource,
|
|
|
|
nsIFile* aDest,
|
|
|
|
bool aNoOverwrite) {
|
2020-08-26 18:37:35 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
2020-08-10 19:00:32 +03:00
|
|
|
|
|
|
|
// Case 1: Destination is an existing directory. Copy/move source into dest.
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
bool destIsDir = false;
|
|
|
|
bool destExists = true;
|
|
|
|
|
2020-11-10 04:58:47 +03:00
|
|
|
nsresult rv = aDest->IsDirectory(&destIsDir);
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
if (NS_SUCCEEDED(rv) && destIsDir) {
|
2020-09-23 18:17:15 +03:00
|
|
|
rv = (aSource->*aMethod)(aDest, u""_ns);
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
2020-08-10 19:00:32 +03:00
|
|
|
"Could not %s source file(%s) to destination directory(%s)",
|
2020-11-10 04:58:47 +03:00
|
|
|
aMethodName, aSource->HumanReadablePath().get(),
|
|
|
|
aDest->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
return Ok();
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
destExists = false;
|
|
|
|
}
|
|
|
|
|
2020-08-10 19:00:32 +03:00
|
|
|
// 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) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_ALREADY_EXISTS)
|
|
|
|
.WithMessage(
|
2020-08-10 19:00:32 +03:00
|
|
|
"Could not %s source file(%s) to destination(%s) because the "
|
2020-07-30 17:02:50 +03:00
|
|
|
"destination already exists and overwrites are not allowed\n"
|
|
|
|
"Specify the `noOverwrite: false` option to mitigate this "
|
|
|
|
"error",
|
2020-11-10 04:58:47 +03:00
|
|
|
aMethodName, aSource->HumanReadablePath().get(),
|
|
|
|
aDest->HumanReadablePath().get()));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
if (destExists && !destIsDir) {
|
|
|
|
// If the source file is a directory, but the target is a file, abort early.
|
2020-08-10 19:00:32 +03:00
|
|
|
// Different implementations of |CopyTo| and |MoveTo| seem to handle this
|
|
|
|
// error case differently (or not at all), so we explicitly handle it here.
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
bool srcIsDir = false;
|
2020-08-10 19:00:32 +03:00
|
|
|
MOZ_TRY(aSource->IsDirectory(&srcIsDir));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
if (srcIsDir) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR)
|
2020-08-10 19:00:32 +03:00
|
|
|
.WithMessage("Could not %s the source directory(%s) to "
|
|
|
|
"the destination(%s) because the destination "
|
|
|
|
"is not a directory",
|
2020-11-10 04:58:47 +03:00
|
|
|
aMethodName,
|
|
|
|
aSource->HumanReadablePath().get(),
|
|
|
|
aDest->HumanReadablePath().get()));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIFile> destDir;
|
|
|
|
nsAutoString destName;
|
2020-08-10 19:00:32 +03:00
|
|
|
MOZ_TRY(aDest->GetLeafName(destName));
|
|
|
|
MOZ_TRY(aDest->GetParent(getter_AddRefs(destDir)));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
|
2021-01-30 08:47:25 +03:00
|
|
|
// We know `destName` is a file and therefore must have a parent directory.
|
|
|
|
MOZ_RELEASE_ASSERT(destDir);
|
|
|
|
|
2020-11-10 04:58:47 +03:00
|
|
|
// NB: if destDir doesn't exist, then |CopyToFollowingLinks| or
|
|
|
|
// |MoveToFollowingLinks| will create it.
|
2020-08-10 19:00:32 +03:00
|
|
|
rv = (aSource->*aMethod)(destDir, destName);
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
2020-08-10 19:00:32 +03:00
|
|
|
"Could not %s the source file(%s) to the destination(%s)", aMethodName,
|
2020-11-10 04:58:47 +03:00
|
|
|
aSource->HumanReadablePath().get(), aDest->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
return Ok();
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
|
2020-07-18 03:31:57 +03:00
|
|
|
/* static */
|
2020-12-03 07:10:30 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(nsIFile* aFile,
|
|
|
|
bool aIgnoreAbsent,
|
|
|
|
bool aRecursive) {
|
2020-07-30 17:02:38 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
nsresult rv = aFile->Remove(aRecursive);
|
2020-07-18 03:31:57 +03:00
|
|
|
if (aIgnoreAbsent && IsFileNotFound(rv)) {
|
2020-07-30 17:02:38 +03:00
|
|
|
return Ok();
|
2020-07-30 00:52:32 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not remove the file at %s because it does not exist.\n"
|
|
|
|
"Specify the `ignoreAbsent: true` option to mitigate this error",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not remove the non-empty directory at %s.\n"
|
|
|
|
"Specify the `recursive: true` option to mitigate this error",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
return Err(err.WithMessage("Could not remove the file at %s",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
return Ok();
|
2020-07-18 03:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:13:35 +03:00
|
|
|
/* static */
|
2020-12-03 07:10:30 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::MakeDirectorySync(nsIFile* aFile,
|
|
|
|
bool aCreateAncestors,
|
|
|
|
bool aIgnoreExisting,
|
|
|
|
int32_t aMode) {
|
2020-07-30 17:02:38 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2021-07-21 21:38:12 +03:00
|
|
|
nsCOMPtr<nsIFile> parent;
|
|
|
|
MOZ_TRY(aFile->GetParent(getter_AddRefs(parent)));
|
|
|
|
if (!parent) {
|
|
|
|
// If we don't have a parent directory, we were called with a
|
|
|
|
// root directory. If the directory doesn't already exist (e.g., asking
|
|
|
|
// for a drive on Windows that does not exist), we will not be able to
|
|
|
|
// create it.
|
|
|
|
//
|
|
|
|
// Calling `nsLocalFile::Create()` on Windows can fail with
|
|
|
|
// `NS_ERROR_ACCESS_DENIED` trying to create a root directory, but we
|
|
|
|
// would rather the call succeed, so return early if the directory exists.
|
|
|
|
//
|
|
|
|
// Otherwise, we fall through to `nsiFile::Create()` and let it fail there
|
|
|
|
// instead.
|
|
|
|
bool exists = false;
|
|
|
|
MOZ_TRY(aFile->Exists(&exists));
|
|
|
|
if (exists) {
|
|
|
|
return Ok();
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-21 21:38:12 +03:00
|
|
|
nsresult rv =
|
|
|
|
aFile->Create(nsIFile::DIRECTORY_TYPE, aMode, !aCreateAncestors);
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
|
|
|
|
// NB: We may report a success only if the target is an existing
|
|
|
|
// directory. We don't want to silence errors that occur if the target is
|
|
|
|
// an existing file, since trying to create a directory where a regular
|
|
|
|
// file exists may be indicative of a logic error.
|
|
|
|
bool isDirectory;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aFile->IsDirectory(&isDirectory));
|
2020-07-30 17:02:50 +03:00
|
|
|
if (!isDirectory) {
|
|
|
|
return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY)
|
|
|
|
.WithMessage("Could not create directory because the "
|
|
|
|
"target file(%s) exists "
|
|
|
|
"and is not a directory",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
// The directory exists.
|
|
|
|
// The caller may suppress this error.
|
|
|
|
if (aIgnoreExisting) {
|
|
|
|
return Ok();
|
|
|
|
}
|
|
|
|
// Otherwise, forward it.
|
2020-11-10 04:59:02 +03:00
|
|
|
return Err(IOError(rv).WithMessage(
|
2020-07-30 17:02:50 +03:00
|
|
|
"Could not create directory because it already exists at %s\n"
|
|
|
|
"Specify the `ignoreExisting: true` option to mitigate this "
|
|
|
|
"error",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
2020-11-10 04:59:02 +03:00
|
|
|
return Err(IOError(rv).WithMessage("Could not create directory at %s",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
return Ok();
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:50 +03:00
|
|
|
Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync(
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* aFile) {
|
2020-07-23 21:15:30 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
InternalFileInfo info;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(info.mPath));
|
2020-07-23 21:15:30 +03:00
|
|
|
|
2020-11-10 04:59:10 +03:00
|
|
|
bool isRegular = false;
|
2020-07-30 17:02:50 +03:00
|
|
|
// IsFile will stat and cache info in the file object. If the file doesn't
|
|
|
|
// exist, or there is an access error, we'll discover it here.
|
|
|
|
// Any subsequent errors are unexpected and will just be forwarded.
|
2020-12-03 07:10:30 +03:00
|
|
|
nsresult rv = aFile->IsFile(&isRegular);
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(
|
|
|
|
err.WithMessage("Could not stat file(%s) because it does not exist",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-07-30 17:02:50 +03:00
|
|
|
}
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can populate the info object by querying the file.
|
|
|
|
info.mType = FileType::Regular;
|
2020-07-23 21:15:30 +03:00
|
|
|
if (!isRegular) {
|
2020-11-10 04:59:10 +03:00
|
|
|
bool isDir = false;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aFile->IsDirectory(&isDir));
|
2020-11-10 04:59:10 +03:00
|
|
|
info.mType = isDir ? FileType::Directory : FileType::Other;
|
2020-07-23 21:15:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int64_t size = -1;
|
|
|
|
if (info.mType == FileType::Regular) {
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aFile->GetFileSize(&size));
|
2020-07-23 21:15:30 +03:00
|
|
|
}
|
|
|
|
info.mSize = size;
|
|
|
|
PRTime lastModified = 0;
|
2020-12-03 07:10:30 +03:00
|
|
|
MOZ_TRY(aFile->GetLastModifiedTime(&lastModified));
|
2020-07-23 21:15:30 +03:00
|
|
|
info.mLastModified = static_cast<int64_t>(lastModified);
|
|
|
|
|
2020-12-03 08:37:33 +03:00
|
|
|
PRTime creationTime = 0;
|
|
|
|
if (nsresult rv = aFile->GetCreationTime(&creationTime); NS_SUCCEEDED(rv)) {
|
|
|
|
info.mCreationTime.emplace(static_cast<int64_t>(creationTime));
|
|
|
|
} else if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
|
|
|
|
// This field is only supported on some platforms.
|
|
|
|
return Err(IOError(rv));
|
|
|
|
}
|
|
|
|
|
2020-12-03 08:37:45 +03:00
|
|
|
MOZ_TRY(aFile->GetPermissions(&info.mPermissions));
|
|
|
|
|
2020-07-23 21:15:30 +03:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2020-08-26 18:31:57 +03:00
|
|
|
/* static */
|
|
|
|
Result<int64_t, IOUtils::IOError> IOUtils::TouchSync(
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* aFile, const Maybe<int64_t>& aNewModTime) {
|
2020-08-26 18:31:57 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
int64_t now = aNewModTime.valueOrFrom([]() {
|
|
|
|
// NB: PR_Now reports time in microseconds since the Unix epoch
|
|
|
|
// (1970-01-01T00:00:00Z). Both nsLocalFile's lastModifiedTime and
|
|
|
|
// JavaScript's Date primitive values are to be expressed in
|
|
|
|
// milliseconds since Epoch.
|
|
|
|
int64_t nowMicros = PR_Now();
|
|
|
|
int64_t nowMillis = nowMicros / PR_USEC_PER_MSEC;
|
|
|
|
return nowMillis;
|
|
|
|
});
|
|
|
|
|
|
|
|
// nsIFile::SetLastModifiedTime will *not* do what is expected when passed 0
|
|
|
|
// as an argument. Rather than setting the time to 0, it will recalculate the
|
|
|
|
// system time and set it to that value instead. We explicit forbid this,
|
|
|
|
// because this side effect is surprising.
|
|
|
|
//
|
|
|
|
// If it ever becomes possible to set a file time to 0, this check should be
|
|
|
|
// removed, though this use case seems rare.
|
|
|
|
if (now == 0) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_ILLEGAL_VALUE)
|
|
|
|
.WithMessage(
|
|
|
|
"Refusing to set the modification time of file(%s) to 0.\n"
|
|
|
|
"To use the current system time, call `touch` with no "
|
2020-11-10 04:59:17 +03:00
|
|
|
"arguments",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-08-26 18:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-12-03 07:10:30 +03:00
|
|
|
nsresult rv = aFile->SetLastModifiedTime(now);
|
2020-08-26 18:31:57 +03:00
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(
|
|
|
|
err.WithMessage("Could not touch file(%s) because it does not exist",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-08-26 18:31:57 +03:00
|
|
|
}
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
return now;
|
|
|
|
}
|
|
|
|
|
2020-08-28 18:49:58 +03:00
|
|
|
/* static */
|
|
|
|
Result<nsTArray<nsString>, IOUtils::IOError> IOUtils::GetChildrenSync(
|
2020-12-03 07:10:30 +03:00
|
|
|
nsIFile* aFile) {
|
2020-08-28 18:49:58 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-11-10 04:59:45 +03:00
|
|
|
nsCOMPtr<nsIDirectoryEnumerator> iter;
|
2020-12-03 07:10:30 +03:00
|
|
|
nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(iter));
|
2020-08-28 18:49:58 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not get children of file(%s) because it does not exist",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-08-28 18:49:58 +03:00
|
|
|
}
|
|
|
|
if (IsNotDirectory(rv)) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not get children of file(%s) because it is not a directory",
|
2020-12-03 07:10:30 +03:00
|
|
|
aFile->HumanReadablePath().get()));
|
2020-08-28 18:49:58 +03:00
|
|
|
}
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
nsTArray<nsString> children;
|
|
|
|
|
|
|
|
bool hasMoreElements = false;
|
|
|
|
MOZ_TRY(iter->HasMoreElements(&hasMoreElements));
|
|
|
|
while (hasMoreElements) {
|
|
|
|
nsCOMPtr<nsIFile> child;
|
|
|
|
MOZ_TRY(iter->GetNextFile(getter_AddRefs(child)));
|
|
|
|
if (child) {
|
|
|
|
nsString path;
|
|
|
|
MOZ_TRY(child->GetPath(path));
|
|
|
|
children.AppendElement(path);
|
|
|
|
}
|
|
|
|
MOZ_TRY(iter->HasMoreElements(&hasMoreElements));
|
|
|
|
}
|
|
|
|
|
|
|
|
return children;
|
|
|
|
}
|
|
|
|
|
2020-12-03 08:37:45 +03:00
|
|
|
/* static */
|
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::SetPermissionsSync(
|
|
|
|
nsIFile* aFile, const uint32_t aPermissions) {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
MOZ_TRY(aFile->SetPermissions(aPermissions));
|
|
|
|
return Ok{};
|
|
|
|
}
|
|
|
|
|
2020-12-03 08:38:03 +03:00
|
|
|
/* static */
|
|
|
|
Result<bool, IOUtils::IOError> IOUtils::ExistsSync(nsIFile* aFile) {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
bool exists = false;
|
|
|
|
MOZ_TRY(aFile->Exists(&exists));
|
|
|
|
|
|
|
|
return exists;
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
/* static */
|
|
|
|
void IOUtils::GetProfileBeforeChange(GlobalObject& aGlobal,
|
|
|
|
JS::MutableHandle<JS::Value> aClient,
|
|
|
|
ErrorResult& aRv) {
|
|
|
|
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
if (auto state = GetState()) {
|
|
|
|
MOZ_RELEASE_ASSERT(state.ref()->mBlockerStatus !=
|
|
|
|
ShutdownBlockerStatus::Uninitialized);
|
|
|
|
|
|
|
|
if (state.ref()->mBlockerStatus == ShutdownBlockerStatus::Failed) {
|
|
|
|
aRv.ThrowAbortError("IOUtils: could not register shutdown blockers");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(state.ref()->mBlockerStatus ==
|
|
|
|
ShutdownBlockerStatus::Initialized);
|
|
|
|
auto result = state.ref()->mEventQueue->GetProfileBeforeChangeClient();
|
|
|
|
if (result.isErr()) {
|
|
|
|
aRv.ThrowAbortError("IOUtils: could not get shutdown client");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<nsIAsyncShutdownClient> client = result.unwrap();
|
|
|
|
MOZ_RELEASE_ASSERT(client);
|
|
|
|
if (nsresult rv = client->GetJsclient(aClient); NS_FAILED(rv)) {
|
|
|
|
aRv.ThrowAbortError("IOUtils: Could not get shutdown jsclient");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aRv.ThrowAbortError(
|
|
|
|
"IOUtils: profileBeforeChange phase has already finished");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sstatic */
|
|
|
|
Maybe<IOUtils::StateMutex::AutoLock> IOUtils::GetState() {
|
|
|
|
auto state = sState.Lock();
|
|
|
|
if (state->mQueueStatus == EventQueueStatus::Shutdown) {
|
|
|
|
return Nothing{};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->mQueueStatus == EventQueueStatus::Uninitialized) {
|
|
|
|
MOZ_RELEASE_ASSERT(!state->mEventQueue);
|
|
|
|
state->mEventQueue = new EventQueue();
|
|
|
|
state->mQueueStatus = EventQueueStatus::Initialized;
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(state->mBlockerStatus ==
|
|
|
|
ShutdownBlockerStatus::Uninitialized);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NS_IsMainThread() &&
|
|
|
|
state->mBlockerStatus == ShutdownBlockerStatus::Uninitialized) {
|
|
|
|
state->SetShutdownHooks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some(std::move(state));
|
|
|
|
}
|
|
|
|
|
|
|
|
IOUtils::EventQueue::EventQueue() {
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
|
|
|
|
"IOUtils::EventQueue", getter_AddRefs(mBackgroundEventTarget)));
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(mBackgroundEventTarget);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IOUtils::State::SetShutdownHooks() {
|
|
|
|
if (mBlockerStatus != ShutdownBlockerStatus::Uninitialized) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NS_WARN_IF(NS_FAILED(mEventQueue->SetShutdownHooks()))) {
|
|
|
|
mBlockerStatus = ShutdownBlockerStatus::Failed;
|
|
|
|
} else {
|
|
|
|
mBlockerStatus = ShutdownBlockerStatus::Initialized;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mBlockerStatus != ShutdownBlockerStatus::Initialized) {
|
|
|
|
NS_WARNING("IOUtils: could not register shutdown blockers.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult IOUtils::EventQueue::SetShutdownHooks() {
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
|
|
|
|
if (!svc) {
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownBlocker> blocker = new IOUtilsShutdownBlocker(
|
|
|
|
IOUtilsShutdownBlocker::Phase::ProfileBeforeChange);
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange;
|
|
|
|
MOZ_TRY(svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)));
|
|
|
|
MOZ_RELEASE_ASSERT(profileBeforeChange);
|
|
|
|
|
|
|
|
MOZ_TRY(profileBeforeChange->AddBlocker(
|
|
|
|
blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
|
|
|
|
u"IOUtils::EventQueue::SetShutdownHooks"_ns));
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> xpcomWillShutdown;
|
|
|
|
MOZ_TRY(svc->GetXpcomWillShutdown(getter_AddRefs(xpcomWillShutdown)));
|
|
|
|
MOZ_RELEASE_ASSERT(xpcomWillShutdown);
|
|
|
|
|
|
|
|
blocker = new IOUtilsShutdownBlocker(
|
|
|
|
IOUtilsShutdownBlocker::Phase::XpcomWillShutdown);
|
|
|
|
MOZ_TRY(xpcomWillShutdown->AddBlocker(
|
|
|
|
blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
|
|
|
|
u"IOUtils::EventQueue::SetShutdownHooks"_ns));
|
|
|
|
|
|
|
|
MOZ_TRY(svc->MakeBarrier(
|
|
|
|
u"IOUtils: waiting for profileBeforeChange IO to complete"_ns,
|
|
|
|
getter_AddRefs(mProfileBeforeChangeBarrier)));
|
|
|
|
MOZ_RELEASE_ASSERT(mProfileBeforeChangeBarrier);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename OkT, typename Fn>
|
|
|
|
RefPtr<IOUtils::IOPromise<OkT>> IOUtils::EventQueue::Dispatch(Fn aFunc) {
|
|
|
|
MOZ_RELEASE_ASSERT(mBackgroundEventTarget);
|
|
|
|
|
|
|
|
return InvokeAsync(
|
|
|
|
mBackgroundEventTarget, __func__, [func = std::move(aFunc)]() {
|
|
|
|
Result<OkT, IOError> result = func();
|
|
|
|
if (result.isErr()) {
|
|
|
|
return IOPromise<OkT>::CreateAndReject(result.unwrapErr(), __func__);
|
|
|
|
}
|
|
|
|
return IOPromise<OkT>::CreateAndResolve(result.unwrap(), __func__);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Result<already_AddRefed<nsIAsyncShutdownClient>, nsresult>
|
|
|
|
IOUtils::EventQueue::GetProfileBeforeChangeClient() {
|
|
|
|
if (!mProfileBeforeChangeBarrier) {
|
|
|
|
return Err(NS_ERROR_NOT_AVAILABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange;
|
|
|
|
MOZ_TRY(mProfileBeforeChangeBarrier->GetClient(
|
|
|
|
getter_AddRefs(profileBeforeChange)));
|
|
|
|
return profileBeforeChange.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
Result<already_AddRefed<nsIAsyncShutdownBarrier>, nsresult>
|
|
|
|
IOUtils::EventQueue::GetProfileBeforeChangeBarrier() {
|
|
|
|
if (!mProfileBeforeChangeBarrier) {
|
|
|
|
return Err(NS_ERROR_NOT_AVAILABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return do_AddRef(mProfileBeforeChangeBarrier);
|
|
|
|
}
|
|
|
|
|
2020-08-28 18:57:44 +03:00
|
|
|
/* static */
|
|
|
|
Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::MozLZ4::Compress(
|
|
|
|
Span<const uint8_t> aUncompressed) {
|
|
|
|
nsTArray<uint8_t> result;
|
|
|
|
size_t worstCaseSize =
|
|
|
|
Compression::LZ4::maxCompressedSize(aUncompressed.Length()) + HEADER_SIZE;
|
|
|
|
if (!result.SetCapacity(worstCaseSize, fallible)) {
|
|
|
|
return Err(IOError(NS_ERROR_OUT_OF_MEMORY)
|
|
|
|
.WithMessage("Could not allocate buffer to compress data"));
|
|
|
|
}
|
|
|
|
result.AppendElements(Span(MAGIC_NUMBER.data(), MAGIC_NUMBER.size()));
|
|
|
|
std::array<uint8_t, sizeof(uint32_t)> contentSizeBytes{};
|
|
|
|
LittleEndian::writeUint32(contentSizeBytes.data(), aUncompressed.Length());
|
|
|
|
result.AppendElements(Span(contentSizeBytes.data(), contentSizeBytes.size()));
|
|
|
|
|
|
|
|
if (aUncompressed.Length() == 0) {
|
|
|
|
// Don't try to compress an empty buffer.
|
|
|
|
// Just return the correctly formed header.
|
|
|
|
result.SetLength(HEADER_SIZE);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t compressed = Compression::LZ4::compress(
|
|
|
|
reinterpret_cast<const char*>(aUncompressed.Elements()),
|
|
|
|
aUncompressed.Length(),
|
|
|
|
reinterpret_cast<char*>(result.Elements()) + HEADER_SIZE);
|
|
|
|
if (!compressed) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_UNEXPECTED).WithMessage("Could not compress data"));
|
|
|
|
}
|
|
|
|
result.SetLength(HEADER_SIZE + compressed);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::MozLZ4::Decompress(
|
|
|
|
Span<const uint8_t> aFileContents, IOUtils::BufferKind aBufferKind) {
|
2020-08-28 18:57:44 +03:00
|
|
|
if (aFileContents.LengthBytes() < HEADER_SIZE) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_CORRUPTED)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not decompress file because the buffer is too short"));
|
|
|
|
}
|
|
|
|
auto header = aFileContents.To(HEADER_SIZE);
|
|
|
|
if (!std::equal(std::begin(MAGIC_NUMBER), std::end(MAGIC_NUMBER),
|
|
|
|
std::begin(header))) {
|
|
|
|
nsCString magicStr;
|
|
|
|
uint32_t i = 0;
|
|
|
|
for (; i < header.Length() - 1; ++i) {
|
|
|
|
magicStr.AppendPrintf("%02X ", header.at(i));
|
|
|
|
}
|
|
|
|
magicStr.AppendPrintf("%02X", header.at(i));
|
|
|
|
|
|
|
|
return Err(IOError(NS_ERROR_FILE_CORRUPTED)
|
|
|
|
.WithMessage("Could not decompress file because it has an "
|
|
|
|
"invalid LZ4 header (wrong magic number: '%s')",
|
|
|
|
magicStr.get()));
|
|
|
|
}
|
|
|
|
size_t numBytes = sizeof(uint32_t);
|
|
|
|
Span<const uint8_t> sizeBytes = header.Last(numBytes);
|
|
|
|
uint32_t expectedDecompressedSize =
|
|
|
|
LittleEndian::readUint32(sizeBytes.data());
|
|
|
|
if (expectedDecompressedSize == 0) {
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
return JsBuffer::CreateEmpty(aBufferKind);
|
2020-08-28 18:57:44 +03:00
|
|
|
}
|
|
|
|
auto contents = aFileContents.From(HEADER_SIZE);
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
auto result = JsBuffer::Create(aBufferKind, expectedDecompressedSize);
|
|
|
|
if (result.isErr()) {
|
|
|
|
return result.propagateErr();
|
2020-08-28 18:57:44 +03:00
|
|
|
}
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
|
|
|
|
JsBuffer decompressed = result.unwrap();
|
2020-08-28 18:57:44 +03:00
|
|
|
size_t actualSize = 0;
|
|
|
|
if (!Compression::LZ4::decompress(
|
|
|
|
reinterpret_cast<const char*>(contents.Elements()), contents.Length(),
|
|
|
|
reinterpret_cast<char*>(decompressed.Elements()),
|
|
|
|
expectedDecompressedSize, &actualSize)) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_CORRUPTED)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not decompress file contents, the file may be corrupt"));
|
|
|
|
}
|
|
|
|
decompressed.SetLength(actualSize);
|
|
|
|
return decompressed;
|
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker, nsIAsyncShutdownBlocker,
|
|
|
|
nsIAsyncShutdownCompletionCallback);
|
2020-07-03 01:32:03 +03:00
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::GetName(nsAString& aName) {
|
2021-03-23 07:26:50 +03:00
|
|
|
aName = u"IOUtils Blocker ("_ns;
|
|
|
|
|
|
|
|
switch (mPhase) {
|
|
|
|
case Phase::ProfileBeforeChange:
|
|
|
|
aName.Append(u"profile-before-change"_ns);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Phase::XpcomWillShutdown:
|
|
|
|
aName.Append(u"xpcom-will-shutdown"_ns);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
MOZ_CRASH("Unknown shutdown phase");
|
|
|
|
}
|
|
|
|
|
|
|
|
aName.Append(')');
|
2020-07-03 01:32:03 +03:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::BlockShutdown(
|
|
|
|
nsIAsyncShutdownClient* aBarrierClient) {
|
2021-03-23 07:26:50 +03:00
|
|
|
using EventQueueStatus = IOUtils::EventQueueStatus;
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
2021-02-22 21:25:05 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
nsCOMPtr<nsIAsyncShutdownBarrier> barrier;
|
2020-07-03 01:32:03 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
{
|
|
|
|
auto state = IOUtils::sState.Lock();
|
|
|
|
if (state->mQueueStatus == EventQueueStatus::Shutdown) {
|
|
|
|
// If the blocker for profile-before-change has already run, then the
|
|
|
|
// event queue is already torn down and we have nothing to do.
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(mPhase == Phase::XpcomWillShutdown);
|
|
|
|
MOZ_RELEASE_ASSERT(!state->mEventQueue);
|
|
|
|
|
|
|
|
Unused << NS_WARN_IF(NS_FAILED(aBarrierClient->RemoveBlocker(this)));
|
|
|
|
mParentClient = nullptr;
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(state->mEventQueue);
|
|
|
|
|
|
|
|
mParentClient = aBarrierClient;
|
|
|
|
|
|
|
|
barrier =
|
|
|
|
state->mEventQueue->GetProfileBeforeChangeBarrier().unwrapOr(nullptr);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
// We cannot barrier->Wait() while holding the mutex because it will lead to
|
|
|
|
// deadlock.
|
|
|
|
if (!barrier || NS_WARN_IF(NS_FAILED(barrier->Wait(this)))) {
|
|
|
|
// If we don't have a barrier, we still need to flush the IOUtils event
|
|
|
|
// queue and disable task submission.
|
|
|
|
//
|
|
|
|
// Likewise, if waiting on the barrier failed, we are going to make our best
|
|
|
|
// attempt to clean up.
|
|
|
|
Unused << Done();
|
|
|
|
}
|
2021-02-18 10:29:17 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::Done() {
|
|
|
|
using EventQueueStatus = IOUtils::EventQueueStatus;
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
2021-03-10 23:34:27 +03:00
|
|
|
|
2021-03-23 07:26:50 +03:00
|
|
|
auto state = IOUtils::sState.Lock();
|
|
|
|
MOZ_RELEASE_ASSERT(state->mEventQueue);
|
|
|
|
|
|
|
|
// This method is called once we have served all shutdown clients. Now we
|
|
|
|
// flush the remaining IO queue and forbid additional IO requests.
|
|
|
|
state->mEventQueue->Dispatch<Ok>([]() { return Ok{}; })
|
|
|
|
->Then(GetMainThreadSerialEventTarget(), __func__,
|
|
|
|
[self = RefPtr(this)]() {
|
|
|
|
if (self->mParentClient) {
|
|
|
|
Unused << NS_WARN_IF(
|
|
|
|
NS_FAILED(self->mParentClient->RemoveBlocker(self)));
|
|
|
|
self->mParentClient = nullptr;
|
|
|
|
|
|
|
|
auto state = IOUtils::sState.Lock();
|
|
|
|
MOZ_RELEASE_ASSERT(state->mEventQueue);
|
|
|
|
state->mEventQueue = nullptr;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
MOZ_RELEASE_ASSERT(state->mQueueStatus == EventQueueStatus::Initialized);
|
|
|
|
state->mQueueStatus = EventQueueStatus::Shutdown;
|
|
|
|
|
|
|
|
return NS_OK;
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::GetState(nsIPropertyBag** aState) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2020-12-09 08:48:46 +03:00
|
|
|
Result<IOUtils::InternalWriteOpts, IOUtils::IOError>
|
|
|
|
IOUtils::InternalWriteOpts::FromBinding(const WriteOptions& aOptions) {
|
|
|
|
InternalWriteOpts opts;
|
2020-11-10 19:04:49 +03:00
|
|
|
opts.mFlush = aOptions.mFlush;
|
2021-04-30 01:38:02 +03:00
|
|
|
opts.mMode = aOptions.mMode;
|
2020-11-10 19:04:49 +03:00
|
|
|
|
|
|
|
if (aOptions.mBackupFile.WasPassed()) {
|
|
|
|
opts.mBackupFile = new nsLocalFile();
|
|
|
|
if (nsresult rv =
|
|
|
|
opts.mBackupFile->InitWithPath(aOptions.mBackupFile.Value());
|
|
|
|
NS_FAILED(rv)) {
|
|
|
|
return Err(IOUtils::IOError(rv).WithMessage(
|
|
|
|
"Could not parse path of backupFile (%s)",
|
|
|
|
NS_ConvertUTF16toUTF8(aOptions.mBackupFile.Value()).get()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aOptions.mTmpPath.WasPassed()) {
|
|
|
|
opts.mTmpFile = new nsLocalFile();
|
|
|
|
if (nsresult rv = opts.mTmpFile->InitWithPath(aOptions.mTmpPath.Value());
|
|
|
|
NS_FAILED(rv)) {
|
|
|
|
return Err(IOUtils::IOError(rv).WithMessage(
|
|
|
|
"Could not parse path of temp file (%s)",
|
|
|
|
NS_ConvertUTF16toUTF8(aOptions.mTmpPath.Value()).get()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.mCompress = aOptions.mCompress;
|
|
|
|
return opts;
|
|
|
|
}
|
|
|
|
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
/* static */
|
|
|
|
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::JsBuffer::Create(
|
|
|
|
IOUtils::BufferKind aBufferKind, size_t aCapacity) {
|
|
|
|
JsBuffer buffer(aBufferKind, aCapacity);
|
|
|
|
if (aCapacity != 0 && !buffer.mBuffer) {
|
|
|
|
return Err(IOError(NS_ERROR_OUT_OF_MEMORY)
|
|
|
|
.WithMessage("Could not allocate buffer"));
|
|
|
|
}
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
IOUtils::JsBuffer IOUtils::JsBuffer::CreateEmpty(
|
|
|
|
IOUtils::BufferKind aBufferKind) {
|
|
|
|
JsBuffer buffer(aBufferKind, 0);
|
|
|
|
MOZ_RELEASE_ASSERT(buffer.mBuffer == nullptr);
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind, size_t aCapacity)
|
|
|
|
: mBufferKind(aBufferKind), mCapacity(aCapacity), mLength(0) {
|
|
|
|
if (mCapacity) {
|
|
|
|
if (aBufferKind == BufferKind::String) {
|
|
|
|
mBuffer = JS::UniqueChars(
|
|
|
|
js_pod_arena_malloc<char>(js::StringBufferArena, mCapacity));
|
|
|
|
} else {
|
|
|
|
MOZ_RELEASE_ASSERT(aBufferKind == BufferKind::Uint8Array);
|
|
|
|
mBuffer = JS::UniqueChars(
|
|
|
|
js_pod_arena_malloc<char>(js::ArrayBufferContentsArena, mCapacity));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
IOUtils::JsBuffer::JsBuffer(IOUtils::JsBuffer&& aOther) noexcept
|
|
|
|
: mBufferKind(aOther.mBufferKind),
|
|
|
|
mCapacity(aOther.mCapacity),
|
|
|
|
mLength(aOther.mLength),
|
|
|
|
mBuffer(std::move(aOther.mBuffer)) {
|
|
|
|
aOther.mCapacity = 0;
|
|
|
|
aOther.mLength = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
IOUtils::JsBuffer& IOUtils::JsBuffer::operator=(
|
|
|
|
IOUtils::JsBuffer&& aOther) noexcept {
|
|
|
|
mBufferKind = aOther.mBufferKind;
|
|
|
|
mCapacity = aOther.mCapacity;
|
|
|
|
mLength = aOther.mLength;
|
|
|
|
mBuffer = std::move(aOther.mBuffer);
|
|
|
|
|
|
|
|
// Invalidate aOther.
|
|
|
|
aOther.mCapacity = 0;
|
|
|
|
aOther.mLength = 0;
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
JSString* IOUtils::JsBuffer::IntoString(JSContext* aCx, JsBuffer aBuffer) {
|
|
|
|
MOZ_RELEASE_ASSERT(aBuffer.mBufferKind == IOUtils::BufferKind::String);
|
|
|
|
|
|
|
|
if (!aBuffer.mCapacity) {
|
|
|
|
return JS_GetEmptyString(aCx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsAscii(aBuffer.BeginReading())) {
|
|
|
|
// If the string is just plain ASCII, then we can hand the buffer off to
|
|
|
|
// JavaScript as a Latin1 string (since ASCII is a subset of Latin1).
|
|
|
|
JS::UniqueLatin1Chars asLatin1(
|
|
|
|
reinterpret_cast<JS::Latin1Char*>(aBuffer.mBuffer.release()));
|
|
|
|
return JS_NewLatin1String(aCx, std::move(asLatin1), aBuffer.mLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the string is encodable as Latin1, we need to deflate the string to a
|
|
|
|
// Latin1 string to accoutn for UTF-8 characters that are encoded as more than
|
|
|
|
// a single byte.
|
|
|
|
//
|
|
|
|
// Otherwise, the string contains characters outside Latin1 so we have to
|
|
|
|
// inflate to UTF-16.
|
|
|
|
return JS_NewStringCopyUTF8N(
|
|
|
|
aCx, JS::UTF8Chars(aBuffer.mBuffer.get(), aBuffer.mLength));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
JSObject* IOUtils::JsBuffer::IntoUint8Array(JSContext* aCx, JsBuffer aBuffer) {
|
|
|
|
MOZ_RELEASE_ASSERT(aBuffer.mBufferKind == IOUtils::BufferKind::Uint8Array);
|
|
|
|
|
|
|
|
if (!aBuffer.mCapacity) {
|
|
|
|
return JS_NewUint8Array(aCx, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
char* rawBuffer = aBuffer.mBuffer.release();
|
|
|
|
MOZ_RELEASE_ASSERT(rawBuffer);
|
|
|
|
JS::Rooted<JSObject*> arrayBuffer(
|
|
|
|
aCx, JS::NewArrayBufferWithContents(aCx, aBuffer.mLength,
|
|
|
|
reinterpret_cast<void*>(rawBuffer)));
|
|
|
|
|
|
|
|
if (!arrayBuffer) {
|
|
|
|
// The array buffer does not take ownership of the data pointer unless
|
|
|
|
// creation succeeds. We are still on the hook to free it.
|
|
|
|
//
|
|
|
|
// aBuffer will be destructed at end of scope, but its destructor does not
|
|
|
|
// take into account |mCapacity| or |mLength|, so it is OK for them to be
|
|
|
|
// non-zero here with a null |mBuffer|.
|
|
|
|
js_free(rawBuffer);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return JS_NewUint8ArrayWithBuffer(aCx, arrayBuffer, 0, aBuffer.mLength);
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:19:25 +03:00
|
|
|
[[nodiscard]] bool ToJSValue(JSContext* aCx, IOUtils::JsBuffer&& aBuffer,
|
|
|
|
JS::MutableHandle<JS::Value> aValue) {
|
Bug 1680151 - Reduce copies in IOUtils::Read{,UTF8} r=nika,tcampbell
Previously, in both Read and ReadUTF8, we were doing copies where we did not
need to. Read allocated an nsTArray and passed that to JS, which performed a
copy of its contents to create a Uint8Array. ReadUTF8, on the other hand, would
take that nsTArray, convert it into ns nsString (1 copy), and then pass it to
JS for it to recreate the string (2 copies).
Now, we allocate our string and array buffers up front in JS' memory pools
directly and use the JS API to create the strings and arrays ourselves (instead
of relying on Promise::MaybeResolve() to do a copying conversion). Read now
performs 0 copies in the best case (if the file is not compressed) and ReadUTF8
also does 0 copies in the best case (if the file is not compressed and the
string is ASCII). In the worst case, Read performs a single extra allocation
(to decompress the file) and ReadUTF8 performs 2 (to decompress the file and to
convert a UTF-8 string to either a Latin1 string or a UTF-16 string).
Differential Revision: https://phabricator.services.mozilla.com/D99004
2021-01-15 07:28:08 +03:00
|
|
|
if (aBuffer.mBufferKind == IOUtils::BufferKind::String) {
|
|
|
|
JSString* str = IOUtils::JsBuffer::IntoString(aCx, std::move(aBuffer));
|
|
|
|
if (!str) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
aValue.setString(str);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSObject* array = IOUtils::JsBuffer::IntoUint8Array(aCx, std::move(aBuffer));
|
|
|
|
if (!array) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
aValue.setObject(*array);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-09 08:48:51 +03:00
|
|
|
} // namespace mozilla::dom
|
2020-07-03 01:32:03 +03:00
|
|
|
|
2020-11-10 19:04:28 +03:00
|
|
|
#undef REJECT_IF_INIT_PATH_FAILED
|