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"
|
|
|
|
#include "mozilla/ResultExtensions.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"
|
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"
|
|
|
|
#include "nsReadableUtils.h"
|
|
|
|
#include "nsThreadManager.h"
|
|
|
|
|
|
|
|
#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-30 17:02:43 +03:00
|
|
|
/**
|
|
|
|
* Unwraps |aResult| into a |MozPromise|.
|
|
|
|
*
|
|
|
|
* If the result is an error, a new MozPromise is created and immediately
|
|
|
|
* rejected with the unwrapped error. Otherwise, if the result is ok, a new
|
|
|
|
* MozPromise is created and immediately resolved with the unwrapped result.
|
|
|
|
*/
|
|
|
|
template <class PromiseT, class OkT, class ErrT>
|
|
|
|
static RefPtr<PromiseT> ToMozPromise(Result<OkT, ErrT>& aResult,
|
|
|
|
const char* aCallSite) {
|
|
|
|
if (aResult.isErr()) {
|
|
|
|
return PromiseT::CreateAndReject(aResult.unwrapErr(), aCallSite);
|
|
|
|
}
|
|
|
|
return PromiseT::CreateAndResolve(aResult.unwrap(), aCallSite);
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_MUST_USE inline bool ToJSValue(
|
|
|
|
JSContext* aCx, const IOUtils::InternalFileInfo& aInternalFileInfo,
|
|
|
|
JS::MutableHandle<JS::Value> aValue) {
|
|
|
|
FileInfo info;
|
|
|
|
info.mPath.Construct(aInternalFileInfo.mPath);
|
|
|
|
info.mType.Construct(aInternalFileInfo.mType);
|
|
|
|
info.mSize.Construct(aInternalFileInfo.mSize);
|
|
|
|
info.mLastModified.Construct(aInternalFileInfo.mLastModified);
|
|
|
|
return ToJSValue(aCx, info, aValue);
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
template <typename MozPromiseT, typename Fn, typename... Args>
|
|
|
|
static RefPtr<MozPromiseT> InvokeToMozPromise(Fn aFunc, Args... aArgs) {
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
auto rv = aFunc(std::forward<Args>(aArgs)...);
|
|
|
|
if (rv.isErr()) {
|
|
|
|
return MozPromiseT::CreateAndReject(rv.unwrapErr(), __func__);
|
|
|
|
}
|
|
|
|
return MozPromiseT::CreateAndResolve(rv.unwrap(), __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
template <typename OkT, typename Fn, typename... Args>
|
|
|
|
already_AddRefed<Promise> IOUtils::RunOnBackgroundThread(
|
|
|
|
RefPtr<Promise>& aPromise, Fn aFunc, Args... aArgs) {
|
|
|
|
REJECT_IF_SHUTTING_DOWN(aPromise);
|
|
|
|
|
|
|
|
RefPtr<nsISerialEventTarget> bg = GetBackgroundEventTarget();
|
|
|
|
REJECT_IF_NULL_EVENT_TARGET(bg, aPromise);
|
|
|
|
|
|
|
|
InvokeAsync(
|
|
|
|
bg, __func__,
|
|
|
|
[fn = aFunc, argsTuple = std::make_tuple(std::move(aArgs)...)]() mutable {
|
|
|
|
return std::apply(
|
|
|
|
[fn](Args... args) mutable {
|
|
|
|
using MozPromiseT = MozPromise<OkT, IOError, true>;
|
|
|
|
return InvokeToMozPromise<MozPromiseT>(
|
|
|
|
fn, std::forward<Args>(args)...);
|
|
|
|
},
|
|
|
|
std::move(argsTuple));
|
|
|
|
})
|
|
|
|
->Then(
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
|
|
[promise = RefPtr(aPromise)](const OkT& ok) {
|
|
|
|
if constexpr (std::is_same_v<OkT, nsTArray<uint8_t>>) {
|
|
|
|
TypedArrayCreator<Uint8Array> arr(ok);
|
|
|
|
promise->MaybeResolve(arr);
|
|
|
|
} else if constexpr (std::is_same_v<OkT, Ok>) {
|
|
|
|
promise->MaybeResolveWithUndefined();
|
|
|
|
} else {
|
|
|
|
promise->MaybeResolve(ok);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[promise = RefPtr(aPromise)](const IOError& err) {
|
|
|
|
RejectJSPromise(promise, err);
|
|
|
|
});
|
|
|
|
return aPromise.forget();
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* 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-03 01:32:03 +03:00
|
|
|
// Process arguments.
|
2020-07-30 17:03:03 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
|
|
|
nsAutoString path(aPath);
|
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 17:03:03 +03:00
|
|
|
promise->MaybeResolve(TypedArrayCreator<Uint8Array>(arr));
|
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:03:03 +03:00
|
|
|
return RunOnBackgroundThread<nsTArray<uint8_t>>(promise, &ReadSync, path,
|
|
|
|
toRead);
|
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-08-07 10:49:47 +03:00
|
|
|
auto buf = Buffer<uint8_t>::CopyFrom(Span(aData.Data(), aData.Length()));
|
2020-07-30 17:03:03 +03:00
|
|
|
if (buf.isNothing()) {
|
2020-07-03 01:32:03 +03:00
|
|
|
promise->MaybeRejectWithOperationError("Out of memory");
|
|
|
|
return promise.forget();
|
|
|
|
}
|
2020-07-30 17:03:03 +03:00
|
|
|
nsAutoString destPath(aPath);
|
|
|
|
InternalWriteAtomicOpts opts;
|
|
|
|
opts.mFlush = aOptions.mFlush;
|
|
|
|
opts.mNoOverwrite = aOptions.mNoOverwrite;
|
|
|
|
if (aOptions.mBackupFile.WasPassed()) {
|
|
|
|
opts.mBackupFile.emplace(aOptions.mBackupFile.Value());
|
|
|
|
}
|
|
|
|
if (aOptions.mTmpPath.WasPassed()) {
|
|
|
|
opts.mTmpPath.emplace(aOptions.mTmpPath.Value());
|
|
|
|
}
|
2020-07-03 01:32:03 +03:00
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
return RunOnBackgroundThread<uint32_t>(promise, &WriteAtomicSync, destPath,
|
|
|
|
std::move(*buf), std::move(opts));
|
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 17:03:03 +03:00
|
|
|
// Process arguments.
|
2020-07-30 17:02:32 +03:00
|
|
|
REJECT_IF_RELATIVE_PATH(aSourcePath, promise);
|
|
|
|
REJECT_IF_RELATIVE_PATH(aDestPath, promise);
|
2020-07-30 17:03:03 +03:00
|
|
|
nsAutoString sourcePath(aSourcePath);
|
|
|
|
nsAutoString destPath(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
|
|
|
bool noOverwrite = false;
|
|
|
|
if (aOptions.IsAnyMemberPresent()) {
|
|
|
|
noOverwrite = aOptions.mNoOverwrite;
|
|
|
|
}
|
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
return RunOnBackgroundThread<Ok>(promise, &MoveSync, sourcePath, destPath,
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 17:03:03 +03:00
|
|
|
nsAutoString path(aPath);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
return RunOnBackgroundThread<Ok>(promise, &RemoveSync, path,
|
|
|
|
aOptions.mIgnoreAbsent, aOptions.mRecursive);
|
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);
|
|
|
|
|
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 17:03:03 +03:00
|
|
|
nsAutoString path(aPath);
|
2020-07-30 00:51:33 +03:00
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
return RunOnBackgroundThread<Ok>(promise, &CreateDirectorySync, path,
|
|
|
|
aOptions.mCreateAncestors,
|
|
|
|
aOptions.mIgnoreExisting, 0777);
|
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);
|
|
|
|
|
|
|
|
REJECT_IF_RELATIVE_PATH(aPath, promise);
|
2020-07-30 17:03:03 +03:00
|
|
|
nsAutoString path(aPath);
|
2020-07-30 16:11:00 +03:00
|
|
|
|
2020-07-30 17:03:03 +03:00
|
|
|
return RunOnBackgroundThread<InternalFileInfo>(promise, &StatSync, path);
|
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);
|
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:50 +03:00
|
|
|
/* static */
|
|
|
|
void IOUtils::RejectJSPromise(const RefPtr<Promise>& aPromise,
|
|
|
|
const IOError& aError) {
|
|
|
|
const auto& errMsg = aError.Message();
|
|
|
|
|
|
|
|
switch (aError.Code()) {
|
|
|
|
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
|
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
|
|
aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("File not found"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_ACCESS_DENIED:
|
|
|
|
aPromise->MaybeRejectWithNotAllowedError(
|
|
|
|
errMsg.refOr("Access was denied to the target file"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_TOO_BIG:
|
|
|
|
aPromise->MaybeRejectWithNotReadableError(
|
|
|
|
errMsg.refOr("Target file is too big"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_ALREADY_EXISTS:
|
|
|
|
aPromise->MaybeRejectWithNoModificationAllowedError(
|
|
|
|
errMsg.refOr("Target file already exists"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Failed to copy or move the target file"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_READ_ONLY:
|
|
|
|
aPromise->MaybeRejectWithReadOnlyError(
|
|
|
|
errMsg.refOr("Target file is read only"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_NOT_DIRECTORY:
|
|
|
|
case NS_ERROR_FILE_DESTINATION_NOT_DIR:
|
|
|
|
aPromise->MaybeRejectWithInvalidAccessError(
|
|
|
|
errMsg.refOr("Target file is not a directory"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Target file path is not recognized"_ns));
|
|
|
|
break;
|
|
|
|
case NS_ERROR_FILE_DIR_NOT_EMPTY:
|
|
|
|
aPromise->MaybeRejectWithOperationError(
|
|
|
|
errMsg.refOr("Target directory is not empty"_ns));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
aPromise->MaybeRejectWithUnknownError(
|
|
|
|
errMsg.refOr(FormatErrorMessage(aError.Code(), "Unexpected error")));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 01:32:03 +03:00
|
|
|
/* static */
|
|
|
|
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::OpenExistingSync(
|
2020-07-15 19:04:17 +03:00
|
|
|
const nsAString& aPath, int32_t aFlags) {
|
2020-07-30 17:02:43 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
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) {
|
2020-07-30 17:02:43 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
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 | 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:50 +03:00
|
|
|
Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::ReadSync(
|
2020-07-30 17:02:34 +03:00
|
|
|
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) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_NOT_FOUND)
|
|
|
|
.WithMessage("Could not open the file at %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
|
|
|
uint32_t bufSize;
|
|
|
|
if (aMaxBytes.isNothing()) {
|
|
|
|
// Limitation: We cannot read files that are larger than the max size of a
|
2020-07-30 17:02:50 +03:00
|
|
|
// TypedArray (UINT32_MAX bytes). Reject if the file is too
|
|
|
|
// big to be read.
|
2020-07-30 17:02:34 +03:00
|
|
|
PRFileInfo64 info;
|
|
|
|
if (PR_FAILURE == PR_GetOpenFileInfo64(fd.get(), &info)) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED)
|
|
|
|
.WithMessage("Could not get info for the file at %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
|
|
|
if (static_cast<uint64_t>(info.size) > UINT32_MAX) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_TOO_BIG)
|
|
|
|
.WithMessage("Could not read the file at %s because it is too "
|
|
|
|
"large(size=%" PRIu64 " bytes)",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get(),
|
|
|
|
static_cast<uint64_t>(info.size)));
|
2020-07-30 17:02:34 +03:00
|
|
|
}
|
|
|
|
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)) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(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:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_UNEXPECTED)
|
|
|
|
.WithMessage("Encountered an unexpected error while reading %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
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 17:02:50 +03:00
|
|
|
Result<uint32_t, IOUtils::IOError> IOUtils::WriteAtomicSync(
|
2020-07-30 17:03:03 +03:00
|
|
|
const nsAString& aDestPath, const Buffer<uint8_t>& aByteArray,
|
|
|
|
const IOUtils::InternalWriteAtomicOpts& aOptions) {
|
2020-07-30 17:02:36 +03:00
|
|
|
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(aDestPath, PR_RDONLY);
|
|
|
|
exists = !!fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (noOverwrite && exists) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS)
|
|
|
|
.WithMessage("Refusing to overwrite the file at %s\n"
|
|
|
|
"Specify `noOverwrite: false` to allow "
|
|
|
|
"overwriting the destination",
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If backupFile was specified, perform the backup as a move.
|
2020-07-30 17:03:03 +03:00
|
|
|
if (exists && aOptions.mBackupFile.isSome() &&
|
|
|
|
MoveSync(aDestPath, aOptions.mBackupFile.value(), noOverwrite).isErr()) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
|
|
|
.WithMessage(
|
|
|
|
"Failed to backup the source file(%s) to %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get(),
|
2020-07-30 17:03:03 +03:00
|
|
|
NS_ConvertUTF16toUTF8(aOptions.mBackupFile.value()).get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
nsAutoString tmpPath;
|
2020-07-30 17:03:03 +03:00
|
|
|
if (aOptions.mTmpPath.isSome()) {
|
|
|
|
tmpPath = aOptions.mTmpPath.value();
|
2020-07-30 17:02:36 +03:00
|
|
|
} else {
|
|
|
|
tmpPath = aDestPath;
|
|
|
|
}
|
|
|
|
// 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) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED)
|
|
|
|
.WithMessage("Could not open the file at %s for writing",
|
|
|
|
NS_ConvertUTF16toUTF8(tmpPath).get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:50 +03:00
|
|
|
auto rv = WriteSync(fd.get(), NS_ConvertUTF16toUTF8(tmpPath), aByteArray);
|
2020-07-30 17:02:36 +03:00
|
|
|
if (rv.isErr()) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return rv.propagateErr();
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
result = rv.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If tmpPath was specified and different from the destPath, then the
|
|
|
|
// operation is finished by performing a move.
|
2020-07-30 17:02:38 +03:00
|
|
|
if (aDestPath != tmpPath && MoveSync(tmpPath, aDestPath, false).isErr()) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
|
|
|
.WithMessage("Could not move temporary file(%s) to destination(%s)",
|
|
|
|
NS_ConvertUTF16toUTF8(tmpPath).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
2020-07-30 17:02:36 +03:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */
|
2020-07-30 17:02:50 +03:00
|
|
|
Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync(
|
2020-07-30 17:03:03 +03:00
|
|
|
PRFileDesc* aFd, const nsACString& aPath, const Buffer<uint8_t>& aBytes) {
|
2020-07-30 17:02:50 +03:00
|
|
|
// 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 17:02:36 +03:00
|
|
|
return 0;
|
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 17:03:03 +03:00
|
|
|
int32_t rv = PR_Write(aFd, aBytes.begin() + bytesWritten, chunkSize);
|
2020-07-03 01:32:03 +03:00
|
|
|
if (rv < 0) {
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_CORRUPTED)
|
|
|
|
.WithMessage("Could not write chunk(size=%" PRIu32
|
|
|
|
") to %s\n"
|
|
|
|
"The file may be corrupt",
|
|
|
|
chunkSize, aPath.BeginReading()));
|
2020-07-03 01:32:03 +03:00
|
|
|
}
|
|
|
|
pendingBytes -= rv;
|
|
|
|
bytesWritten += rv;
|
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:36 +03:00
|
|
|
return bytesWritten;
|
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 17:02:50 +03:00
|
|
|
Result<Ok, IOUtils::IOError> 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;
|
|
|
|
|
2020-07-30 17:02:50 +03:00
|
|
|
// Assess the source 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
|
|
|
nsCOMPtr<nsIFile> srcFile = new nsLocalFile();
|
|
|
|
MOZ_TRY(srcFile->InitWithPath(aSourcePath)); // Fails if not absolute.
|
2020-07-30 17:02:50 +03:00
|
|
|
bool srcExists;
|
|
|
|
// Ensure the source file exists before continuing. If it doesn't exist, the
|
|
|
|
// following operations can fail in different ways on different platforms.
|
|
|
|
MOZ_TRY(srcFile->Exists(&srcExists));
|
|
|
|
if (!srcExists) {
|
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_NOT_FOUND)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not move source file(%s) because it does not exist",
|
|
|
|
NS_ConvertUTF16toUTF8(aSourcePath).get()));
|
|
|
|
}
|
|
|
|
MOZ_TRY(srcFile->Normalize());
|
|
|
|
|
|
|
|
// Prepare the destination 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
|
|
|
nsCOMPtr<nsIFile> destFile = new nsLocalFile();
|
2020-07-30 17:02:50 +03:00
|
|
|
MOZ_TRY(destFile->InitWithPath(aDestPath)); // Fails if not absolute.
|
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 17:02:38 +03:00
|
|
|
if (NS_FAILED(rv) && !IsFileNotFound(rv)) {
|
|
|
|
// Deliberately ignore "not found" errors, but propagate all others.
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(IOError(rv));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 17:02:50 +03:00
|
|
|
rv = srcFile->MoveTo(destFile, EmptyString());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
|
|
|
"Could not move source file(%s) to destination(%s)",
|
|
|
|
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
|
|
|
}
|
|
|
|
return Ok();
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
if (!IsFileNotFound(rv)) {
|
|
|
|
// It's ok if the dest file doesn't exist. Case 2 handles this below.
|
|
|
|
// Bail out early for any other kind of error though.
|
|
|
|
return Err(IOError(rv));
|
|
|
|
}
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
destExists = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Case 2: Destination is a file which may or may not exist. Try to rename the
|
2020-07-30 17:02:50 +03:00
|
|
|
// source to the destination. If the destination exists and the source
|
|
|
|
// is not a regular file, then this may fail.
|
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 17:02:50 +03:00
|
|
|
return Err(
|
|
|
|
IOError(NS_ERROR_FILE_ALREADY_EXISTS)
|
|
|
|
.WithMessage(
|
|
|
|
"Could not move source file(%s) to destination(%s) because the "
|
|
|
|
"destination already exists and overwrites are not allowed\n"
|
|
|
|
"Specify the `noOverwrite: false` option to mitigate this "
|
|
|
|
"error",
|
|
|
|
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
if (destExists && !destIsDir) {
|
|
|
|
// If the source file is a directory, but the target is a file, abort early.
|
|
|
|
// 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 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR)
|
|
|
|
.WithMessage("Could not move the source directory(%s) to "
|
|
|
|
"the destination(%s) "
|
|
|
|
"because the destination is not a directory",
|
|
|
|
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIFile> destDir;
|
|
|
|
nsAutoString destName;
|
|
|
|
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 17:02:50 +03:00
|
|
|
rv = srcFile->MoveTo(destDir, destName);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return Err(IOError(rv).WithMessage(
|
|
|
|
"Could not move the source file(%s) to the destination(%s)",
|
|
|
|
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
|
|
|
}
|
|
|
|
return Ok();
|
Bug 1650227: Implement IOUtils move method r=barret,Gijs
This patch introduces a move method to the IOUtils interface, which allows
for renaming/moving files or directories on disk. Source and destination
files may be specified either by an absolute path, or a relative path from
the current working directory.
This method has well-defined behaviour similar to the POSIX mv command
(except that this may create missing directories as necessary).
The behaviour is briefly summarized below:
1. If the source is a file that exists:
a. If the destination is a file that does not exist, the source is
renamed (and re-parented as a child of the destination parent
directory). The destination parent directory will be created if
necessary.
b. If the destination is a file that does exist, the destination is
replaced with the source (unless the noOverwrite option is true).
2. If the source is a directory that exists:
a. If the destination is a directory, then the source directory is
re-parented such that it becomes a child of the destination.
b. If the destination does not exist, then the source is renamed,
creating additional directories if needed.
c. If the destination is a file, then an error occurs.
3. If the source does not exist, an error occurs.
Differential Revision: https://phabricator.services.mozilla.com/D82202
2020-07-15 19:03:52 +03:00
|
|
|
}
|
|
|
|
|
2020-07-18 03:31:57 +03:00
|
|
|
/* static */
|
2020-07-30 17:02:50 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(const nsAString& aPath,
|
|
|
|
bool aIgnoreAbsent,
|
|
|
|
bool aRecursive) {
|
2020-07-30 17:02:38 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
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 17:02:38 +03:00
|
|
|
return Ok();
|
2020-07-30 00:52:32 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not remove the file at %s because it does not exist.\n"
|
|
|
|
"Specify the `ignoreAbsent: true` option to mitigate this error",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
|
|
|
}
|
|
|
|
if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY) {
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not remove the non-empty directory at %s.\n"
|
|
|
|
"Specify the `recursive: true` option to mitigate this error",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
|
|
|
}
|
|
|
|
return Err(err.WithMessage("Could not remove the file at %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
|
|
|
}
|
|
|
|
return Ok();
|
2020-07-18 03:31:57 +03:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:13:35 +03:00
|
|
|
/* static */
|
2020-07-30 17:02:50 +03:00
|
|
|
Result<Ok, IOUtils::IOError> IOUtils::CreateDirectorySync(
|
|
|
|
const nsAString& aPath, bool aCreateAncestors, bool aIgnoreExisting,
|
|
|
|
int32_t aMode) {
|
2020-07-30 17:02:38 +03:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
|
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 17:02:50 +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 17:02:50 +03:00
|
|
|
return Err(IOError(NS_ERROR_FILE_NOT_FOUND)
|
|
|
|
.WithMessage("Could not create directory at %s because "
|
|
|
|
"the path has missing "
|
|
|
|
"ancestor components",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult rv = targetFile->Create(nsIFile::DIRECTORY_TYPE, aMode);
|
2020-07-30 17:02:50 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
|
|
|
|
// NB: We may report a success only if the target is an existing
|
|
|
|
// directory. We don't want to silence errors that occur if the target is
|
|
|
|
// an existing file, since trying to create a directory where a regular
|
|
|
|
// file exists may be indicative of a logic error.
|
|
|
|
bool isDirectory;
|
|
|
|
MOZ_TRY(targetFile->IsDirectory(&isDirectory));
|
|
|
|
if (!isDirectory) {
|
|
|
|
return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY)
|
|
|
|
.WithMessage("Could not create directory because the "
|
|
|
|
"target file(%s) exists "
|
|
|
|
"and is not a directory",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
|
|
|
}
|
|
|
|
// The directory exists.
|
|
|
|
// The caller may suppress this error.
|
|
|
|
if (aIgnoreExisting) {
|
|
|
|
return Ok();
|
|
|
|
}
|
|
|
|
// Otherwise, forward it.
|
|
|
|
return Err(err.WithMessage(
|
|
|
|
"Could not create directory because it already exists at %s\n"
|
|
|
|
"Specify the `ignoreExisting: true` option to mitigate this "
|
|
|
|
"error",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
return Err(err.WithMessage("Could not create directory at %s",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
2020-07-30 17:02:50 +03:00
|
|
|
return Ok();
|
2020-07-21 18:13:35 +03:00
|
|
|
}
|
|
|
|
|
2020-07-30 17:02:50 +03:00
|
|
|
Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync(
|
2020-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 16:11:00 +03:00
|
|
|
bool isRegular;
|
2020-07-30 17:02:50 +03:00
|
|
|
// IsFile will stat and cache info in the file object. If the file doesn't
|
|
|
|
// exist, or there is an access error, we'll discover it here.
|
|
|
|
// Any subsequent errors are unexpected and will just be forwarded.
|
|
|
|
nsresult rv = file->IsFile(&isRegular);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
IOError err(rv);
|
|
|
|
if (IsFileNotFound(rv)) {
|
|
|
|
return Err(
|
|
|
|
err.WithMessage("Could not stat file(%s) because it does not exist",
|
|
|
|
NS_ConvertUTF16toUTF8(aPath).get()));
|
|
|
|
}
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can populate the info object by querying the file.
|
|
|
|
info.mType = FileType::Regular;
|
2020-07-23 21:15:30 +03:00
|
|
|
if (!isRegular) {
|
|
|
|
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
|