Backed out 3 changesets (bug 1650898, bug 1651742, bug 1650227) for bustage on IOUtils.cpp. CLOSED TREE

Backed out changeset 4a1958574a2f (bug 1651742)
Backed out changeset 2809655ba642 (bug 1650898)
Backed out changeset d8930122993c (bug 1650227)
This commit is contained in:
Csoregi Natalia 2020-07-14 23:03:48 +03:00
Родитель 1412ba8ba3
Коммит cf4cf69408
5 изменённых файлов: 125 добавлений и 837 удалений

Просмотреть файл

@ -9,36 +9,22 @@ namespace IOUtils {
* Reads up to |maxBytes| of the file at |path|. If |maxBytes| is unspecified,
* the entire file is read.
*
* @param path An absolute file path.
* @param path A forward-slash separated path.
* @param maxBytes The max bytes to read from the file at path.
*/
Promise<Uint8Array> read(DOMString path, optional unsigned long maxBytes);
/**
* Attempts to safely write |data| to a file at |path|.
* Atomically write |data| to a file at |path|. This operation attempts to
* ensure that until the data is entirely written to disk, the destination
* file is not modified.
*
* This operation can be made atomic by specifying the |tmpFile| option. If
* specified, then this method ensures that the destination file is not
* modified until the data is entirely written to the temporary file, after
* which point the |tmpFile| is moved to the specified |path|.
* This is generally accomplished by writing to a temporary file, then
* performing an overwriting move.
*
* The target file can also be backed up to a |backupFile| before any writes
* are performed to prevent data loss in case of corruption.
*
* @param path An absolute file path.
* @param path A forward-slash separated path.
* @param data Data to write to the file at path.
*/
Promise<unsigned long long> writeAtomic(DOMString path, Uint8Array data, optional WriteAtomicOptions options = {});
/**
* Moves the file from |sourcePath| to |destPath|, creating necessary parents.
* If |destPath| is a directory, then the source file will be moved into the
* destination directory.
*
* @param sourcePath An absolute file path identifying the file or directory
* to move.
* @param destPath An absolute file path identifying the destination
* directory and/or file name.
*/
Promise<void> move(DOMString sourcePath, DOMString destPath, optional MoveOptions options = {});
};
/**
@ -50,10 +36,8 @@ dictionary WriteAtomicOptions {
*/
DOMString backupFile;
/**
* If specified, write the data to a file at |tmpPath| instead of directly to
* the destination. Once the write is complete, the destination will be
* overwritten by a move. Specifying this option will make the write a little
* slower, but also safer.
* If specified, write the data to a file at |tmpPath|. Once the write is
* complete, the destination will be overwritten by a move.
*/
DOMString tmpPath;
/**
@ -68,13 +52,3 @@ dictionary WriteAtomicOptions {
*/
boolean flush = false;
};
/**
* Options to be passed to the |IOUtils.move| method.
*/
dictionary MoveOptions {
/**
* If true, fail if the destination already exists.
*/
boolean noOverwrite = false;
};

Просмотреть файл

