// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/fileapi/diversion_backend_delegate.h"
#include <unistd.h>
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ash/fileapi/copy_from_fd.h"
#include "chrome/browser/ash/fusebox/fusebox_errno.h"
#include "chrome/browser/ash/fusebox/fusebox_histograms.h"
#include "content/public/browser/browser_thread.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_util.h"
namespace ash {
namespace {
constexpr auto kDiversionFileIdleTimeout = base::Seconds(5);
DiversionBackendDelegate::Policy ShouldDivert(
const storage::FileSystemURL& url) {
// For now (January 2024), we'll be conservative and only apply diversion to
// "crdownload" files (from browser downloads) or "crswap" files (as
// generated by File System Access code) that are freshly created. Later, we
// could roll out diversion more widely.
//
// If we were to divert previously-existing files (not only diverting freshly
// created files), as kDivertMingled (not kDivertIsolated), then we'd need to
// initialize the Diversion File's contents (via a FileStreamWriter), just
// after the StartDiverting call, before running the
// EnsureFileExistsCallback. There would be the usual trade-offs here between
// lazy and eager initialization of those file contents.
const std::string& extension = url.virtual_path().FinalExtension();
if ((extension == ".crdownload") || (extension == ".crswap")) {
return DiversionBackendDelegate::Policy::kDivertIsolated;
} else if (extension == ".cros_divert_mingled_test") {
return DiversionBackendDelegate::Policy::kDivertMingled;
}
return DiversionBackendDelegate::Policy::kDoNotDivert;
}
// Duplicates a FileSystemOperationContext, returning something (a unique_ptr)
// with independent ownership.
std::unique_ptr<storage::FileSystemOperationContext>
DuplicateFileSystemOperationContext(
const storage::FileSystemOperationContext& original) {
return std::make_unique<storage::FileSystemOperationContext>(
original.file_system_context(), original.task_runner());
}
storage::AsyncFileUtil::StatusCallback HistogramWrap(
const char* histogram_name,
storage::AsyncFileUtil::StatusCallback callback) {
static constexpr auto func =
[](const char* histogram_name,
storage::AsyncFileUtil::StatusCallback wrappee,
base::File::Error error) {
base::UmaHistogramEnumeration(histogram_name,
fusebox::GetHistogramEnumPosixErrorCode(
fusebox::FileErrorToErrno(error)));
if (wrappee) {
std::move(wrappee).Run(error);
}
};
return base::BindOnce(func, histogram_name, std::move(callback));
}
} // namespace
DiversionBackendDelegate::DiversionBackendDelegate(
std::unique_ptr<FileSystemBackendDelegate> wrappee)
: wrappee_(std::move(wrappee)),
diversion_file_manager_(base::MakeRefCounted<DiversionFileManager>()) {
CHECK(wrappee_);
}
DiversionBackendDelegate::~DiversionBackendDelegate() = default;
storage::AsyncFileUtil* DiversionBackendDelegate::GetAsyncFileUtil(
storage::FileSystemType type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
return this;
}
std::unique_ptr<storage::FileStreamReader>
DiversionBackendDelegate::CreateFileStreamReader(
const storage::FileSystemURL& url,
int64_t offset,
int64_t max_bytes_to_read,
const base::Time& expected_modification_time,
storage::FileSystemContext* context) {
// TODO: honor max_bytes_to_read. On the other hand,
// storage::FileStreamReader::CreateForLocalFile also doesn't honor it.
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (std::unique_ptr<storage::FileStreamReader> fs_reader =
diversion_file_manager_->CreateDivertedFileStreamReader(url,
offset)) {
return fs_reader;
}
return wrappee_->CreateFileStreamReader(url, offset, max_bytes_to_read,
expected_modification_time, context);
}
std::unique_ptr<storage::FileStreamWriter>
DiversionBackendDelegate::CreateFileStreamWriter(
const storage::FileSystemURL& url,
int64_t offset,
storage::FileSystemContext* context) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (std::unique_ptr<storage::FileStreamWriter> fs_writer =
diversion_file_manager_->CreateDivertedFileStreamWriter(url,
offset)) {
return fs_writer;
}
return wrappee_->CreateFileStreamWriter(url, offset, context);
}
storage::WatcherManager* DiversionBackendDelegate::GetWatcherManager(
storage::FileSystemType type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
return wrappee_->GetWatcherManager(type);
}
void DiversionBackendDelegate::CreateOrOpen(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
uint32_t file_flags,
CreateOrOpenCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->CreateOrOpen(std::move(context), url, file_flags,
std::move(callback));
}
void DiversionBackendDelegate::EnsureFileExists(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
EnsureFileExistsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
if (!url.is_valid()) {
std::move(callback).Run(base::File::FILE_ERROR_INVALID_URL,
/*created=*/false);
return;
}
const Policy policy = ShouldDivert(url);
base::UmaHistogramBoolean(
"FileBrowser.Diversion.EnsureFileExists.ShouldDivert",
policy != Policy::kDoNotDivert);
if (policy == Policy::kDoNotDivert) {
af_util->EnsureFileExists(std::move(context), url, std::move(callback));
return;
} else if (diversion_file_manager_->IsDiverting(url)) {
std::move(callback).Run(base::File::FILE_OK, /*created=*/false);
return;
} else if (policy == Policy::kDivertIsolated) {
auto sd_result = diversion_file_manager_->StartDiverting(
url, kDiversionFileIdleTimeout,
base::BindOnce(&DiversionBackendDelegate::OnDiversionFinished,
weak_ptr_factory_.GetWeakPtr(),
OnDiversionFinishedCallSite::kEnsureFileExists,
std::move(context), url,
storage::AsyncFileUtil::StatusCallback()));
bool created = sd_result == DiversionFileManager::StartDivertingResult::kOK;
std::move(callback).Run(base::File::FILE_OK, created);
return;
}
static constexpr auto on_get_file_info =
[](base::WeakPtr<DiversionBackendDelegate> weak_ptr,
scoped_refptr<DiversionFileManager> diversion_file_manager,
std::unique_ptr<storage::FileSystemOperationContext>
duplicated_context,
const storage::FileSystemURL& url, EnsureFileExistsCallback callback,
base::File::Error gfi_result, const base::File::Info& file_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
bool created = false;
if (gfi_result == base::File::FILE_OK) {
std::move(callback).Run(file_info.is_directory
? base::File::FILE_ERROR_NOT_A_FILE
: base::File::FILE_OK,
created);
} else if (gfi_result == base::File::FILE_ERROR_NOT_FOUND) {
auto sd_result = diversion_file_manager->StartDiverting(
url, kDiversionFileIdleTimeout,
base::BindOnce(&DiversionBackendDelegate::OnDiversionFinished,
std::move(weak_ptr),
OnDiversionFinishedCallSite::kEnsureFileExists,
std::move(duplicated_context), url,
storage::AsyncFileUtil::StatusCallback()));
created =
sd_result == DiversionFileManager::StartDivertingResult::kOK;
std::move(callback).Run(base::File::FILE_OK, created);
} else {
std::move(callback).Run(gfi_result, created);
}
};
std::unique_ptr<storage::FileSystemOperationContext> fsoc0 =
DuplicateFileSystemOperationContext(*context);
std::unique_ptr<storage::FileSystemOperationContext> fsoc1 =
std::move(context);
af_util->GetFileInfo(
std::move(fsoc0), url,
{storage::FileSystemOperation::GetMetadataField::kIsDirectory},
base::BindOnce(on_get_file_info, weak_ptr_factory_.GetWeakPtr(),
diversion_file_manager_, std::move(fsoc1), url,
std::move(callback)));
}
void DiversionBackendDelegate::CreateDirectory(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
bool exclusive,
bool recursive,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->CreateDirectory(std::move(context), url, exclusive, recursive,
std::move(callback));
}
void DiversionBackendDelegate::GetFileInfo(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
GetMetadataFieldSet fields,
GetFileInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (diversion_file_manager_->IsDiverting(url)) {
diversion_file_manager_->GetDivertedFileInfo(url, fields,
std::move(callback));
return;
} else if (ShouldDivert(url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND,
base::File::Info());
return;
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->GetFileInfo(std::move(context), url, fields, std::move(callback));
}
void DiversionBackendDelegate::ReadDirectory(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
ReadDirectoryCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->ReadDirectory(std::move(context), url, std::move(callback));
}
void DiversionBackendDelegate::Touch(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
const base::Time& last_access_time,
const base::Time& last_modified_time,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (diversion_file_manager_->IsDiverting(url)) {
// TODO: touch the O_TMPFILE file.
} else if (ShouldDivert(url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND);
return;
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->Touch(std::move(context), url, last_access_time, last_modified_time,
std::move(callback));
}
void DiversionBackendDelegate::Truncate(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
int64_t length,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (diversion_file_manager_->IsDiverting(url)) {
diversion_file_manager_->TruncateDivertedFile(url, length,
std::move(callback));
return;
} else if (ShouldDivert(url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND);
return;
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->Truncate(std::move(context), url, length, std::move(callback));
}
void DiversionBackendDelegate::CopyFileLocal(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
CopyFileProgressCallback progress_callback,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!src_url.is_valid() || !dest_url.is_valid()) {
std::move(callback).Run(base::File::FILE_ERROR_INVALID_URL);
return;
} else if (src_url == dest_url) {
std::move(callback).Run(base::File::FILE_OK);
return;
}
if (diversion_file_manager_->IsDiverting(dest_url)) {
// Passing a null DiversionFileManager::Callback deletes the diversion file.
diversion_file_manager_->FinishDiverting(dest_url,
DiversionFileManager::Callback());
}
if (diversion_file_manager_->IsDiverting(src_url)) {
diversion_file_manager_->FinishDiverting(
src_url,
base::BindOnce(&DiversionBackendDelegate::OnDiversionFinished,
weak_ptr_factory_.GetWeakPtr(),
OnDiversionFinishedCallSite::kCopyFileLocal,
std::move(context), dest_url, std::move(callback)));
return;
} else if (ShouldDivert(src_url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND);
return;
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(dest_url.type());
af_util->CopyFileLocal(std::move(context), src_url, dest_url,
std::move(options), std::move(progress_callback),
std::move(callback));
}
void DiversionBackendDelegate::MoveFileLocal(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!src_url.is_valid() || !dest_url.is_valid()) {
std::move(callback).Run(base::File::FILE_ERROR_INVALID_URL);
return;
} else if (src_url == dest_url) {
std::move(callback).Run(base::File::FILE_OK);
return;
}
if (diversion_file_manager_->IsDiverting(src_url)) {
diversion_file_manager_->FinishDiverting(
src_url,
base::BindOnce(&DiversionBackendDelegate::OnDiversionFinished,
weak_ptr_factory_.GetWeakPtr(),
OnDiversionFinishedCallSite::kMoveFileLocal,
std::move(context), dest_url, std::move(callback)));
return;
} else if (ShouldDivert(src_url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND);
return;
} else if (diversion_file_manager_->IsDiverting(dest_url)) {
// Passing a null DiversionFileManager::Callback deletes the diversion file.
diversion_file_manager_->FinishDiverting(dest_url,
DiversionFileManager::Callback());
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(dest_url.type());
af_util->MoveFileLocal(std::move(context), src_url, dest_url,
std::move(options), std::move(callback));
}
void DiversionBackendDelegate::CopyInForeignFile(
std::unique_ptr<storage::FileSystemOperationContext> context,
const base::FilePath& src_file_path,
const storage::FileSystemURL& dest_url,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(dest_url.type());
af_util->CopyInForeignFile(std::move(context), src_file_path, dest_url,
std::move(callback));
}
void DiversionBackendDelegate::DeleteFile(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (diversion_file_manager_->IsDiverting(url)) {
// Passing a null DiversionFileManager::Callback deletes the diversion file.
diversion_file_manager_->FinishDiverting(url,
DiversionFileManager::Callback());
if (callback) {
callback = base::BindOnce(
[](StatusCallback inner_callback, base::File::Error result) {
if (result == base::File::FILE_ERROR_NOT_FOUND) {
result = base::File::FILE_OK;
}
std::move(inner_callback).Run(result);
},
std::move(callback));
}
} else if (ShouldDivert(url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND);
return;
}
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->DeleteFile(std::move(context), url, std::move(callback));
}
void DiversionBackendDelegate::DeleteDirectory(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->DeleteDirectory(std::move(context), url, std::move(callback));
}
void DiversionBackendDelegate::DeleteRecursively(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// TODO: this could be tricky if a diverted-file is a descendent of the "url
// to be deleted recursively".
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->DeleteRecursively(std::move(context), url, std::move(callback));
}
void DiversionBackendDelegate::CreateSnapshotFile(
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& url,
CreateSnapshotFileCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// TODO: do we need to do anything here??
storage::AsyncFileUtil* af_util = wrappee_->GetAsyncFileUtil(url.type());
af_util->CreateSnapshotFile(std::move(context), url, std::move(callback));
}
void DiversionBackendDelegate::OverrideTmpfileDirForTesting(
const base::FilePath& tmpfile_dir) {
diversion_file_manager_->OverrideTmpfileDirForTesting( // IN-TEST
tmpfile_dir);
}
// static
DiversionBackendDelegate::Policy
DiversionBackendDelegate::ShouldDivertForTesting(
const storage::FileSystemURL& url) {
return ShouldDivert(url);
}
// static
base::TimeDelta DiversionBackendDelegate::IdleTimeoutForTesting() {
return kDiversionFileIdleTimeout;
}
// static
void DiversionBackendDelegate::OnDiversionFinished(
base::WeakPtr<DiversionBackendDelegate> weak_ptr,
OnDiversionFinishedCallSite call_site,
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& dest_url,
storage::AsyncFileUtil::StatusCallback callback,
DiversionFileManager::StoppedReason stopped_reason,
const storage::FileSystemURL& src_url,
base::ScopedFD scoped_fd,
int64_t file_size,
base::File::Error error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
CHECK(dest_url.is_valid());
CHECK(src_url.is_valid());
switch (stopped_reason) {
case DiversionFileManager::StoppedReason::kExplicitFinish:
base::UmaHistogramEnumeration(
"FileBrowser.Diversion.Commit.ExplicitFinish.PartOne",
fusebox::GetHistogramEnumPosixErrorCode(
fusebox::FileErrorToErrno(error)));
callback =
HistogramWrap("FileBrowser.Diversion.Commit.ExplicitFinish.PartTwo",
std::move(callback));
break;
case DiversionFileManager::StoppedReason::kImplicitIdle:
base::UmaHistogramEnumeration(
"FileBrowser.Diversion.Commit.ImplicitIdle.PartOne",
fusebox::GetHistogramEnumPosixErrorCode(
fusebox::FileErrorToErrno(error)));
callback =
HistogramWrap("FileBrowser.Diversion.Commit.ImplicitIdle.PartTwo",
std::move(callback));
break;
}
if (error != base::File::FILE_OK) {
if (callback) {
std::move(callback).Run(error);
}
return;
} else if (!weak_ptr || !context || !scoped_fd.is_valid()) {
if (callback) {
std::move(callback).Run(base::File::FILE_ERROR_FAILED);
}
return;
}
// TODO(b/289322939): when wrapping a FileSystemProvider backend, can it
// accept a Blob instead of using CopyFromFileDescriptor? The latter might
// need the FileSystemProvider JS implementation to buffer the entire file's
// contents in memory at once, which will obviously fail if those contents
// are larger than the amount of available RAM.
static constexpr auto ignore_file_error_not_found =
[](storage::AsyncFileUtil::StatusCallback callback,
base::File::Error file_error) {
if (file_error == base::File::FILE_ERROR_NOT_FOUND) {
file_error = base::File::FILE_OK;
}
if (callback) {
std::move(callback).Run(file_error);
}
};
static constexpr auto on_copy_complete =
[](base::WeakPtr<DiversionBackendDelegate> weak_ptr,
OnDiversionFinishedCallSite call_site,
std::unique_ptr<storage::FileSystemOperationContext> context,
const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
storage::AsyncFileUtil::StatusCallback callback,
DiversionFileManager::StoppedReason stopped_reason, int64_t file_size,
base::ScopedFD scoped_fd,
std::unique_ptr<storage::FileStreamWriter> fs_writer,
net::Error net_error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (src_url == dest_url) {
if (callback) {
std::move(callback).Run(storage::NetErrorToFileError(net_error));
}
return;
}
CHECK_NE(call_site, OnDiversionFinishedCallSite::kEnsureFileExists);
if (callback && (net_error != net::OK)) {
base::File::Error first_error =
storage::NetErrorToFileError(net_error);
callback = base::BindOnce(
[](storage::AsyncFileUtil::StatusCallback inner_callback,
base::File::Error first_error_is_passed_on,
base::File::Error second_error_is_ignored) {
std::move(inner_callback).Run(first_error_is_passed_on);
},
std::move(callback), first_error);
}
if (!weak_ptr) {
if (callback) {
std::move(callback).Run(base::File::FILE_ERROR_FAILED);
}
return;
}
switch (call_site) {
case OnDiversionFinishedCallSite::kEnsureFileExists:
NOTREACHED();
case OnDiversionFinishedCallSite::kCopyFileLocal: {
if (!scoped_fd.is_valid()) {
std::move(callback).Run(base::File::FILE_ERROR_FAILED);
return;
}
// Copy from that scoped_fd again, but this time to src_url instead
// of to dest_url.
lseek(scoped_fd.get(), 0, SEEK_SET);
weak_ptr->OnDiversionFinished(
weak_ptr, call_site, std::move(context), src_url,
std::move(callback), stopped_reason, src_url,
std::move(scoped_fd), file_size, base::File::FILE_OK);
break;
}
case OnDiversionFinishedCallSite::kMoveFileLocal:
if (ShouldDivert(src_url) == Policy::kDivertIsolated) {
std::move(callback).Run(base::File::FILE_OK);
} else {
weak_ptr->wrappee_->GetAsyncFileUtil(src_url.type())
->DeleteFile(std::move(context), src_url,
base::BindOnce(ignore_file_error_not_found,
std::move(callback)));
}
break;
}
};
static constexpr auto on_truncated =
[](base::WeakPtr<DiversionBackendDelegate> weak_ptr,
OnDiversionFinishedCallSite call_site,
std::unique_ptr<storage::FileSystemOperationContext> context,
base::ScopedFD scoped_fd, const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
storage::AsyncFileUtil::StatusCallback callback,
DiversionFileManager::StoppedReason stopped_reason, int64_t file_size,
base::File::Error result) {
if (result != base::File::FILE_OK) {
if (callback) {
std::move(callback).Run(result);
}
return;
} else if (!weak_ptr) {
if (callback) {
std::move(callback).Run(base::File::FILE_ERROR_FAILED);
}
return;
}
// TODO: for now, we are assuming that "write to foo.dat" is atomic:
// other clients reading "foo.dat" will either see the complete old
// version (if it existed) or the complete new version but not a
// partial prefix of the new version. Specifically for ODFS+FSP, file
// upload is indeed atomic.
//
// More generally, we may have to take multiple steps (the Google Drive
// team call this a "File Dance"), first writing to a temporary sibling
// file and then moving the sibling over the ultimate destination. But
// this is obviously more complicated and has more failure states.
//
// Ideally, the wrappee FileSystemBackendDelegate or AsyncFileUtil
// would be able to say whether it is capable of atomic upload (see
// also b/289322939). Absent that, it's simplest to assume that it can.
std::unique_ptr<storage::FileStreamWriter> fs_writer =
weak_ptr->wrappee_->CreateFileStreamWriter(
dest_url, 0, context->file_system_context());
CopyFromFileDescriptor(
std::move(scoped_fd), std::move(fs_writer),
dest_url.mount_option().flush_policy(),
base::BindOnce(on_copy_complete, std::move(weak_ptr), call_site,
std::move(context), src_url, dest_url,
std::move(callback), stopped_reason, file_size));
};
static constexpr auto on_ensure_file_exists =
[](base::WeakPtr<DiversionBackendDelegate> weak_ptr,
OnDiversionFinishedCallSite call_site,
std::unique_ptr<storage::FileSystemOperationContext> context,
base::ScopedFD scoped_fd, const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
storage::AsyncFileUtil::StatusCallback callback,
DiversionFileManager::StoppedReason stopped_reason, int64_t file_size,
base::File::Error result, bool created) {
if (result != base::File::FILE_OK) {
if (callback) {
std::move(callback).Run(result);
}
return;
} else if (!weak_ptr) {
if (callback) {
std::move(callback).Run(base::File::FILE_ERROR_FAILED);
}
return;
} else if (created) {
on_truncated(std::move(weak_ptr), call_site, std::move(context),
std::move(scoped_fd), src_url, dest_url,
std::move(callback), stopped_reason, file_size,
base::File::FILE_OK);
return;
}
std::unique_ptr<storage::FileSystemOperationContext> fsoc0 =
DuplicateFileSystemOperationContext(*context);
std::unique_ptr<storage::FileSystemOperationContext> fsoc1 =
std::move(context);
FileSystemBackendDelegate* wrappee = weak_ptr->wrappee_.get();
wrappee->GetAsyncFileUtil(dest_url.type())
->Truncate(
std::move(fsoc0), dest_url, 0,
base::BindOnce(on_truncated, std::move(weak_ptr), call_site,
std::move(fsoc1), std::move(scoped_fd), src_url,
dest_url, std::move(callback), stopped_reason,
file_size));
};
static constexpr auto on_get_file_info =
[](base::WeakPtr<DiversionBackendDelegate> weak_ptr,
OnDiversionFinishedCallSite call_site,
std::unique_ptr<storage::FileSystemOperationContext> context,
base::ScopedFD scoped_fd, const storage::FileSystemURL& src_url,
const storage::FileSystemURL& dest_url,
storage::AsyncFileUtil::StatusCallback callback,
DiversionFileManager::StoppedReason stopped_reason, int64_t file_size,
base::File::Error result, const base::File::Info& file_info) {
if (result == base::File::FILE_ERROR_NOT_FOUND) {
// No-op. Every other if-else branch returns.
} else if (result != base::File::FILE_OK) {
if (callback) {
std::move(callback).Run(result);
}
return;
} else if (file_info.is_directory) {
if (callback) {
std::move(callback).Run(base::File::FILE_ERROR_NOT_A_FILE);
}
return;
} else if (file_info.size != 0) {
on_ensure_file_exists(std::move(weak_ptr), call_site,
std::move(context), std::move(scoped_fd),
src_url, dest_url, std::move(callback),
stopped_reason, file_size, base::File::FILE_OK,
/*created=*/false);
return;
} else {
on_truncated(std::move(weak_ptr), call_site, std::move(context),
std::move(scoped_fd), src_url, dest_url,
std::move(callback), stopped_reason, file_size,
base::File::FILE_OK);
return;
}
std::unique_ptr<storage::FileSystemOperationContext> fsoc0 =
DuplicateFileSystemOperationContext(*context);
std::unique_ptr<storage::FileSystemOperationContext> fsoc1 =
std::move(context);
FileSystemBackendDelegate* wrappee = weak_ptr->wrappee_.get();
wrappee->GetAsyncFileUtil(dest_url.type())
->EnsureFileExists(
std::move(fsoc0), dest_url,
base::BindOnce(on_ensure_file_exists, std::move(weak_ptr),
call_site, std::move(fsoc1),
std::move(scoped_fd), src_url, dest_url,
std::move(callback), stopped_reason, file_size));
};
std::unique_ptr<storage::FileSystemOperationContext> fsoc0 =
DuplicateFileSystemOperationContext(*context);
std::unique_ptr<storage::FileSystemOperationContext> fsoc1 =
std::move(context);
FileSystemBackendDelegate* wrappee = weak_ptr->wrappee_.get();
wrappee->GetAsyncFileUtil(dest_url.type())
->GetFileInfo(
std::move(fsoc0), dest_url,
{storage::FileSystemOperation::GetMetadataField::kSize,
storage::FileSystemOperation::GetMetadataField::kIsDirectory},
base::BindOnce(on_get_file_info, std::move(weak_ptr), call_site,
std::move(fsoc1), std::move(scoped_fd), src_url,
dest_url, std::move(callback), stopped_reason,
file_size));
}
} // namespace ash