|  | // 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_NORETURN(); | 
|  |  | 
|  | 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 |