@ -8,23 +8,15 @@
#include "mozilla/dom/IOUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Services.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"
#include "nspr/prio.h"
#include "nspr/private/pprio.h"
#include "nspr/prtypes.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsNativeCharsetUtils.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsThreadManager.h"
#include "SpecialSystemDirectory.h"
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
# include <fcntl.h>
@ -53,86 +45,6 @@
namespace mozilla {
namespace dom {
// static helper functions
/**
* Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
* report I/O errors inconsistently. For convenience, this function will attempt
* to match a |nsresult| against known results which imply a file cannot be
* found.
*
* @see nsLocalFileWin.cpp
* @see nsLocalFileUnix.cpp
*/
static bool IsFileNotFound(nsresult aResult) {
switch (aResult) {
case NS_ERROR_FILE_NOT_FOUND:
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
return true;
default:
return false;
}
}
/**
* 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 {
msg.AppendPrintf(": 0x%x", aError);
}
return std::move(msg);
}
static nsCString FormatErrorMessage(nsresult aError,
const char* const aMessage) {
const char* errName = GetStaticErrorName(aError);
if (errName) {
return nsPrintfCString("%s: %s", aMessage, errName);
}
return nsPrintfCString("%s: 0x%x", aMessage, aError);
}
#ifdef XP_WIN
constexpr char PathSeparator = u'\\';
#else
constexpr char PathSeparator = u'/';
#endif
/* static */
bool IOUtils::IsAbsolutePath(const nsAString& aPath) {
// NB: This impl is adapted from js::shell::IsAbsolutePath(JSLinearString*).
const size_t length = aPath.Length();
#ifdef XP_WIN
// On Windows there are various forms of absolute paths (see
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
// for details):
//
// "\..."
// "\\..."
// "C:\..."
//
// The first two cases are handled by the common test below so we only need a
// specific test for the last one here.
if (length > 3 && mozilla::IsAsciiAlpha(aPath.CharAt(0)) &&
aPath.CharAt(1) == u':' && aPath.CharAt(2) == u'\\') {
return true;
}
#endif
return length > 0 && aPath.CharAt(0) == PathSeparator;
}
// IOUtils implementation
/* static */
StaticDataMutex<StaticRefPtr<nsISerialEventTarget>>
IOUtils::sBackgroundEventTarget("sBackgroundEventTarget");
@ -149,16 +61,7 @@ already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
NS_ENSURE_TRUE(!!promise, nullptr);
REJECT_IF_SHUTTING_DOWN(promise);
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
// Process arguments.
if (!IsAbsolutePath(aPath)) {
promise->MaybeRejectWithOperationError(
"Only absolute file paths are permitted");
return promise.forget();
}
uint32_t toRead = 0;
if (aMaxBytes.WasPassed()) {
toRead = aMaxBytes.Value();
@ -171,58 +74,56 @@ already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
}
}
InvokeAsync(bg, __func__,
[path = nsAutoString(aPath), toRead]() {
MOZ_ASSERT(!NS_IsMainThread());
NS_ConvertUTF16toUTF8 path(aPath);
UniquePtr<PRFileDesc, PR_CloseDelete> fd =
OpenExistingSync(path, PR_RDONLY);
if (!fd) {
return IOReadMozPromise::CreateAndReject(
nsPrintfCString("Could not open file at %s",
NS_ConvertUTF16toUTF8(path).get()),
__func__);
}
uint32_t bufSize;
if (toRead == 0) { // maxBytes was unspecified.
// Limitation: We cannot read files that are larger than the
// max size of a TypedArray (UINT32_MAX bytes).
// Reject if the file is too big to be read.
PRFileInfo64 info;
if (PR_FAILURE == PR_GetOpenFileInfo64(fd.get(), &info)) {
return IOReadMozPromise::CreateAndReject(
nsPrintfCString("Could not get info for file at %s",
NS_ConvertUTF16toUTF8(path).get()),
__func__);
}
uint32_t fileSize = info.size;
if (fileSize > UINT32_MAX) {
return IOReadMozPromise::CreateAndReject(
nsPrintfCString("File at %s is too large to read",
NS_ConvertUTF16toUTF8(path).get()),
__func__);
}
bufSize = fileSize;
} else {
bufSize = toRead;
}
nsTArray<uint8_t> fileContents;
nsresult rv =
IOUtils::ReadSync(fd.get(), bufSize, fileContents);
// Do the IO on a background thread and return the result to this thread.
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
if (NS_SUCCEEDED(rv)) {
return IOReadMozPromise::CreateAndResolve(
std::move(fileContents), __func__);
}
if (rv == NS_ERROR_OUT_OF_MEMORY) {
return IOReadMozPromise::CreateAndReject("Out of memory"_ns,
__func__);
}
return IOReadMozPromise::CreateAndReject(
nsPrintfCString("Unexpected error reading file at %s",
NS_ConvertUTF16toUTF8(path).get()),
__func__);
})
InvokeAsync(
bg, __func__,
[path, toRead]() {
MOZ_ASSERT(!NS_IsMainThread());
UniquePtr<PRFileDesc, PR_CloseDelete> fd =
OpenExistingSync(path.get(), PR_RDONLY);
if (!fd) {
return IOReadMozPromise::CreateAndReject(
nsLiteralCString("Could not open file"), __func__);
}
uint32_t bufSize;
if (toRead == 0) { // maxBytes was unspecified.
// Limitation: We cannot read files that are larger than the max size
// of a TypedArray (UINT32_MAX bytes). Reject if the file is too
// big to be read.
PRFileInfo64 info;
if (PR_FAILURE == PR_GetOpenFileInfo64(fd.get(), &info)) {
return IOReadMozPromise::CreateAndReject(
nsLiteralCString("Could not get file info"), __func__);
}
uint32_t fileSize = info.size;
if (fileSize > UINT32_MAX) {
return IOReadMozPromise::CreateAndReject(
nsLiteralCString("File is too large to read"), __func__);
}
bufSize = fileSize;
} else {
bufSize = toRead;
}
nsTArray<uint8_t> fileContents;
nsresult er = IOUtils::ReadSync(fd.get(), bufSize, fileContents);
if (NS_SUCCEEDED(er)) {
return IOReadMozPromise::CreateAndResolve(std::move(fileContents),
__func__);
}
if (er == NS_ERROR_OUT_OF_MEMORY) {
return IOReadMozPromise::CreateAndReject(
nsLiteralCString("Out of memory"), __func__);
}
return IOReadMozPromise::CreateAndReject(
nsLiteralCString("Unexpected error reading file"), __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise = RefPtr(promise)](const nsTArray<uint8_t>& aBuf) {
@ -250,15 +151,7 @@ already_AddRefed<Promise> IOUtils::WriteAtomic(
NS_ENSURE_TRUE(!!promise, nullptr);
REJECT_IF_SHUTTING_DOWN(promise);
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
// Process arguments.
if (!IsAbsolutePath(aPath)) {
promise->MaybeRejectWithOperationError(
"Only absolute file paths are permitted");
return promise.forget();
}
aData.ComputeState();
FallibleTArray<uint8_t> toWrite;
if (!toWrite.InsertElementsAt(0, aData.Data(), aData.Length(), fallible)) {
@ -266,100 +159,57 @@ already_AddRefed<Promise> IOUtils::WriteAtomic(
return promise.forget();
}
InvokeAsync(
bg, __func__,
[destPath = nsString(aPath), toWrite = std::move(toWrite), aOptions]() {
MOZ_ASSERT(!NS_IsMainThread());
// TODO: Implement tmpPath and backupFile options.
// The data to be written to file might be larger than can be written in any
// single call, so we must truncate the file and set the write mode to append
// to the file.
int32_t flags = PR_WRONLY | PR_TRUNCATE | PR_APPEND;
bool noOverwrite = false;
if (aOptions.IsAnyMemberPresent()) {
if (aOptions.mBackupFile.WasPassed() || aOptions.mTmpPath.WasPassed()) {
promise->MaybeRejectWithNotSupportedError(
"Unsupported options were passed");
return promise.forget();
}
if (aOptions.mFlush) {
flags |= PR_SYNC;
}
noOverwrite = aOptions.mNoOverwrite;
}
// Check if the file exists and test it against the noOverwrite option.
const bool& noOverwrite = aOptions.mNoOverwrite;
bool exists = false;
{
UniquePtr<PRFileDesc, PR_CloseDelete> fd =
OpenExistingSync(destPath, PR_RDONLY);
exists = !!fd;
}
NS_ConvertUTF16toUTF8 path(aPath);
if (noOverwrite && exists) {
return IOWriteMozPromise::CreateAndReject(
nsPrintfCString("Refusing to overwrite the file at %s",
NS_ConvertUTF16toUTF8(destPath).get()),
__func__);
}
// Do the IO on a background thread and return the result to this thread.
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
// If backupFile was specified, perform the backup as a move.
if (exists && aOptions.mBackupFile.WasPassed()) {
const nsString& backupFile(aOptions.mBackupFile.Value());
nsresult rv = MoveSync(destPath, backupFile, noOverwrite);
if (NS_FAILED(rv)) {
return IOWriteMozPromise::CreateAndReject(
FormatErrorMessage(rv,
"Failed to back up the file from %s to %s",
NS_ConvertUTF16toUTF8(destPath).get(),
NS_ConvertUTF16toUTF8(backupFile).get()),
__func__);
}
}
InvokeAsync(bg, __func__,
[path, flags, noOverwrite, toWrite = std::move(toWrite)]() {
MOZ_ASSERT(!NS_IsMainThread());
// If tmpPath was specified, we will write to there first, then perform
// a move to ensure the file ends up at the final requested destination.
nsString tmpPath = destPath;
if (aOptions.mTmpPath.WasPassed()) {
tmpPath = aOptions.mTmpPath.Value();
}
// The data to be written to file might be larger than can be
// written in any single call, so we must truncate the file and
// set the write mode to append to the file.
int32_t flags = PR_WRONLY | PR_TRUNCATE | PR_APPEND;
if (aOptions.mFlush) {
flags |= PR_SYNC;
}
UniquePtr<PRFileDesc, PR_CloseDelete> fd =
OpenExistingSync(path.get(), flags);
if (noOverwrite && fd) {
return IOWriteMozPromise::CreateAndReject(
nsLiteralCString("Refusing to overwrite file"), __func__);
}
if (!fd) {
fd = CreateFileSync(path.get(), flags);
}
if (!fd) {
return IOWriteMozPromise::CreateAndReject(
nsLiteralCString("Could not open file"), __func__);
}
uint32_t result;
nsresult er = IOUtils::WriteSync(fd.get(), toWrite, result);
// Try to perform the write and ensure that the file is closed before
// continuing.
uint32_t result = 0;
{
UniquePtr<PRFileDesc, PR_CloseDelete> fd =
OpenExistingSync(tmpPath, flags);
if (!fd) {
fd = CreateFileSync(tmpPath, flags);
}
if (!fd) {
return IOWriteMozPromise::CreateAndReject(
nsPrintfCString("Could not open the file at %s",
NS_ConvertUTF16toUTF8(tmpPath).get()),
__func__);
}
nsresult rv = IOUtils::WriteSync(fd.get(), toWrite, result);
if (NS_FAILED(rv)) {
return IOWriteMozPromise::CreateAndReject(
FormatErrorMessage(
rv,
"Unexpected error occurred while writing to the file at %s",
NS_ConvertUTF16toUTF8(tmpPath).get()),
__func__);
}
}
// The requested destination was written to, so there is nothing left to
// do.
if (destPath == tmpPath) {
return IOWriteMozPromise::CreateAndResolve(result, __func__);
}
// Otherwise, if tmpPath was specified and different from the destPath,
// then the operation is finished by performing a move.
nsresult rv = MoveSync(tmpPath, destPath, false);
if (NS_FAILED(rv)) {
return IOWriteMozPromise::CreateAndReject(
FormatErrorMessage(
rv, "Error moving temporary file at %s to destination at %s",
NS_ConvertUTF16toUTF8(tmpPath).get(),
NS_ConvertUTF16toUTF8(destPath).get()),
__func__);
}
return IOWriteMozPromise::CreateAndResolve(result, __func__);
})
if (NS_SUCCEEDED(er)) {
return IOWriteMozPromise::CreateAndResolve(result, __func__);
}
return IOWriteMozPromise::CreateAndReject(
nsLiteralCString("Unexpected error writing file"),
__func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise = RefPtr(promise)](const uint32_t& aBytesWritten) {
@ -377,80 +227,6 @@ already_AddRefed<Promise> IOUtils::WriteAtomic(
return promise.forget();
}
/* static */
already_AddRefed<Promise> IOUtils::Move(GlobalObject& aGlobal,
const nsAString& aSourcePath,
const nsAString& aDestPath,
const MoveOptions& aOptions) {
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
NS_ENSURE_TRUE(!!promise, nullptr);
REJECT_IF_SHUTTING_DOWN(promise);
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
// Process arguments.
bool noOverwrite = false;
if (aOptions.IsAnyMemberPresent()) {
noOverwrite = aOptions.mNoOverwrite;
}
InvokeAsync(bg, __func__,
[srcPathString = nsAutoString(aSourcePath),
destPathString = nsAutoString(aDestPath), noOverwrite]() {
nsresult rv =
MoveSync(srcPathString, destPathString, noOverwrite);
if (NS_FAILED(rv)) {
return IOMoveMozPromise::CreateAndReject(rv, __func__);
}
return IOMoveMozPromise::CreateAndResolve(true, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise = RefPtr(promise)](const bool&) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
promise->MaybeReject(NS_ERROR_UNEXPECTED);
return;
}
promise->MaybeResolveWithUndefined();
},
[promise = RefPtr(promise)](const nsresult& aError) {
switch (aError) {
case NS_ERROR_FILE_ACCESS_DENIED:
promise->MaybeRejectWithInvalidAccessError("Access denied");
break;
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
case NS_ERROR_FILE_NOT_FOUND:
promise->MaybeRejectWithNotFoundError(
"Source file does not exist");
break;
case NS_ERROR_FILE_ALREADY_EXISTS:
promise->MaybeRejectWithNoModificationAllowedError(
"Destination file exists and overwrites are not allowed");
break;
case NS_ERROR_FILE_READ_ONLY:
promise->MaybeRejectWithNoModificationAllowedError(
"Destination is read only");
break;
case NS_ERROR_FILE_DESTINATION_NOT_DIR:
promise->MaybeRejectWithInvalidAccessError(
"Source is a directory but destination is not");
break;
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
promise->MaybeRejectWithOperationError(
"Only absolute file paths are permitted");
break;
default: {
promise->MaybeRejectWithUnknownError(
FormatErrorMessage(aError, "Unexpected error moving file"));
}
}
});
return promise.forget();
}
/* static */
already_AddRefed<nsISerialEventTarget> IOUtils::GetBackgroundEventTarget() {
if (sShutdownStarted) {
@ -525,38 +301,20 @@ already_AddRefed<Promise> IOUtils::CreateJSPromise(GlobalObject& aGlobal) {
/* static */
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::OpenExistingSync(
const nsAString& aPath, int32_t aFlags) {
const char* aPath, int32_t aFlags) {
// Ensure that CREATE_FILE and EXCL flags were not included, as we do not
// want to create a new file.
MOZ_ASSERT((aFlags & (PR_CREATE_FILE | PR_EXCL)) == 0);
// We open the file descriptor through an nsLocalFile to ensure that the paths
// are interpreted/encoded correctly on all platforms.
RefPtr<nsLocalFile> file = new nsLocalFile();
nsresult rv = file->InitWithPath(aPath);
NS_ENSURE_SUCCESS(rv, nullptr);
PRFileDesc* fd;
rv = file->OpenNSPRFileDesc(aFlags, /* mode */ 0, &fd);
NS_ENSURE_SUCCESS(rv, nullptr);
return UniquePtr<PRFileDesc, PR_CloseDelete>(fd);
return UniquePtr<PRFileDesc, PR_CloseDelete>(PR_Open(aPath, aFlags, 0));
}
/* static */
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::CreateFileSync(
const nsAString& aPath, int32_t aFlags, int32_t aMode) {
// We open the file descriptor through an nsLocalFile to ensure that the paths
// are interpreted/encoded correctly on all platforms.
RefPtr<nsLocalFile> file = new nsLocalFile();
nsresult rv = file->InitWithPath(aPath);
NS_ENSURE_SUCCESS(rv, nullptr);
PRFileDesc* fd;
rv = file->OpenNSPRFileDesc(aFlags | PR_CREATE_FILE | PR_EXCL, aMode, &fd);
NS_ENSURE_SUCCESS(rv, nullptr);
return UniquePtr<PRFileDesc, PR_CloseDelete>(fd);
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::CreateFileSync(const char* aPath,
int32_t aFlags,
int32_t aMode) {
return UniquePtr<PRFileDesc, PR_CloseDelete>(
PR_Open(aPath, aFlags | PR_CREATE_FILE | PR_EXCL, aMode));
}
/* static */
@ -629,68 +387,6 @@ nsresult IOUtils::WriteSync(PRFileDesc* aFd, const nsTArray<uint8_t>& aBytes,
return NS_OK;
}
/* static */
nsresult IOUtils::MoveSync(const nsAString& aSourcePath,
const nsAString& aDestPath, bool noOverwrite) {
MOZ_ASSERT(!NS_IsMainThread());
nsresult rv = NS_OK;
nsCOMPtr<nsIFile> srcFile = new nsLocalFile();
MOZ_TRY(srcFile->InitWithPath(aSourcePath)); // Fails if not absolute.
MOZ_TRY(srcFile->Normalize()); // Fails if path does not exist.
nsCOMPtr<nsIFile> destFile = new nsLocalFile();
MOZ_TRY(destFile->InitWithPath(aDestPath));
rv = destFile->Normalize();
// Normalize can fail for a number of reasons, including if the file doesn't
// exist. It is expected that the file might not exist for a number of calls
// (e.g. if we want to rename a file to a new location).
if (!IsFileNotFound(rv)) { // Deliberately ignore "not found" errors.
NS_ENSURE_SUCCESS(rv, rv);
}
// Case 1: Destination is an existing directory. Move source into dest.
bool destIsDir = false;
bool destExists = true;
rv = destFile->IsDirectory(&destIsDir);
if (NS_SUCCEEDED(rv) && destIsDir) {
return srcFile->MoveTo(destFile, EmptyString());
}
if (IsFileNotFound(rv)) {
// It's ok if the file doesn't exist. Case 2 handles this below.
destExists = false;
} else {
// Bail out early for any other kind of error though.
NS_ENSURE_SUCCESS(rv, rv);
}
// Case 2: Destination is a file which may or may not exist. Try to rename the
// source to the destination. This will fail if the source is a not a
// regular file.
if (noOverwrite && destExists) {
return NS_ERROR_FILE_ALREADY_EXISTS;
}
if (destExists && !destIsDir) {
// If the source file is a directory, but the target is a file, abort early.
// Different implementations of |MoveTo| seem to handle this error case
// differently (or not at all), so we explicitly handle it here.
bool srcIsDir = false;
MOZ_TRY(srcFile->IsDirectory(&srcIsDir));
if (srcIsDir) {
return NS_ERROR_FILE_DESTINATION_NOT_DIR;
}
}
nsCOMPtr<nsIFile> destDir;
nsAutoString destName;
MOZ_TRY(destFile->GetLeafName(destName));
MOZ_TRY(destFile->GetParent(getter_AddRefs(destDir)));
// NB: if destDir doesn't exist, then MoveTo will create it.
return srcFile->MoveTo(destDir, destName);
}
NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker, nsIAsyncShutdownBlocker);
NS_IMETHODIMP IOUtilsShutdownBlocker::GetName(nsAString& aName) {
@ -718,8 +414,8 @@ NS_IMETHODIMP IOUtilsShutdownBlocker::BlockShutdown(
*lockedBackgroundET = nullptr;
IOUtils::sBarrier = nullptr;
});
nsresult rv = NS_DispatchToMainThread(mainThreadRunnable.forget());
NS_ENSURE_SUCCESS_VOID(rv);
nsresult er = NS_DispatchToMainThread(mainThreadRunnable.forget());
NS_ENSURE_SUCCESS_VOID(er);
});
return et->Dispatch(backgroundRunnable.forget(),

Просмотреть файл

@ -17,7 +17,6 @@
#include "nspr/prio.h"
#include "nsIAsyncShutdown.h"
#include "nsISerialEventTarget.h"
#include "nsLocalFile.h"
namespace mozilla {
@ -52,13 +51,6 @@ class IOUtils final {
GlobalObject& aGlobal, const nsAString& aPath, const Uint8Array& aData,
const WriteAtomicOptions& aOptions);
static already_AddRefed<Promise> Move(GlobalObject& aGlobal,
const nsAString& aSourcePath,
const nsAString& aDestPath,
const MoveOptions& aOptions);
static bool IsAbsolutePath(const nsAString& aPath);
private:
~IOUtils() = default;
@ -80,23 +72,23 @@ class IOUtils final {
/**
* Opens an existing file at |path|.
*
* @param path The location of the file as an absolute path string.
* @param path The location of the file as a unix-style UTF-8 path string.
* @param flags PRIO flags, excluding |PR_CREATE| and |PR_EXCL|.
*/
static UniquePtr<PRFileDesc, PR_CloseDelete> OpenExistingSync(
const nsAString& aPath, int32_t aFlags);
const char* aPath, int32_t aFlags);
/**
* Creates a new file at |path|.
*
* @param aPath The location of the file as an absolute path string.
* @param aPath The location of the file as a unix-style UTF-8 path string.
* @param aFlags PRIO flags to be used in addition to |PR_CREATE| and
* |PR_EXCL|.
* @param aMode Optional file mode. Defaults to 0666 to allow the system
* umask to compute the best mode for the new file.
*/
static UniquePtr<PRFileDesc, PR_CloseDelete> CreateFileSync(
const nsAString& aPath, int32_t aFlags, int32_t aMode = 0666);
const char* aPath, int32_t aFlags, int32_t aMode = 0666);
static nsresult ReadSync(PRFileDesc* aFd, const uint32_t aBufSize,
nsTArray<uint8_t>& aResult);
@ -104,19 +96,12 @@ class IOUtils final {
static nsresult WriteSync(PRFileDesc* aFd, const nsTArray<uint8_t>& aBytes,
uint32_t& aResult);
static nsresult MoveSync(const nsAString& aSource, const nsAString& aDest,
bool noOverwrite);
using IOReadMozPromise =
mozilla::MozPromise<nsTArray<uint8_t>, const nsCString,
/* IsExclusive */ true>;
using IOWriteMozPromise =
mozilla::MozPromise<uint32_t, const nsCString, /* IsExclusive */ true>;
using IOMoveMozPromise =
mozilla::MozPromise<bool /* ignored */, const nsresult,
/* IsExclusive */ true>;
};
class IOUtilsShutdownBlocker : public nsIAsyncShutdownBlocker {

Просмотреть файл

@ -14,14 +14,11 @@ importScripts("resource://gre/modules/ObjectUtils.jsm");
importScripts("resource://gre/modules/osfile.jsm");
self.onmessage = async function(msg) {
const tmpDir = OS.Constants.Path.tmpDir;
// IOUtils functionality is the same when called from the main thread, or a
// web worker. These tests are a modified subset of the main thread tests, and
// serve as a confidence check that the implementation is thread-safe.
// serve as a sanity check that the implementation is thread-safe.
await test_api_is_available_on_worker();
await test_full_read_and_write();
await test_move_file();
finish();
info("test_ioutils_worker.xhtml: Test finished");
@ -32,7 +29,7 @@ self.onmessage = async function(msg) {
async function test_full_read_and_write() {
// Write a file.
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_numbers.tmp");
const tmpFileName = "test_ioutils_numbers.tmp";
const bytes = Uint8Array.of(...new Array(50).keys());
const bytesWritten = await self.IOUtils.writeAtomic(tmpFileName, bytes);
is(
@ -59,21 +56,6 @@ self.onmessage = async function(msg) {
cleanup(tmpFileName);
}
async function test_move_file() {
const src = OS.Path.join(tmpDir, "test_move_file_src.tmp");
const dest = OS.Path.join(tmpDir, "test_move_file_dest.tmp");
const bytes = Uint8Array.of(...new Array(50).keys());
await self.IOUtils.writeAtomic(src, bytes);
await self.IOUtils.move(src, dest);
ok(
!OS.File.exists(src) && OS.File.exists(dest),
"IOUtils::move can move files from a worker"
);
cleanup(dest);
}
};
function cleanup(...files) {

Просмотреть файл

@ -24,16 +24,13 @@
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const tmpDir = OS.Constants.Path.tmpDir;
add_task(async function test_api_is_available_on_window() {
ok(window.IOUtils, "IOUtils is present on the window");
});
add_task(async function test_read_failure() {
const doesNotExist = OS.Path.join(tmpDir, "does_not_exist.tmp");
await Assert.rejects(
window.IOUtils.read(doesNotExist),
window.IOUtils.read("does_not_exist.txt"),
/Could not open file/,
"IOUtils::read rejects when file does not exist"
);
@ -41,7 +38,7 @@
add_task(async function test_write_no_overwrite() {
// Make a new file, and try to write to it with overwrites disabled.
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_overwrite.tmp");
const tmpFileName = "test_ioutils_overwrite.tmp";
const untouchableContents = new TextEncoder().encode("Can't touch this!\n");
await window.IOUtils.writeAtomic(tmpFileName, untouchableContents);
@ -50,13 +47,9 @@
window.IOUtils.writeAtomic(tmpFileName, newContents, {
noOverwrite: true,
}),
/Refusing to overwrite the file at */,
/Refusing to overwrite file/,
"IOUtils::writeAtomic rejects writing to existing file if overwrites are disabled"
);
ok(
await fileHasBinaryContents(tmpFileName, untouchableContents),
"IOUtils::writeAtomic doesn't change target file when overwrite is refused"
);
const bytesWritten = await window.IOUtils.writeAtomic(
tmpFileName,
@ -72,107 +65,8 @@
await cleanup(tmpFileName);
});
add_task(async function test_write_with_backup() {
info("Test backup file option with non-existing file");
let fileContents = new TextEncoder().encode("Original file contents");
let destFileName = OS.Path.join(tmpDir, "test_write_with_backup_option.tmp");
let backupFileName = destFileName + ".backup";
let bytesWritten =
await window.IOUtils.writeAtomic(destFileName, fileContents, {
backupFile: backupFileName,
});
ok(
await fileHasTextContents(destFileName, "Original file contents"),
"IOUtils::writeAtomic creates a new file with the correct contents"
);
ok(
!await fileExists(backupFileName),
"IOUtils::writeAtomic does not create a backup if the target file does not exist"
);
is(
bytesWritten,
fileContents.length,
"IOUtils::writeAtomic correctly writes to a new file without performing a backup"
);
info("Test backup file option with existing destination");
let newFileContents = new TextEncoder().encode("New file contents");
ok(await fileExists(destFileName), `Expected ${destFileName} to exist`);
bytesWritten =
await window.IOUtils.writeAtomic(destFileName, newFileContents, {
backupFile: backupFileName,
});
ok(
await fileHasTextContents(backupFileName, "Original file contents"),
"IOUtils::writeAtomic can backup an existing file before writing"
);
ok(
await fileHasTextContents(destFileName, "New file contents"),
"IOUtils::writeAtomic can create the target with the correct contents"
);
is(
bytesWritten,
newFileContents.length,
"IOUtils::writeAtomic correctly writes to the target after taking a backup"
);
await cleanup(destFileName, backupFileName);
});
add_task(async function test_write_with_backup_and_tmp() {
info("Test backup with tmp and backup file options, non-existing destination");
let fileContents = new TextEncoder().encode("Original file contents");
let destFileName = OS.Path.join(tmpDir, "test_write_with_backup_and_tmp_options.tmp");
let backupFileName = destFileName + ".backup";
let tmpFileName = OS.Path.join(tmpDir, "temp_file.tmp");
let bytesWritten =
await window.IOUtils.writeAtomic(destFileName, fileContents, {
backupFile: backupFileName,
tmpPath: tmpFileName,
});
ok(!await fileExists(tmpFileName), "IOUtils::writeAtomic cleans up the tmpFile");
ok(
!await fileExists(backupFileName),
"IOUtils::writeAtomic does not create a backup if the target file does not exist"
);
ok(
await fileHasTextContents(destFileName, "Original file contents"),
"IOUtils::writeAtomic can write to the destination when a temporary file is used"
);
is(
bytesWritten,
fileContents.length,
"IOUtils::writeAtomic can copy tmp file to destination without performing a backup"
);
info("Test backup with tmp and backup file options, existing destination");
let newFileContents = new TextEncoder().encode("New file contents");
bytesWritten =
await window.IOUtils.writeAtomic(destFileName, newFileContents, {
backupFile: backupFileName,
tmpPath: tmpFileName,
});
ok(!await fileExists(tmpFileName), "IOUtils::writeAtomic cleans up the tmpFile");
ok(
await fileHasTextContents(backupFileName, "Original file contents"),
"IOUtils::writeAtomic can create a backup if the target file exists"
);
ok(
await fileHasTextContents(destFileName, "New file contents"),
"IOUtils::writeAtomic can write to the destination when a temporary file is used"
);
is(
bytesWritten,
newFileContents.length,
"IOUtils::writeAtomic IOUtils::writeAtomic can move tmp file to destination after performing a backup"
);
await cleanup(destFileName, backupFileName);
});
add_task(async function test_partial_read() {
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_partial_read.tmp");
const tmpFileName = "test_ioutils_partial_read.tmp";
const bytes = Uint8Array.of(...new Array(50).keys());
const bytesWritten = await window.IOUtils.writeAtomic(tmpFileName, bytes);
is(
@ -200,7 +94,7 @@
add_task(async function test_empty_read_and_write() {
// Trying to write an empty file isn't very useful, but it should still
// succeed.
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_empty.tmp");
const tmpFileName = "test_ioutils_empty.tmp";
const emptyByteArray = new Uint8Array(0);
const bytesWritten = await window.IOUtils.writeAtomic(
tmpFileName,
@ -222,8 +116,7 @@
add_task(async function test_full_read_and_write() {
// Write a file.
info("Test writing to a new binary file");
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_numbers.tmp");
const tmpFileName = "test_ioutils_numbers.tmp";
const bytes = Uint8Array.of(...new Array(50).keys());
const bytesWritten = await window.IOUtils.writeAtomic(tmpFileName, bytes);
is(
@ -233,7 +126,6 @@
);
// Read it back.
info("Test reading a binary file");
let fileContents = await window.IOUtils.read(tmpFileName);
ok(
ObjectUtils.deepEqual(bytes, fileContents) &&
@ -249,259 +141,18 @@
"IOUtils::read can read entire file when requested maxBytes is too large"
);
// Clean up.
await cleanup(tmpFileName);
});
add_task(async function test_write_relative_path() {
const tmpFileName = "test_ioutils_write_relative_path.tmp";
const bytes = Uint8Array.of(...new Array(50).keys());
info("Test writing a file at a relative destination");
await Assert.rejects(
window.IOUtils.writeAtomic(tmpFileName, bytes),
/Only absolute file paths are permitted/,
"IOUtils::writeAtomic only works with absolute paths"
);
});
add_task(async function test_read_relative_path() {
const tmpFileName = "test_ioutils_read_relative_path.tmp";
info("Test reading a file at a relative destination");
await Assert.rejects(
window.IOUtils.read(tmpFileName),
/Only absolute file paths are permitted/,
"IOUtils::writeAtomic only works with absolute paths"
);
});
add_task(async function test_move_relative_path() {
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_move_relative_path.tmp");
const dest = "relative_to_cwd.tmp";
await createFile(tmpFileName, "source");
info("Test moving a file to a relative destination");
await Assert.rejects(
window.IOUtils.move(tmpFileName, dest),
/Only absolute file paths are permitted/,
"IOUtils::move only works with absolute paths"
);
ok(
await fileHasTextContents(tmpFileName, "source"),
"IOUtils::move doesn't change source file when move fails"
);
cleanup(tmpFileName);
});
add_task(async function test_move_rename() {
// Set up.
const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_move_src.tmp");
const destFileName = OS.Path.join(tmpDir, "test_ioutils_move_dest.tmp");
await createFile(tmpFileName, "dest");
// Test.
info("Test move to new file in same directory");
await window.IOUtils.move(tmpFileName, destFileName);
info(`moved ${tmpFileName} to ${destFileName}`);
ok(
!await fileExists(tmpFileName)
&& await fileHasTextContents(destFileName, "dest"),
"IOUtils::move can move source to dest in same directory"
)
// Set up.
info("Test move to existing file with no overwrite");
await createFile(tmpFileName, "source");
// Test.
await Assert.rejects(
window.IOUtils.move(tmpFileName, destFileName, { noOverwrite: true }),
/Destination file exists and overwrites are not allowed/,
"IOUtils::move will refuse to move a file if overwrites are disabled"
);
ok(
await fileExists(tmpFileName)
&& await fileHasTextContents(destFileName, "dest"),
"Failed IOUtils::move doesn't move the source file"
);
// Test.
info("Test move to existing file with overwrite");
await window.IOUtils.move(tmpFileName, destFileName, { noOverwrite: false });
ok(!await fileExists(tmpFileName), "IOUtils::move moved source");
ok(
await fileHasTextContents(destFileName, "source"),
"IOUtils::move overwrote the destination with the source"
);
// Clean up.
await cleanup(tmpFileName, destFileName);
});
add_task(async function test_move_to_dir() {
// Set up.
info("Test move and rename to non-existing directory");
const tmpFileName = OS.Path.join(tmpDir, "test_move_to_dir.tmp");
const destDir = OS.Path.join(tmpDir, "test_move_to_dir.tmp.d");
const dest = OS.Path.join(destDir, "dest.tmp");
await createFile(tmpFileName);
// Test.
ok(!await OS.File.exists(destDir), "Expected path not to exist");
await window.IOUtils.move(tmpFileName, dest);
ok(
!await fileExists(tmpFileName) && await fileExists(dest),
"IOUtils::move creates non-existing parents if needed"
);
// Set up.
info("Test move and rename to existing directory.")
await createFile(tmpFileName);
// Test.
ok(await OS.File.exists(destDir), "Expected path to exist");
await window.IOUtils.move(tmpFileName, dest);
ok(
!await fileExists(tmpFileName)
&& await fileExists(dest),
"IOUtils::move can move/rename a file into an existing dir"
);
// Set up.
info("Test move to existing directory without specifying leaf name.")
await createFile(tmpFileName);
// Test.
await window.IOUtils.move(tmpFileName, destDir);
ok(await OS.File.exists(destDir), "Expected path to exist");
ok(
!await fileExists(tmpFileName)
&& await fileExists(OS.Path.join(destDir, OS.Path.basename(tmpFileName))),
"IOUtils::move can move a file into an existing dir"
);
// Clean up.
await OS.File.removeDir(destDir);
});
add_task(async function test_move_dir() {
// Set up.
info("Test rename an empty directory");
const srcDir = OS.Path.join(tmpDir, "test_move_dir.tmp.d");
const destDir = OS.Path.join(tmpDir, "test_move_dir_dest.tmp.d");
await OS.File.makeDir(srcDir);
// Test.
await window.IOUtils.move(srcDir, destDir);
ok(
!await OS.File.exists(srcDir) && await OS.File.exists(destDir),
"IOUtils::move can rename directories"
);
// Set up.
info("Test move directory and its content into another directory");
await OS.File.makeDir(srcDir);
await createFile(OS.Path.join(srcDir, "file.tmp"), "foo");
// Test.
await window.IOUtils.move(srcDir, destDir);
const destFile = OS.Path.join(destDir, OS.Path.basename(srcDir), "file.tmp");
ok(
!await OS.File.exists(srcDir)
&& await OS.File.exists(destDir)
&& await OS.File.exists(OS.Path.join(destDir, OS.Path.basename(srcDir)))
&& await fileHasTextContents(destFile, "foo"),
"IOUtils::move can move a directory and its contents into another one"
)
// Clean up.
await OS.File.removeDir(destDir);
});
add_task(async function test_move_failures() {
// Set up.
info("Test attempt to rename a non-existent source file");
const notExistsSrc = OS.Path.join(tmpDir, "not_exists_src.tmp");
const notExistsDest = OS.Path.join(tmpDir, "not_exists_dest.tmp");
// Test.
await Assert.rejects(
window.IOUtils.move(notExistsSrc, notExistsDest),
/Source file does not exist/,
"IOUtils::move throws if source file does not exist"
);
ok(
!await fileExists(notExistsSrc) && !await fileExists(notExistsDest),
"IOUtils::move fails if source file does not exist"
);
// Set up.
info("Test attempt to move a directory to a file");
const destFile = OS.Path.join(tmpDir, "test_move_failures_file_dest.tmp");
const srcDir = OS.Path.join(tmpDir, "test_move_failure_src.tmp.d");
await createFile(destFile);
await OS.File.makeDir(srcDir);
ok(await OS.File.exists(srcDir), "Expected directory to exist");
ok(await OS.File.exists(destFile), "Expected file to exist");
// Test.
await Assert.rejects(
window.IOUtils.move(srcDir, destFile),
/Source is a directory but destination is not/,
"IOUtils::move throws if try to move dir into an existing file"
);
// Clean up.
await cleanup(destFile);
await OS.File.removeDir(srcDir);
});
// Utility functions.
Uint8Array.prototype.equals = function equals(other) {
if (this.byteLength !== other.byteLength) return false;
return this.every((val, i) => val === other[i]);
}
async function createFile(location, contents = "") {
if (typeof contents === "string") {
contents = new TextEncoder().encode(contents);
}
await window.IOUtils.writeAtomic(location, contents);
const exists = await fileExists(location);
ok(exists, `Created temporary file at: ${location}`);
}
async function fileHasBinaryContents(location, expectedContents) {
if (!(expectedContents instanceof Uint8Array)) {
throw new TypeError("expectedContents must be a byte array");
}
info(`opening ${location} for reading`);
const bytes = await window.IOUtils.read(location);
return bytes.equals(expectedContents);
}
async function fileHasTextContents(location, expectedContents) {
if (typeof expectedContents !== "string") {
throw new TypeError("expectedContents must be a string");
}
info(`opening ${location} for reading`);
const bytes = await window.IOUtils.read(location);
const contents = new TextDecoder().decode(bytes);
return contents === expectedContents;
}
async function fileExists(file) {
try {
await window.IOUtils.read(file);
} catch (ex) {
return false;
}
return true;
}
async function cleanup(...files) {
for (const file of files) {
await OS.File.remove(file);
const exists = await fileExists(file);
const exists = await OS.File.exists(file);
ok(!exists, `Removed temporary file: ${file}`);
}
}
</script>
</head>