/* -*- 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/. */
#ifdefined(XP_UNIX) && !defined(ANDROID) # include "nsSystemInfo.h" #endif
#ifdefined(XP_WIN) # include "nsILocalFileWin.h" #elifdefined(XP_MACOSX) # include "nsILocalFileMac.h" #endif
#ifdef XP_UNIX # include "base/process_util.h" #endif
#define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise, _msg, ...) \ do { \ if (nsresult _rv = PathUtils::InitFileWithPath((_file), (_path)); \
NS_FAILED(_rv)) { \
(_promise)->MaybeRejectWithOperationError(FormatErrorMessage( \
_rv, _msg ": could not parse path", ##__VA_ARGS__)); \ return; \
} \
} while (0)
#define IOUTILS_TRY_WITH_CONTEXT(_expr, _fmt, ...) \ do { \ if (nsresult _rv = (_expr); NS_FAILED(_rv)) { \ return Err(IOUtils::IOError(_rv, _fmt, ##__VA_ARGS__)); \
} \
} while (0)
static constexpr auto SHUTDOWN_ERROR = "IOUtils: Shutting down and refusing additional I/O tasks"_ns;
namespace mozilla::dom {
// static helper functions
/** * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may * report I/O errors inconsistently. For convenience, this function will attempt * to match a |nsresult| against known results which imply a file cannot be * found. * * @see nsLocalFileWin.cpp * @see nsLocalFileUnix.cpp
*/ staticbool IsFileNotFound(nsresult aResult) { return aResult == NS_ERROR_FILE_NOT_FOUND;
} /** * Like |IsFileNotFound|, but checks for known results that suggest a file * is not a directory.
*/ staticbool IsNotDirectory(nsresult aResult) { return aResult == NS_ERROR_FILE_DESTINATION_NOT_DIR ||
aResult == NS_ERROR_FILE_NOT_DIRECTORY;
}
/** * Formats an error message and appends the error name to the end.
*/ static nsCString MOZ_FORMAT_PRINTF(2, 3)
FormatErrorMessage(nsresult aError, constchar* const aFmt, ...) {
nsAutoCString errorName;
GetErrorName(aError, errorName);
switch (aError.Code()) { case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK:
[[fallthrough]]; case NS_ERROR_FILE_NOT_FOUND:
[[fallthrough]]; case NS_ERROR_FILE_INVALID_PATH:
[[fallthrough]]; case NS_ERROR_NOT_AVAILABLE:
aPromise->MaybeRejectWithNotFoundError(errMsg); break;
case NS_ERROR_FILE_IS_LOCKED:
[[fallthrough]]; case NS_ERROR_FILE_ACCESS_DENIED:
aPromise->MaybeRejectWithNotAllowedError(errMsg); break;
case NS_ERROR_FILE_TOO_BIG:
[[fallthrough]]; case NS_ERROR_FILE_NO_DEVICE_SPACE:
[[fallthrough]]; case NS_ERROR_FILE_DEVICE_FAILURE:
[[fallthrough]]; case NS_ERROR_FILE_FS_CORRUPTED:
[[fallthrough]]; case NS_ERROR_FILE_CORRUPTED:
aPromise->MaybeRejectWithNotReadableError(errMsg); break;
case NS_ERROR_FILE_ALREADY_EXISTS:
aPromise->MaybeRejectWithNoModificationAllowedError(errMsg); break;
case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
[[fallthrough]]; case NS_ERROR_FILE_NAME_TOO_LONG:
[[fallthrough]]; case NS_ERROR_FILE_UNRECOGNIZED_PATH:
[[fallthrough]]; case NS_ERROR_FILE_DIR_NOT_EMPTY:
aPromise->MaybeRejectWithOperationError(errMsg); break;
case NS_ERROR_FILE_READ_ONLY:
aPromise->MaybeRejectWithReadOnlyError(errMsg); break;
case NS_ERROR_FILE_NOT_DIRECTORY:
[[fallthrough]]; case NS_ERROR_FILE_DESTINATION_NOT_DIR:
[[fallthrough]]; case NS_ERROR_FILE_IS_DIRECTORY:
[[fallthrough]]; case NS_ERROR_FILE_UNKNOWN_TYPE:
aPromise->MaybeRejectWithInvalidAccessError(errMsg); break;
case NS_ERROR_ILLEGAL_INPUT:
[[fallthrough]]; case NS_ERROR_ILLEGAL_VALUE:
aPromise->MaybeRejectWithDataError(errMsg); break;
case NS_ERROR_ABORT:
aPromise->MaybeRejectWithAbortError(errMsg); break;
reason.AppendPrintf(" Called from %s:%d:%d.", scriptFilename.get(), lineNo,
colNo.oneOriginValue()); returnfalse;
}
staticvoid AssertParentProcessWithCallerLocation(GlobalObject& aGlobal) {
nsCString reason = "IOUtils can only be used in the parent process."_ns; if (!AssertParentProcessWithCallerLocationImpl(aGlobal, reason)) {
MOZ_CRASH_UNSAFE_PRINTF("%s", reason.get());
}
}
if (auto state = GetState()) {
aFn(promise, state.ref());
} else {
RejectShuttingDown(promise);
} return promise.forget();
}
/* static */ template <typename OkT, typename Fn> void IOUtils::DispatchAndResolve(IOUtils::EventQueue* aQueue, Promise* aPromise,
Fn aFunc) {
RefPtr<StrongWorkerRef> workerRef; if (!NS_IsMainThread()) { // We need to manually keep the worker alive until the promise returned by // Dispatch() resolves or rejects.
workerRef = StrongWorkerRef::CreateForcibly(GetCurrentThreadWorkerPrivate(),
__func__);
}
Maybe<uint32_t> toRead = Nothing(); if (!aOptions.mMaxBytes.IsNull()) { if (aOptions.mDecompress) {
RejectJSPromise(
promise, IOError(NS_ERROR_ILLEGAL_INPUT, "Could not read `%s': the `maxBytes' and " "`decompress' options are mutually exclusive",
file->HumanReadablePath().get())); return;
}
if (aOptions.mMaxBytes.Value() == 0) { // Resolve with an empty buffer.
nsTArray<uint8_t> arr(0);
promise->MaybeResolve(TypedArrayCreator<Uint8Array>(arr)); return;
}
toRead.emplace(aOptions.mMaxBytes.Value());
}
// This API is only exposed to workers, so we should not be on the main // thread here.
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
nsCOMPtr<nsIFile> file = new nsLocalFile(); if (nsresult rv = PathUtils::InitFileWithPath(file, aPath); NS_FAILED(rv)) {
aRv.ThrowOperationError(FormatErrorMessage(
rv, "Could not parse path (%s)", NS_ConvertUTF16toUTF8(aPath).get())); return nullptr;
}
RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream(); if (nsresult rv =
stream->Init(file, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
NS_FAILED(rv)) {
aRv.ThrowOperationError(
FormatErrorMessage(rv, "Could not open the file at %s",
NS_ConvertUTF16toUTF8(aPath).get())); return nullptr;
}
int64_t size = 0; if (nsresult rv = stream->GetSize(&size); NS_FAILED(rv)) {
aRv.ThrowOperationError(FormatErrorMessage(
rv, "Could not get the stream size for the file at %s",
NS_ConvertUTF16toUTF8(aPath).get())); return nullptr;
}
RefPtr<StrongWorkerRef> workerRef; if (!NS_IsMainThread()) { // We need to manually keep the worker alive until the promise // returned by Dispatch() resolves or rejects.
workerRef = StrongWorkerRef::CreateForcibly(
GetCurrentThreadWorkerPrivate(), __func__);
}
Maybe<Buffer<uint8_t>> buf = aData.CreateFromData<Buffer<uint8_t>>(); if (buf.isNothing()) {
promise->MaybeRejectWithOperationError(nsPrintfCString( "Could not write to `%s': could not allocate buffer",
file->HumanReadablePath().get())); return;
}
auto result = InternalWriteOpts::FromBinding(aOptions); if (result.isErr()) {
RejectJSPromise(
promise,
IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
file->HumanReadablePath().get())); return;
}
auto result = InternalWriteOpts::FromBinding(aOptions); if (result.isErr()) {
RejectJSPromise(
promise,
IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
file->HumanReadablePath().get())); return;
}
auto result = InternalWriteOpts::FromBinding(aOptions); if (result.isErr()) {
RejectJSPromise(
promise,
IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
file->HumanReadablePath().get())); return;
}
auto opts = result.unwrap();
if (opts.mMode == WriteMode::Append ||
opts.mMode == WriteMode::AppendOrCreate) {
promise->MaybeRejectWithNotSupportedError(
nsPrintfCString("Could not write to `%s': IOUtils.writeJSON does " "not support appending to files.",
file->HumanReadablePath().get())); return;
}
JSContext* cx = aGlobal.Context();
JS::Rooted<JS::Value> rootedValue(cx, aValue);
nsString string; if (!nsContentUtils::StringifyJSON(cx, aValue, string,
UndefinedIsNullStringLiteral)) {
JS::Rooted<JS::Value> exn(cx, JS::UndefinedValue()); if (JS_GetPendingException(cx, &exn)) {
JS_ClearPendingException(cx);
promise->MaybeReject(exn);
} else {
RejectJSPromise(promise,
IOError(NS_ERROR_DOM_UNKNOWN_ERR, "Could not serialize object to JSON"_ns));
} return;
}
DispatchAndResolve<uint32_t>(
state->mEventQueue, promise,
[file = std::move(file), string = std::move(string),
opts = std::move(opts)]() -> Result<uint32_t, IOError> {
nsAutoCString utf8Str; if (!CopyUTF16toUTF8(string, utf8Str, fallible)) { return Err(IOError(
NS_ERROR_OUT_OF_MEMORY, "Failed to write to `%s': could not allocate buffer",
file->HumanReadablePath().get()));
} return WriteSync(file, AsBytes(Span(utf8Str)), opts);
});
});
}
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not set permissions on `%s'",
NS_ConvertUTF16toUTF8(aPath).get());
/* static */
already_AddRefed<Promise> IOUtils::GetWindowsAttributes(GlobalObject& aGlobal, const nsAString& aPath,
ErrorResult& aError) { return WithPromiseAndState(
aGlobal, aError, [&](Promise* promise, auto& state) {
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise, "Could not get Windows file attributes of " "`%s'",
NS_ConvertUTF16toUTF8(aPath).get());
RefPtr<StrongWorkerRef> workerRef; if (!NS_IsMainThread()) { // We need to manually keep the worker alive until the promise // returned by Dispatch() resolves or rejects.
workerRef = StrongWorkerRef::CreateForcibly(
GetCurrentThreadWorkerPrivate(), __func__);
}
/* static */
already_AddRefed<Promise> IOUtils::SetMacXAttr(GlobalObject& aGlobal, const nsAString& aPath, const nsACString& aAttr, const Uint8Array& aValue,
ErrorResult& aError) { return WithPromiseAndState(
aGlobal, aError, [&](Promise* promise, auto& state) {
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(
file, aPath, promise, "Could not set the extended attribute `%s' on `%s'",
PromiseFlatCString(aAttr).get(),
NS_ConvertUTF16toUTF8(aPath).get());
nsTArray<uint8_t> value;
if (!aValue.AppendDataTo(value)) {
RejectJSPromise(
promise, IOError(NS_ERROR_OUT_OF_MEMORY, "Could not set extended attribute `%s' on `%s': " "could not allocate buffer",
PromiseFlatCString(aAttr).get(),
file->HumanReadablePath().get())); return;
}
nsCOMPtr<nsIFile> parent; if (nsresult rv = file->GetParent(getter_AddRefs(parent));
NS_FAILED(rv)) {
RejectJSPromise(promise, IOError(rv, "Could not get nsIFile for `%s': " "could not get parent directory",
file->HumanReadablePath().get())); return;
}
RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream(); if (nsresult rv =
stream->Init(aFile, PR_RDONLY | nsIFile::OS_READAHEAD, 0666, 0);
NS_FAILED(rv)) { if (IsFileNotFound(rv)) { return Err(IOError(rv, "Could not open `%s': file does not exist",
aFile->HumanReadablePath().get()));
} return Err(
IOError(rv, "Could not open `%s'", aFile->HumanReadablePath().get()));
}
uint32_t bufSize = 0;
if (aMaxBytes.isNothing()) { // Limitation: We cannot read more than the maximum size of a TypedArray // (UINT32_MAX bytes). Reject if we have been requested to // perform too large of a read.
int64_t rawStreamSize = -1; if (nsresult rv = stream->GetSize(&rawStreamSize); NS_FAILED(rv)) { return Err(
IOError(NS_ERROR_FILE_ACCESS_DENIED, "Could not open `%s': could not stat file or directory",
aFile->HumanReadablePath().get()));
}
MOZ_RELEASE_ASSERT(rawStreamSize >= 0);
uint64_t streamSize = static_cast<uint64_t>(rawStreamSize); if (aOffset >= streamSize) {
bufSize = 0;
} else { if (streamSize - offset > static_cast<int64_t>(UINT32_MAX)) { return Err(IOError(NS_ERROR_FILE_TOO_BIG, "Could not read `%s' with offset %" PRIu64 ": file is too large (%" PRIu64 " bytes)",
aFile->HumanReadablePath().get(), offset,
streamSize));
}
if (offset > 0) { if (nsresult rv = stream->Seek(PR_SEEK_SET, offset); NS_FAILED(rv)) { return Err(IOError(
rv, "Could not read `%s': could not seek to position %" PRId64,
aFile->HumanReadablePath().get(), offset));
}
}
if (bufSize > 0) { auto result = JsBuffer::Create(aBufferKind, bufSize); if (result.isErr()) { return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'",
aFile->HumanReadablePath().get()));
}
buffer = result.unwrap();
Span<char> toRead = buffer.BeginWriting();
// Read the file from disk.
uint32_t totalRead = 0; while (totalRead != bufSize) { // Read no more than INT32_MAX on each call to stream->Read, otherwise it // returns an error.
uint32_t bytesToReadThisChunk =
std::min<uint32_t>(bufSize - totalRead, INT32_MAX);
uint32_t bytesRead = 0; if (nsresult rv =
stream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead);
NS_FAILED(rv)) { return Err(
IOError(rv, "Could not read `%s': encountered an unexpected error",
aFile->HumanReadablePath().get()));
} if (bytesRead == 0) { break;
}
totalRead += bytesRead;
toRead = toRead.From(bytesRead);
}
buffer.SetLength(totalRead);
}
// Decompress the file contents, if required. if (aDecompress) { auto result =
MozLZ4::Decompress(AsBytes(buffer.BeginReading()), aBufferKind); if (result.isErr()) { return Err(IOError::WithCause(result.unwrapErr(), "Could not read `%s'",
aFile->HumanReadablePath().get()));
} return result;
}
return std::move(buffer);
}
/* static */
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::ReadUTF8Sync(
nsIFile* aFile, bool aDecompress) { auto result = ReadSync(aFile, 0, Nothing{}, aDecompress, BufferKind::String); if (result.isErr()) { return result.propagateErr();
}
JsBuffer buffer = result.unwrap(); if (!IsUtf8(buffer.BeginReading())) { return Err(IOError(NS_ERROR_FILE_CORRUPTED, "Could not read `%s': file is not UTF-8 encoded",
aFile->HumanReadablePath().get()));
}
bool exists = false;
IOUTILS_TRY_WITH_CONTEXT(
aFile->Exists(&exists), "Could not write to `%s': could not stat file or directory",
aFile->HumanReadablePath().get());
if (exists && aOptions.mMode == WriteMode::Create) { return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS, "Could not write to `%s': refusing to overwrite file, " "`mode' is not \"overwrite\"",
aFile->HumanReadablePath().get()));
}
// If backupFile was specified, perform the backup as a move. if (exists && backupFile) { // We copy `destFile` here to a new `nsIFile` because // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we // did not do this, we would end up having `destFile` point to the same // location as `backupFile`. Then, when we went to write to `destFile`, we // would end up overwriting `backupFile` and never actually write to the // file we were supposed to.
nsCOMPtr<nsIFile> toMove;
MOZ_ALWAYS_SUCCEEDS(aFile->Clone(getter_AddRefs(toMove)));
if (auto result = MoveSync(toMove, backupFile, noOverwrite);
result.isErr()) { return Err(IOError::WithCause(
result.unwrapErr(), "Could not write to `%s': failed to back up source file",
aFile->HumanReadablePath().get()));
}
}
// If tempFile was specified, we will write to there first, then perform a // move to ensure the file ends up at the final requested destination.
nsIFile* writeFile;
// Try to perform the write and ensure that the file is closed before // continuing.
uint32_t totalWritten = 0;
{ // Compress the byte array if required.
nsTArray<uint8_t> compressed;
Span<constchar> bytes; if (aOptions.mCompress) { auto result = MozLZ4::Compress(aByteArray); if (result.isErr()) { return Err(IOError::WithCause(result.unwrapErr(), "Could not write to `%s'",
writeFile->HumanReadablePath().get()));
}
compressed = result.unwrap();
bytes = Span(reinterpret_cast<constchar*>(compressed.Elements()),
compressed.Length());
} else {
bytes = Span(reinterpret_cast<constchar*>(aByteArray.Elements()),
aByteArray.Length());
}
RefPtr<nsFileOutputStream> stream = new nsFileOutputStream(); if (nsresult rv = stream->Init(writeFile, flags, 0666, 0); NS_FAILED(rv)) { // Normalize platform-specific errors for opening a directory to an access // denied error. if (rv == nsresult::NS_ERROR_FILE_IS_DIRECTORY) {
rv = NS_ERROR_FILE_ACCESS_DENIED;
} return Err(IOError(
rv, "Could not write to `%s': failed to open file for writing",
writeFile->HumanReadablePath().get()));
}
// nsFileRandomAccessStream::Write uses PR_Write under the hood, which // accepts a *int32_t* for the chunk size.
uint32_t chunkSize = INT32_MAX;
Span<constchar> pendingBytes = bytes;
while (pendingBytes.Length() > 0) { if (pendingBytes.Length() < chunkSize) {
chunkSize = pendingBytes.Length();
}
uint32_t bytesWritten = 0; if (nsresult rv =
stream->Write(pendingBytes.Elements(), chunkSize, &bytesWritten);
NS_FAILED(rv)) { return Err(IOError(rv, "Could not write to `%s': failed to write chunk; " "the file may be corrupt",
writeFile->HumanReadablePath().get()));
}
pendingBytes = pendingBytes.From(bytesWritten);
totalWritten += bytesWritten;
}
}
// If tempFile was passed, check destFile against writeFile and, if they // differ, the operation is finished by performing a move. if (tempFile) {
nsAutoStringN<256> destPath;
nsAutoStringN<256> writePath;
// nsIFile::MoveToFollowingLinks will only update the path of the file if // the move succeeds. if (destPath != writePath) { if (aOptions.mTmpFile) { bool isDir = false; if (nsresult rv = aFile->IsDirectory(&isDir);
NS_FAILED(rv) && !IsFileNotFound(rv)) { return Err(IOError(
rv, "Could not write to `%s': could not stat file or directory",
aFile->HumanReadablePath().get()));
}
// If we attempt to write to a directory *without* a temp file, we get a // permission error. // // However, if we are writing to a temp file first, when we copy the // temp file over the destination file, we actually end up copying it // inside the directory, which is not what we want. In this case, we are // just going to bail out early. if (isDir) { return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED, "Could not write to `%s': file is a directory",
aFile->HumanReadablePath().get()));
}
}
if (auto result = MoveSync(writeFile, aFile, /* aNoOverwrite = */ false);
result.isErr()) { return Err(IOError::WithCause(
result.unwrapErr(), "Could not write to `%s': could not move overwite with temporary " "file",
aFile->HumanReadablePath().get()));
}
}
} return totalWritten;
}
// Ensure the source file exists before continuing. If it doesn't exist, // subsequent operations can fail in different ways on different platforms. bool srcExists = false;
IOUTILS_TRY_WITH_CONTEXT(
aSourceFile->Exists(&srcExists), "Could not move `%s' to `%s': could not stat source file or directory",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get());
if (!srcExists) { return Err(
IOError(NS_ERROR_FILE_NOT_FOUND, "Could not move `%s' to `%s': source file does not exist",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get()));
}
// Ensure the source file exists before continuing. If it doesn't exist, // subsequent operations can fail in different ways on different platforms. bool srcExists;
IOUTILS_TRY_WITH_CONTEXT(
aSourceFile->Exists(&srcExists), "Could not copy `%s' to `%s': could not stat source file or directory",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get());
if (!srcExists) { return Err(
IOError(NS_ERROR_FILE_NOT_FOUND, "Could not copy `%s' to `%s': source file does not exist",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get()));
}
// If source is a directory, fail immediately unless the recursive option is // true. bool srcIsDir = false;
IOUTILS_TRY_WITH_CONTEXT(
aSourceFile->IsDirectory(&srcIsDir), "Could not copy `%s' to `%s': could not stat source file or directory",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get());
if (srcIsDir && !aRecursive) { return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED, "Refused to copy directory `%s' to `%s': `recursive' is " "false\n",
aSourceFile->HumanReadablePath().get(),
aDestFile->HumanReadablePath().get()));
}
// Case 1: Destination is an existing directory. Copy/move source into dest. bool destIsDir = false; bool destExists = true;
nsresult rv = aDest->IsDirectory(&destIsDir); if (NS_SUCCEEDED(rv) && destIsDir) {
rv = (aSource->*aMethod)(aDest, u""_ns); if (NS_FAILED(rv)) { return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName,
aSource->HumanReadablePath().get(),
aDest->HumanReadablePath().get()));
} return Ok();
}
if (NS_FAILED(rv)) { if (!IsFileNotFound(rv)) { // It's ok if the dest file doesn't exist. Case 2 handles this below. // Bail out early for any other kind of error though. return Err(IOError(rv, "Could not %s `%s' to `%s'", aMethodName,
aSource->HumanReadablePath().get(),
aDest->HumanReadablePath().get()));
}
destExists = false;
}
// Case 2: Destination is a file which may or may not exist. // Try to copy or rename the source to the destination. // If the destination exists and the source is not a regular file, // then this may fail. if (aNoOverwrite && destExists) { return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS, "Could not %s `%s' to `%s': destination file exists and " "`noOverwrite' is true",
aMethodName, aSource->HumanReadablePath().get(),
aDest->HumanReadablePath().get()));
} if (destExists && !destIsDir) { // If the source file is a directory, but the target is a file, abort early. // Different implementations of |CopyTo| and |MoveTo| seem to handle this // error case differently (or not at all), so we explicitly handle it here. bool srcIsDir = false;
IOUTILS_TRY_WITH_CONTEXT(
aSource->IsDirectory(&srcIsDir), "Could not %s `%s' to `%s': could not stat source file or directory",
aMethodName, aSource->HumanReadablePath().get(),
aDest->HumanReadablePath().get()); if (srcIsDir) { return Err(IOError(
NS_ERROR_FILE_DESTINATION_NOT_DIR, "Could not %s directory `%s' to `%s': destination is not a directory",
aMethodName, aSource->HumanReadablePath().get(),
aDest->HumanReadablePath().get()));
}
}
// We would have already thrown if the path was zero-length.
nsAutoString destName;
MOZ_ALWAYS_SUCCEEDS(aDest->GetLeafName(destName));
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.34 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.