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/. */
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "mozilla/dom/IOUtils.h"
|
|
|
|
#include "mozilla/dom/Promise.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"
|
2020-07-30 16:11:00 +03:00
|
|
|
#include "mozilla/Result.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-07-30 16:11:00 +03:00
|
|
|
#include "mozilla/Services.h"
|
|
|
|
#include "mozilla/Span.h"
|
2020-07-15 19:04:51 +03:00
|
|
|
#include "mozilla/TextUtils.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nspr/prio.h"
|
|
|
|
#include "nspr/private/pprio.h"
|
|
|
|
#include "nspr/prtypes.h"
|
2020-07-30 16:11:00 +03:00
|
|
|
#include "nsDirectoryServiceDefs.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"
|
2020-07-30 16:11:00 +03:00
|
|
|
#include "nsNativeCharsetUtils.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nsReadableUtils.h"
|
2020-07-30 16:11:00 +03:00
|
|
|
#include "nsString.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
#include "nsThreadManager.h"
|
2020-07-30 16:11:00 +03:00
|
|
|
#include "SpecialSystemDirectory.h"
|
2020-07-03 01:32:03 +03:00
|
|
|
|
|
|
|
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
|
|
|
# include <fcntl.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define REJECT_IF_NULL_EVENT_TARGET(aEventTarget, aJSPromise) \
|
|
|
|
do { \
|
|
|
|
if (!(aEventTarget)) { \
|
|
|
|
(aJSPromise) \
|
|
|
|
->MaybeRejectWithAbortError( \
|
|
|
|
"Could not dispatch task to background thread"); \
|
|
|
|
return (aJSPromise).forget(); \
|
|
|
|
} \
|
|
|
|
} while (false)
|
|
|
|
|
|
|
|
#define REJECT_IF_SHUTTING_DOWN(aJSPromise) \
|
|
|
|
do { \
|
|
|
|
if (sShutdownStarted) { \
|
|
|
|
(aJSPromise) \
|
|
|
|
->MaybeRejectWithNotAllowedError( \
|
|
|
|
"Shutting down and refusing additional I/O tasks"); \
|
|
|
|
return (aJSPromise).forget(); \
|
|
|
|
} \
|
|
|
|
} while (false)
|
|
|
|
|
2020-07-30 17:02:32 +03:00
|
|
|
#define REJECT_IF_RELATIVE_PATH(aPath, aJSPromise) \
|
|
|
|
do { \
|
|
|
|
if (!IsAbsolutePath(aPath)) { \
|
|
|
|
(aJSPromise) \
|
|
|
|
->MaybeRejectWithOperationError(nsPrintfCString( \
|
|
|
|
"Refusing to work with path(%s) because only absolute " \
|
|
|
|
"file paths are permitted", \
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get())); \
|
|
|
|
return (aJSPromise).forget(); \
|
|
|
|
} \
|
|
|
|
} while (false)
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
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) {
|
|
|
|
switch (aResult) {
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-07-15 19:04:51 +03:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// IOUtils implementation
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
|
|
|
StaticDataMutex<StaticRefPtr<nsISerialEventTarget>>
|
|
|
|
IOUtils::sBackgroundEventTarget("sBackgroundEventTarget");
|
|
|
|
/* static */
|
|
|
|
StaticRefPtr<nsIAsyncShutdownClient> IOUtils::sBarrier;
|
|
|
|
/* static */
|
|
|
|
Atomic<bool> IOUtils::sShutdownStarted = Atomic<bool>(false);
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath,
|
|
|
|
const Optional<uint32_t>& aMaxBytes) {
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
|
|
|
NS_ENSURE_TRUE(!!promise, nullptr);
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
2020-07-30 00:51:33 +03:00
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
// Process arguments.
|
2020-07-30 17:02:34 +03:00
|
|
|
Maybe<uint32_t> toRead = Nothing();
|
2020-07-03 01:32:03 +03:00
|
|
|
if (aMaxBytes.WasPassed()) {
|
2020-07-30 17:02:34 +03:00
|
|
|
if (aMaxBytes.Value() == 0) {
|
2020-07-03 01:32:03 +03:00
|
|
|
// Resolve with an empty buffer.
|
|
|
|
nsTArray<uint8_t> arr(0);
|
2020-07-30 16:11:00 +03:00
|
|
|
TypedArrayCreator<Uint8Array> arrCreator(arr);
|
|
|
|
promise->MaybeResolve(arrCreator);
|
2020-07-03 01:32:03 +03:00
|
|
|
return promise.forget();
|
|
|
|
}
|
2020-07-30 17:02:34 +03:00
|
|
|
toRead.emplace(aMaxBytes.Value());
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:34 +03:00
|
|
|
InvokeAsync(
|
|
|
|
bg, __func__,
|
|
|
|
[path = nsAutoString(aPath), toRead]() {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
auto rv = IOUtils::ReadSync(path, toRead);
|
|
|
|
if (rv.isErr()) {
|
|
|
|
return IOReadMozPromise::CreateAndReject(rv.unwrapErr(), __func__);
|
|
|
|
}
|
|
|
|
return IOReadMozPromise::CreateAndResolve(rv.unwrap(), __func__);
|
|
|
|
})
|
2020-07-30 16:11:00 +03:00
|
|
|
->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise = RefPtr(promise)](const nsTArray<uint8_t>& aBuf) {
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
|
|
|
|
promise->MaybeReject(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const TypedArrayCreator<Uint8Array> arrayCreator(aBuf);
|
|
|
|
promise->MaybeResolve(arrayCreator);
|
|
|
|
},
|
2020-07-30 17:02:34 +03:00
|
|
|
[promise = RefPtr(promise),
|
|
|
|
path = NS_ConvertUTF16toUTF8(aPath)](const nsresult& aError) {
|
|
|
|
switch (aError) {
|
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
promise->MaybeRejectWithNotFoundError(
|
|
|
|
nsPrintfCString("Could not open file at %s", path.get()));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_ACCESS_DENIED:
|
|
|
|
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
|
|
|
"Could not get info for file at %s", path.get()));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_TOO_BIG:
|
|
|
|
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
|
|
|
"File at %s is too large to be read", path.get()));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
|
|
|
aError, "Unexpected error reading file at %s", path.get()));
|
|
|
|
}
|
2020-07-30 16:11:00 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return promise.forget();
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
already_AddRefed<Promise> IOUtils::WriteAtomic(
|
|
|
|
GlobalObject& aGlobal, const nsAString& aPath, const Uint8Array& aData,
|
|
|
|
const WriteAtomicOptions& aOptions) {
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
|
|
|
NS_ENSURE_TRUE(!!promise, nullptr);
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
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
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
// Process arguments.
|
|
|
|
aData.ComputeState();
|
2020-07-30 16:11:00 +03:00
|
|
|
FallibleTArray<uint8_t> toWrite;
|
|
|
|
if (!toWrite.InsertElementsAt(0, aData.Data(), aData.Length(), fallible)) {
|
2020-07-03 01:32:03 +03:00
|
|
|
promise->MaybeRejectWithOperationError("Out of memory");
|
|
|
|
return promise.forget();
|
|
|
|
}
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
InvokeAsync(
|
|
|
|
bg, __func__,
|
|
|
|
[destPath = nsString(aPath), toWrite = std::move(toWrite), aOptions]() {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (noOverwrite && exists) {
|
|
|
|
return IOWriteMozPromise::CreateAndReject(
|
|
|
|
nsPrintfCString("Refusing to overwrite the file at %s",
|
|
|
|
NS_ConvertUTF16toUTF8(destPath).get()),
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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__);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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__);
|
|
|
|
})
|
|
|
|
->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise = RefPtr(promise)](const uint32_t& aBytesWritten) {
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
|
|
|
|
promise->MaybeReject(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
promise->MaybeResolve(aBytesWritten);
|
|
|
|
},
|
|
|
|
[promise = RefPtr(promise)](const nsACString& aMsg) {
|
|
|
|
promise->MaybeRejectWithOperationError(aMsg);
|
|
|
|
});
|
|
|
|
|
|
|
|
return promise.forget();
|
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 */
|
|
|
|
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);
|
2020-07-30 17:02:32 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aSourcePath, promise);
|
|
|
|
REJECT_IF_RELATIVE_PATH(aDestPath, promise);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
2020-07-30 00:51:33 +03:00
|
|
|
|
2020-07-30 01:35:45 +03:00
|
|
|
// Process arguments.
|
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 noOverwrite = false;
|
|
|
|
if (aOptions.IsAnyMemberPresent()) {
|
|
|
|
noOverwrite = aOptions.mNoOverwrite;
|
|
|
|
}
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
InvokeAsync(bg, __func__,
|
|
|
|
[srcPathString = nsAutoString(aSourcePath),
|
|
|
|
destPathString = nsAutoString(aDestPath), noOverwrite]() {
|
|
|
|
nsresult rv =
|
|
|
|
MoveSync(srcPathString, destPathString, noOverwrite);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return IOMozPromise::CreateAndReject(rv, __func__);
|
|
|
|
}
|
|
|
|
return IOMozPromise::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();
|
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) {
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2020-07-30 17:02:32 +03:00
|
|
|
NS_ENSURE_TRUE(!!promise, nullptr);
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
2020-07-30 00:51:33 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
InvokeAsync(bg, __func__,
|
|
|
|
[path = nsAutoString(aPath), aOptions]() {
|
|
|
|
nsresult rv = RemoveSync(path, aOptions.mIgnoreAbsent,
|
|
|
|
aOptions.mRecursive);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return IOMozPromise::CreateAndReject(rv, __func__);
|
|
|
|
}
|
|
|
|
return IOMozPromise::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_TARGET_DOES_NOT_EXIST:
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
promise->MaybeRejectWithNotFoundError(
|
|
|
|
"Target file does not exist");
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_DIR_NOT_EMPTY:
|
|
|
|
promise->MaybeRejectWithOperationError(
|
|
|
|
"Could not remove non-empty directory, specify the "
|
|
|
|
"`recursive: true` option to mitigate this error");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
|
|
|
aError, "Unexpected error removing file"));
|
|
|
|
}
|
|
|
|
});
|
2020-07-18 03:31:57 +03:00
|
|
|
|
2020-07-30 16:11:00 +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) {
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2020-07-30 17:02:32 +03:00
|
|
|
NS_ENSURE_TRUE(!!promise, nullptr);
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 00:51:33 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
2020-07-21 18:13:35 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
InvokeAsync(bg, __func__,
|
|
|
|
[path = nsAutoString(aPath), aOptions]() {
|
|
|
|
nsresult rv = CreateDirectorySync(
|
|
|
|
path, aOptions.mCreateAncestors, aOptions.mIgnoreExisting);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return IOMozPromise::CreateAndReject(rv, __func__);
|
|
|
|
}
|
|
|
|
return IOMozPromise::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_ALREADY_EXISTS:
|
|
|
|
promise->MaybeRejectWithNoModificationAllowedError(
|
|
|
|
"Could not create directory because file already exists");
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
promise->MaybeRejectWithNotFoundError(
|
|
|
|
"Target path has missing ancestors");
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_NOT_DIRECTORY:
|
|
|
|
promise->MaybeRejectWithOperationError(
|
|
|
|
"Target exists and is not a directory");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
|
|
|
aError, "Unexpected error creating directory"));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return promise.forget();
|
2020-07-23 21:15:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal,
|
|
|
|
const nsAString& aPath) {
|
|
|
|
RefPtr<Promise> promise = CreateJSPromise(aGlobal);
|
2020-07-30 17:02:32 +03:00
|
|
|
NS_ENSURE_TRUE(!!promise, nullptr);
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
REJECT_IF_SHUTTING_DOWN(promise);
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, promise);
|
|
|
|
|
|
|
|
InvokeAsync(
|
|
|
|
bg, __func__,
|
|
|
|
[path = nsAutoString(aPath)]() {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
auto rv = StatSync(path);
|
|
|
|
if (rv.isErr()) {
|
|
|
|
return IOStatMozPromise::CreateAndReject(rv.propagateErr(), __func__);
|
|
|
|
}
|
|
|
|
return IOStatMozPromise::CreateAndResolve(rv.unwrap(), __func__);
|
|
|
|
})
|
|
|
|
->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise = RefPtr(promise)](const InternalFileInfo& aInfo) {
|
|
|
|
AutoJSAPI jsapi;
|
|
|
|
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
|
|
|
|
promise->MaybeReject(NS_ERROR_UNEXPECTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
FileInfo jsResult;
|
|
|
|
jsResult.mPath.Construct(aInfo.mPath);
|
|
|
|
jsResult.mType.Construct(aInfo.mType);
|
|
|
|
jsResult.mSize.Construct(aInfo.mSize);
|
|
|
|
jsResult.mLastModified.Construct(aInfo.mLastModified);
|
|
|
|
promise->MaybeResolve(jsResult);
|
|
|
|
},
|
|
|
|
[promise = RefPtr(promise)](const nsresult& aError) {
|
|
|
|
switch (aError) {
|
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
promise->MaybeRejectWithNotFoundError(
|
|
|
|
"Target file does not exist");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
|
|
|
aError, "Unexpected error accessing file"));
|
|
|
|
}
|
|
|
|
});
|
2020-07-23 21:15:30 +03:00
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
return promise.forget();
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
|
|
|
already_AddRefed<nsISerialEventTarget> IOUtils::GetBackgroundEventTarget() {
|
|
|
|
if (sShutdownStarted) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto lockedBackgroundEventTarget = sBackgroundEventTarget.Lock();
|
|
|
|
if (!lockedBackgroundEventTarget.ref()) {
|
|
|
|
RefPtr<nsISerialEventTarget> et;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
|
|
|
|
"IOUtils::BackgroundIOThread", getter_AddRefs(et)));
|
|
|
|
MOZ_ASSERT(et);
|
|
|
|
*lockedBackgroundEventTarget = et;
|
|
|
|
|
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
IOUtils::SetShutdownHooks();
|
|
|
|
} else {
|
|
|
|
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
|
|
|
|
__func__, []() { IOUtils::SetShutdownHooks(); });
|
|
|
|
NS_DispatchToMainThread(runnable.forget());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return do_AddRef(*lockedBackgroundEventTarget);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
already_AddRefed<nsIAsyncShutdownClient> IOUtils::GetShutdownBarrier() {
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
if (!sBarrier) {
|
|
|
|
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
|
|
|
|
MOZ_ASSERT(svc);
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> barrier;
|
|
|
|
nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
|
|
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
sBarrier = barrier;
|
|
|
|
}
|
|
|
|
return do_AddRef(sBarrier);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
void IOUtils::SetShutdownHooks() {
|
|
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
|
|
|
|
nsCOMPtr<nsIAsyncShutdownBlocker> blocker = new IOUtilsShutdownBlocker();
|
|
|
|
|
|
|
|
nsresult rv = barrier->AddBlocker(
|
|
|
|
blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
|
|
|
|
u"IOUtils: waiting for pending I/O to finish"_ns);
|
|
|
|
// Adding a new shutdown blocker should only fail if the current shutdown
|
|
|
|
// phase has completed. Ensure that we have set our shutdown flag to stop
|
|
|
|
// accepting new I/O tasks in this case.
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
sShutdownStarted = true;
|
|
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::OpenExistingSync(
|
2020-07-15 19:04:17 +03:00
|
|
|
const nsAString& aPath, int32_t aFlags) {
|
2020-07-03 01:32:03 +03:00
|
|
|
// 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);
|
|
|
|
|
2020-07-15 19:04:17 +03:00
|
|
|
// 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);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-07-15 19:04:17 +03:00
|
|
|
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);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-07-30 17:02:34 +03:00
|
|
|
Result<nsTArray<uint8_t>, nsresult> IOUtils::ReadSync(
|
|
|
|
const nsAString& aPath, const Maybe<uint32_t>& aMaxBytes) {
|
2020-07-03 01:32:03 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
2020-07-30 17:02:34 +03:00
|
|
|
UniquePtr<PRFileDesc, PR_CloseDelete> fd = OpenExistingSync(aPath, PR_RDONLY);
|
|
|
|
if (!fd) {
|
|
|
|
return Err(NS_ERROR_FILE_NOT_FOUND);
|
|
|
|
}
|
|
|
|
uint32_t bufSize;
|
|
|
|
if (aMaxBytes.isNothing()) {
|
|
|
|
// 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 Err(NS_ERROR_FILE_ACCESS_DENIED);
|
|
|
|
}
|
|
|
|
if (static_cast<uint64_t>(info.size) > UINT32_MAX) {
|
|
|
|
return Err(NS_ERROR_FILE_TOO_BIG);
|
|
|
|
}
|
|
|
|
bufSize = static_cast<uint32_t>(info.size);
|
|
|
|
} else {
|
|
|
|
bufSize = aMaxBytes.value();
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
nsTArray<uint8_t> buffer;
|
2020-07-30 17:02:34 +03:00
|
|
|
if (!buffer.SetCapacity(bufSize, fallible)) {
|
|
|
|
return Err(NS_ERROR_OUT_OF_MEMORY);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If possible, advise the operating system that we will be reading the file
|
2020-07-30 17:02:34 +03:00
|
|
|
// pointed to by |fd| sequentially, in full. This advice is not binding, it
|
2020-07-03 01:32:03 +03:00
|
|
|
// informs the OS about our expectations as an application.
|
|
|
|
#if defined(HAVE_POSIX_FADVISE)
|
2020-07-30 17:02:34 +03:00
|
|
|
posix_fadvise(PR_FileDesc2NativeHandle(fd.get()), 0, 0,
|
|
|
|
POSIX_FADV_SEQUENTIAL);
|
2020-07-03 01:32:03 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
uint32_t totalRead = 0;
|
2020-07-30 17:02:34 +03:00
|
|
|
while (totalRead != bufSize) {
|
2020-07-03 01:32:03 +03:00
|
|
|
int32_t nRead =
|
2020-07-30 17:02:34 +03:00
|
|
|
PR_Read(fd.get(), buffer.Elements() + totalRead, bufSize - totalRead);
|
2020-07-03 01:32:03 +03:00
|
|
|
if (nRead == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (nRead < 0) {
|
2020-07-30 17:02:34 +03:00
|
|
|
return Err(NS_ERROR_UNEXPECTED);
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
totalRead += nRead;
|
|
|
|
DebugOnly<bool> success = buffer.SetLength(totalRead, fallible);
|
|
|
|
MOZ_ASSERT(success);
|
|
|
|
}
|
2020-07-30 17:02:34 +03:00
|
|
|
return std::move(buffer);
|
2020-07-30 00:51:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-07-30 16:11:00 +03:00
|
|
|
nsresult IOUtils::WriteSync(PRFileDesc* aFd, const nsTArray<uint8_t>& aBytes,
|
|
|
|
uint32_t& aResult) {
|
|
|
|
// aBytes comes from a JavaScript TypedArray, which has UINT32_MAX max length.
|
2020-07-03 01:32:03 +03:00
|
|
|
MOZ_ASSERT(aBytes.Length() <= UINT32_MAX);
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
if (aBytes.Length() == 0) {
|
2020-07-30 16:11:00 +03:00
|
|
|
aResult = 0;
|
|
|
|
return NS_OK;
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t bytesWritten = 0;
|
|
|
|
|
|
|
|
// PR_Write can only write up to PR_INT32_MAX bytes at a time, but the data
|
|
|
|
// source might be as large as UINT32_MAX bytes.
|
|
|
|
uint32_t chunkSize = PR_INT32_MAX;
|
|
|
|
uint32_t pendingBytes = aBytes.Length();
|
|
|
|
|
|
|
|
while (pendingBytes > 0) {
|
|
|
|
if (pendingBytes < chunkSize) {
|
|
|
|
chunkSize = pendingBytes;
|
|
|
|
}
|
2020-07-30 16:11:00 +03:00
|
|
|
int32_t rv = PR_Write(aFd, aBytes.Elements() + bytesWritten, chunkSize);
|
2020-07-03 01:32:03 +03:00
|
|
|
if (rv < 0) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return NS_ERROR_FILE_CORRUPTED;
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
pendingBytes -= rv;
|
|
|
|
bytesWritten += rv;
|
|
|
|
}
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
aResult = bytesWritten;
|
|
|
|
return NS_OK;
|
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 */
|
2020-07-30 16:11:00 +03:00
|
|
|
nsresult IOUtils::MoveSync(const nsAString& aSourcePath,
|
|
|
|
const nsAString& aDestPath, bool noOverwrite) {
|
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());
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
|
|
|
|
nsCOMPtr<nsIFile> srcFile = new nsLocalFile();
|
|
|
|
MOZ_TRY(srcFile->InitWithPath(aSourcePath)); // Fails if not absolute.
|
2020-07-30 16:11:00 +03:00
|
|
|
MOZ_TRY(srcFile->Normalize()); // Fails if path does not exist.
|
|
|
|
|
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> destFile = new nsLocalFile();
|
2020-07-30 16:11:00 +03:00
|
|
|
MOZ_TRY(destFile->InitWithPath(aDestPath));
|
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
|
|
|
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).
|
2020-07-30 16:11:00 +03:00
|
|
|
if (!IsFileNotFound(rv)) { // Deliberately ignore "not found" errors.
|
|
|
|
NS_ENSURE_SUCCESS(rv, 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
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return srcFile->MoveTo(destFile, EmptyString());
|
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 16:11:00 +03:00
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
// It's ok if the file doesn't exist. Case 2 handles this below.
|
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-07-30 16:11:00 +03:00
|
|
|
} else {
|
|
|
|
// Bail out early for any other kind of error though.
|
|
|
|
NS_ENSURE_SUCCESS(rv, 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
|
|
|
}
|
|
|
|
|
|
|
|
// Case 2: Destination is a file which may or may not exist. Try to rename the
|
2020-07-30 16:11:00 +03:00
|
|
|
// source to the destination. This will fail if the source is a not a
|
|
|
|
// regular file.
|
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 (noOverwrite && destExists) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return NS_ERROR_FILE_ALREADY_EXISTS;
|
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.
|
|
|
|
// 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) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR;
|
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;
|
|
|
|
MOZ_TRY(destFile->GetLeafName(destName));
|
|
|
|
MOZ_TRY(destFile->GetParent(getter_AddRefs(destDir)));
|
|
|
|
|
|
|
|
// NB: if destDir doesn't exist, then MoveTo will create it.
|
2020-07-30 16:11:00 +03:00
|
|
|
return srcFile->MoveTo(destDir, destName);
|
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-07-30 16:11:00 +03:00
|
|
|
nsresult IOUtils::RemoveSync(const nsAString& aPath, bool aIgnoreAbsent,
|
|
|
|
bool aRecursive) {
|
2020-07-18 03:31:57 +03:00
|
|
|
RefPtr<nsLocalFile> file = new nsLocalFile();
|
|
|
|
MOZ_TRY(file->InitWithPath(aPath));
|
|
|
|
|
|
|
|
nsresult rv = file->Remove(aRecursive);
|
|
|
|
if (aIgnoreAbsent && IsFileNotFound(rv)) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return NS_OK;
|
2020-07-30 00:52:32 +03:00
|
|
|
}
|
2020-07-30 16:11:00 +03:00
|
|
|
return rv;
|
2020-07-18 03:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:13:35 +03:00
|
|
|
/* static */
|
2020-07-30 16:11:00 +03:00
|
|
|
nsresult IOUtils::CreateDirectorySync(const nsAString& aPath,
|
|
|
|
bool aCreateAncestors,
|
|
|
|
bool aIgnoreExisting, int32_t aMode) {
|
2020-07-21 18:13:35 +03:00
|
|
|
RefPtr<nsLocalFile> targetFile = new nsLocalFile();
|
|
|
|
MOZ_TRY(targetFile->InitWithPath(aPath));
|
|
|
|
|
|
|
|
// nsIFile::Create will create ancestor directories by default.
|
2020-07-30 16:11:00 +03:00
|
|
|
// If the caller does not want this behaviour, then check and possibly return
|
|
|
|
// an error.
|
2020-07-21 18:13:35 +03:00
|
|
|
if (!aCreateAncestors) {
|
|
|
|
nsCOMPtr<nsIFile> parent;
|
|
|
|
MOZ_TRY(targetFile->GetParent(getter_AddRefs(parent)));
|
|
|
|
bool parentExists;
|
|
|
|
MOZ_TRY(parent->Exists(&parentExists));
|
|
|
|
if (!parentExists) {
|
2020-07-30 16:11:00 +03:00
|
|
|
return NS_ERROR_FILE_NOT_FOUND;
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult rv = targetFile->Create(nsIFile::DIRECTORY_TYPE, aMode);
|
2020-07-30 16:11:00 +03:00
|
|
|
if (NS_FAILED(rv) && 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;
|
|
|
|
MOZ_TRY(targetFile->IsDirectory(&isDirectory));
|
|
|
|
if (!isDirectory) {
|
|
|
|
return NS_ERROR_FILE_NOT_DIRECTORY;
|
|
|
|
}
|
|
|
|
if (aIgnoreExisting) {
|
|
|
|
return NS_OK;
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
}
|
2020-07-30 16:11:00 +03:00
|
|
|
return rv;
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 16:11:00 +03:00
|
|
|
Result<IOUtils::InternalFileInfo, nsresult> IOUtils::StatSync(
|
2020-07-23 21:15:30 +03:00
|
|
|
const nsAString& aPath) {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
|
|
|
RefPtr<nsLocalFile> file = new nsLocalFile();
|
|
|
|
MOZ_TRY(file->InitWithPath(aPath));
|
|
|
|
|
|
|
|
InternalFileInfo info;
|
|
|
|
info.mPath = nsString(aPath);
|
|
|
|
|
2020-07-30 00:52:32 +03:00
|
|
|
info.mType = FileType::Regular;
|
2020-07-30 16:11:00 +03:00
|
|
|
bool isRegular;
|
|
|
|
MOZ_TRY(file->IsFile(&isRegular));
|
2020-07-23 21:15:30 +03:00
|
|
|
if (!isRegular) {
|
|
|
|
bool isDir;
|
|
|
|
MOZ_TRY(file->IsDirectory(&isDir));
|
|
|
|
if (isDir) {
|
|
|
|
info.mType = FileType::Directory;
|
|
|
|
} else {
|
|
|
|
info.mType = FileType::Other;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t size = -1;
|
|
|
|
if (info.mType == FileType::Regular) {
|
|
|
|
MOZ_TRY(file->GetFileSize(&size));
|
|
|
|
}
|
|
|
|
info.mSize = size;
|
|
|
|
PRTime lastModified = 0;
|
|
|
|
MOZ_TRY(file->GetLastModifiedTime(&lastModified));
|
|
|
|
info.mLastModified = static_cast<int64_t>(lastModified);
|
|
|
|
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker, nsIAsyncShutdownBlocker);
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::GetName(nsAString& aName) {
|
|
|
|
aName = u"IOUtils Blocker"_ns;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::BlockShutdown(
|
|
|
|
nsIAsyncShutdownClient* aBarrierClient) {
|
|
|
|
nsCOMPtr<nsISerialEventTarget> et = IOUtils::GetBackgroundEventTarget();
|
|
|
|
|
|
|
|
IOUtils::sShutdownStarted = true;
|
|
|
|
|
|
|
|
if (!IOUtils::sBarrier) {
|
|
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIRunnable> backgroundRunnable =
|
|
|
|
NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() {
|
|
|
|
nsCOMPtr<nsIRunnable> mainThreadRunnable =
|
|
|
|
NS_NewRunnableFunction(__func__, [self = RefPtr(self)]() {
|
|
|
|
IOUtils::sBarrier->RemoveBlocker(self);
|
|
|
|
|
|
|
|
auto lockedBackgroundET = IOUtils::sBackgroundEventTarget.Lock();
|
|
|
|
*lockedBackgroundET = nullptr;
|
|
|
|
IOUtils::sBarrier = nullptr;
|
|
|
|
});
|
2020-07-15 19:04:17 +03:00
|
|
|
nsresult rv = NS_DispatchToMainThread(mainThreadRunnable.forget());
|
|
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
2020-07-03 01:32:03 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
return et->Dispatch(backgroundRunnable.forget(),
|
|
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP IOUtilsShutdownBlocker::GetState(nsIPropertyBag** aState) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|
|
|
|
|
|
|
|
#undef REJECT_IF_NULL_EVENT_TARGET
|
|
|
|
#undef REJECT_IF_SHUTTING_DOWN
|
2020-07-30 17:02:32 +03:00
|
|
|
#undef REJECT_IF_RELATIVE_PATH
|