blob: 2c7aa7f717c1dedacad6f606625d1a5ab0ae5609 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/file_system/file_system_operation_impl.h"
#include <stdint.h>
#include <limits>
#include <memory>
#include <tuple>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/copy_or_move_hook_delegate.h"
#include "storage/browser/file_system/copy_or_move_operation_delegate.h"
#include "storage/browser/file_system/file_observers.h"
#include "storage/browser/file_system/file_system_backend.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_file_util.h"
#include "storage/browser/file_system/remove_operation_delegate.h"
#include "storage/browser/file_system/sandbox_file_system_backend_delegate.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/common/file_system/file_system_types.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace storage {
namespace {
// Takes ownership and destruct on the target thread.
void Destruct(base::File file) {}
void DidOpenFile(scoped_refptr<FileSystemContext> context,
base::WeakPtr<FileSystemOperationImpl> operation,
FileSystemOperationImpl::OpenFileCallback callback,
base::File file,
base::OnceClosure on_close_callback) {
if (!operation) {
context->default_file_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Destruct, std::move(file)));
return;
}
std::move(callback).Run(std::move(file), std::move(on_close_callback));
}
} // namespace
std::unique_ptr<FileSystemOperation> FileSystemOperation::Create(
OperationType type,
const FileSystemURL& url,
FileSystemContext* file_system_context,
std::unique_ptr<FileSystemOperationContext> operation_context) {
return std::make_unique<FileSystemOperationImpl>(
type, url, file_system_context, std::move(operation_context),
base::PassKey<FileSystemOperation>());
}
FileSystemOperationImpl::~FileSystemOperationImpl() = default;
void FileSystemOperationImpl::CreateFile(const FileSystemURL& url,
bool exclusive,
StatusCallback callback) {
CheckOperationType(OperationType::kCreateFile);
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
url,
base::BindOnce(&FileSystemOperationImpl::DoCreateFile,
weak_factory_.GetWeakPtr(), url,
std::move(split_callback.first), exclusive),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
void FileSystemOperationImpl::CreateDirectory(const FileSystemURL& url,
bool exclusive,
bool recursive,
StatusCallback callback) {
CheckOperationType(OperationType::kCreateDirectory);
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
url,
base::BindOnce(&FileSystemOperationImpl::DoCreateDirectory,
weak_factory_.GetWeakPtr(), url,
std::move(split_callback.first), exclusive, recursive),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
void FileSystemOperationImpl::Copy(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
ErrorBehavior error_behavior,
std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate,
StatusCallback callback) {
DCHECK(copy_or_move_hook_delegate);
CheckOperationType(OperationType::kCopy);
DCHECK(!recursive_operation_delegate_);
recursive_operation_delegate_ = std::make_unique<CopyOrMoveOperationDelegate>(
file_system_context(), src_url, dest_url,
CopyOrMoveOperationDelegate::OPERATION_COPY, options, error_behavior,
std::move(copy_or_move_hook_delegate),
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
recursive_operation_delegate_->RunRecursively();
}
void FileSystemOperationImpl::Move(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
ErrorBehavior error_behavior,
std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate,
StatusCallback callback) {
DCHECK(copy_or_move_hook_delegate);
CheckOperationType(OperationType::kMove);
DCHECK(!recursive_operation_delegate_);
recursive_operation_delegate_ = std::make_unique<CopyOrMoveOperationDelegate>(
file_system_context(), src_url, dest_url,
CopyOrMoveOperationDelegate::OPERATION_MOVE, options, error_behavior,
std::move(copy_or_move_hook_delegate),
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
recursive_operation_delegate_->RunRecursively();
}
void FileSystemOperationImpl::DirectoryExists(const FileSystemURL& url,
StatusCallback callback) {
CheckOperationType(OperationType::kDirectoryExists);
async_file_util_->GetFileInfo(
std::move(operation_context_), url, {GetMetadataField::kIsDirectory},
base::BindOnce(&FileSystemOperationImpl::DidDirectoryExists,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FileSystemOperationImpl::FileExists(const FileSystemURL& url,
StatusCallback callback) {
CheckOperationType(OperationType::kFileExists);
async_file_util_->GetFileInfo(
std::move(operation_context_), url, {GetMetadataField::kIsDirectory},
base::BindOnce(&FileSystemOperationImpl::DidFileExists,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FileSystemOperationImpl::GetMetadata(const FileSystemURL& url,
GetMetadataFieldSet fields,
GetMetadataCallback callback) {
CheckOperationType(OperationType::kGetMetadata);
async_file_util_->GetFileInfo(std::move(operation_context_), url, fields,
std::move(callback));
}
void FileSystemOperationImpl::ReadDirectory(
const FileSystemURL& url,
const ReadDirectoryCallback& callback) {
CheckOperationType(OperationType::kReadDirectory);
async_file_util_->ReadDirectory(std::move(operation_context_), url, callback);
}
void FileSystemOperationImpl::Remove(const FileSystemURL& url,
bool recursive,
StatusCallback callback) {
CheckOperationType(OperationType::kRemove);
DCHECK(!recursive_operation_delegate_);
if (recursive) {
// For recursive removal, try to delegate the operation to AsyncFileUtil
// first. If not supported, it is delegated to RemoveOperationDelegate
// in DidDeleteRecursively.
async_file_util_->DeleteRecursively(
std::move(operation_context_), url,
base::BindOnce(&FileSystemOperationImpl::DidDeleteRecursively,
weak_factory_.GetWeakPtr(), url, std::move(callback)));
return;
}
recursive_operation_delegate_ = std::make_unique<RemoveOperationDelegate>(
file_system_context(), url,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
recursive_operation_delegate_->Run();
}
void FileSystemOperationImpl::WriteBlob(
const FileSystemURL& url,
std::unique_ptr<FileWriterDelegate> writer_delegate,
std::unique_ptr<BlobReader> blob_reader,
const WriteCallback& callback) {
CheckOperationType(OperationType::kWrite);
file_writer_delegate_ = std::move(writer_delegate);
file_writer_delegate_->Start(
std::move(blob_reader),
base::BindRepeating(&FileSystemOperationImpl::DidWrite,
weak_factory_.GetWeakPtr(), url, callback));
}
void FileSystemOperationImpl::Write(
const FileSystemURL& url,
std::unique_ptr<FileWriterDelegate> writer_delegate,
mojo::ScopedDataPipeConsumerHandle data_pipe,
const WriteCallback& callback) {
CheckOperationType(OperationType::kWrite);
file_writer_delegate_ = std::move(writer_delegate);
file_writer_delegate_->Start(
std::move(data_pipe),
base::BindRepeating(&FileSystemOperationImpl::DidWrite,
weak_factory_.GetWeakPtr(), url, callback));
}
void FileSystemOperationImpl::Truncate(const FileSystemURL& url,
int64_t length,
StatusCallback callback) {
CheckOperationType(OperationType::kTruncate);
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
url,
base::BindOnce(&FileSystemOperationImpl::DoTruncate,
weak_factory_.GetWeakPtr(), url,
std::move(split_callback.first), length),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
void FileSystemOperationImpl::TouchFile(const FileSystemURL& url,
const base::Time& last_access_time,
const base::Time& last_modified_time,
StatusCallback callback) {
CheckOperationType(OperationType::kTouchFile);
async_file_util_->Touch(
std::move(operation_context_), url, last_access_time, last_modified_time,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FileSystemOperationImpl::OpenFile(const FileSystemURL& url,
uint32_t file_flags,
OpenFileCallback callback) {
CheckOperationType(OperationType::kOpenFile);
if (file_flags &
(base::File::FLAG_WIN_TEMPORARY | base::File::FLAG_WIN_HIDDEN)) {
std::move(callback).Run(base::File(base::File::FILE_ERROR_FAILED),
base::OnceClosure());
return;
}
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
url,
base::BindOnce(&FileSystemOperationImpl::DoOpenFile,
weak_factory_.GetWeakPtr(), url,
std::move(split_callback.first), file_flags),
base::BindOnce(std::move(split_callback.second),
base::File(base::File::FILE_ERROR_FAILED),
base::OnceClosure()));
}
// We can only get here on a write or truncate that's not yet completed.
// We don't support cancelling any other operation at this time.
void FileSystemOperationImpl::Cancel(StatusCallback cancel_callback) {
DCHECK(cancel_callback_.is_null());
cancel_callback_ = std::move(cancel_callback);
if (file_writer_delegate_.get()) {
CHECK_EQ(OperationType::kWrite, type_);
// This will call DidWrite() with ABORT status code.
file_writer_delegate_->Cancel();
} else if (recursive_operation_delegate_) {
// This will call DidFinishOperation() with ABORT status code.
recursive_operation_delegate_->Cancel();
} else {
// For truncate we have no way to cancel the inflight operation (for now).
// Let it just run and dispatch cancel callback later.
CHECK_EQ(OperationType::kTruncate, type_);
}
}
void FileSystemOperationImpl::CreateSnapshotFile(
const FileSystemURL& url,
SnapshotFileCallback callback) {
CheckOperationType(OperationType::kCreateSnapshotFile);
async_file_util_->CreateSnapshotFile(std::move(operation_context_), url,
std::move(callback));
}
void FileSystemOperationImpl::CopyInForeignFile(
const base::FilePath& src_local_disk_file_path,
const FileSystemURL& dest_url,
StatusCallback callback) {
CheckOperationType(OperationType::kCopyInForeignFile);
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
dest_url,
base::BindOnce(&FileSystemOperationImpl::DoCopyInForeignFile,
weak_factory_.GetWeakPtr(), src_local_disk_file_path,
dest_url, std::move(split_callback.first)),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
void FileSystemOperationImpl::RemoveFile(const FileSystemURL& url,
StatusCallback callback) {
CheckOperationType(OperationType::kRemove);
async_file_util_->DeleteFile(
std::move(operation_context_), url,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FileSystemOperationImpl::RemoveDirectory(const FileSystemURL& url,
StatusCallback callback) {
CheckOperationType(OperationType::kRemove);
async_file_util_->DeleteDirectory(
std::move(operation_context_), url,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void FileSystemOperationImpl::CopyFileLocal(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
const CopyFileProgressCallback& progress_callback,
StatusCallback callback) {
CheckOperationType(OperationType::kCopy);
// Don't just DCHECK src_url.IsInSameFileSystem(dest_url). We don't care if
// the two URLs are mounted in two different isolated file systems. As long
// as their origin and type are the same, they are part of the same file
// system, and local operations are allowed. See https://crbug.com/1396116.
DCHECK(src_url.origin() == dest_url.origin() ||
(src_url.origin().opaque() && dest_url.origin().opaque()));
DCHECK_EQ(src_url.type(), dest_url.type());
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
dest_url,
base::BindOnce(&FileSystemOperationImpl::DoCopyFileLocal,
weak_factory_.GetWeakPtr(), src_url, dest_url, options,
progress_callback, std::move(split_callback.first)),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
void FileSystemOperationImpl::MoveFileLocal(const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
StatusCallback callback) {
CheckOperationType(OperationType::kMove);
// Don't just DCHECK src_url.IsInSameFileSystem(dest_url). We don't care if
// the two URLs are mounted in two different isolated file systems. As long
// as their origin and type are the same, they are part of the same file
// system, and local operations are allowed. See https://crbug.com/1396116.
DCHECK(src_url.origin() == dest_url.origin() ||
(src_url.origin().opaque() && dest_url.origin().opaque()));
DCHECK_EQ(src_url.type(), dest_url.type());
auto split_callback = base::SplitOnceCallback(std::move(callback));
GetBucketSpaceRemainingAndRunTask(
dest_url,
base::BindOnce(&FileSystemOperationImpl::DoMoveFileLocal,
weak_factory_.GetWeakPtr(), src_url, dest_url, options,
std::move(split_callback.first)),
base::BindOnce(std::move(split_callback.second),
base::File::FILE_ERROR_FAILED));
}
base::File::Error FileSystemOperationImpl::SyncGetPlatformPath(
const FileSystemURL& url,
base::FilePath* platform_path) {
CheckOperationType(OperationType::kGetLocalPath);
if (!file_system_context()->IsSandboxFileSystem(url.type()))
return base::File::FILE_ERROR_INVALID_OPERATION;
FileSystemFileUtil* file_util =
file_system_context()->sandbox_delegate()->sync_file_util();
file_util->GetLocalFilePath(operation_context_.get(), url, platform_path);
return base::File::FILE_OK;
}
FileSystemOperationImpl::FileSystemOperationImpl(
OperationType type,
const FileSystemURL& url,
FileSystemContext* file_system_context,
std::unique_ptr<FileSystemOperationContext> operation_context,
base::PassKey<FileSystemOperation>)
: type_(type),
file_system_context_(file_system_context),
operation_context_(std::move(operation_context)),
async_file_util_(nullptr) {
weak_ptr_ = weak_factory_.GetWeakPtr();
DCHECK(operation_context_.get());
async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type());
DCHECK(async_file_util_);
}
void FileSystemOperationImpl::GetBucketSpaceRemainingAndRunTask(
const FileSystemURL& url,
base::OnceClosure task,
base::OnceClosure error_callback) {
const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy =
file_system_context()->quota_manager_proxy();
if (!quota_manager_proxy ||
!file_system_context()->GetQuotaUtil(url.type())) {
// If we don't have the quota manager or the requested filesystem type
// does not support quota, we should be able to let it go.
operation_context_->set_allowed_bytes_growth(
std::numeric_limits<int64_t>::max());
std::move(task).Run();
return;
}
BucketLocator bucket =
url.bucket().value_or(BucketLocator::ForDefaultBucket(url.storage_key()));
DCHECK(quota_manager_proxy);
quota_manager_proxy->GetBucketSpaceRemaining(
bucket, base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&FileSystemOperationImpl::DidGetBucketSpaceRemaining,
weak_ptr_, std::move(task), std::move(error_callback)));
}
void FileSystemOperationImpl::DidGetBucketSpaceRemaining(
base::OnceClosure task,
base::OnceClosure error_callback,
QuotaErrorOr<int64_t> space_left) {
if (!space_left.has_value()) {
LOG(WARNING) << "Got unexpected quota error";
std::move(error_callback).Run();
return;
}
operation_context_->set_allowed_bytes_growth(space_left.value());
std::move(task).Run();
}
void FileSystemOperationImpl::DoCreateFile(const FileSystemURL& url,
StatusCallback callback,
bool exclusive) {
async_file_util_->EnsureFileExists(
std::move(operation_context_), url,
base::BindOnce(
exclusive ? &FileSystemOperationImpl::DidEnsureFileExistsExclusive
: &FileSystemOperationImpl::DidEnsureFileExistsNonExclusive,
weak_ptr_, std::move(callback)));
}
void FileSystemOperationImpl::DoCreateDirectory(const FileSystemURL& url,
StatusCallback callback,
bool exclusive,
bool recursive) {
async_file_util_->CreateDirectory(
std::move(operation_context_), url, exclusive, recursive,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DoCopyFileLocal(
const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
const CopyFileProgressCallback& progress_callback,
StatusCallback callback) {
async_file_util_->CopyFileLocal(
std::move(operation_context_), src_url, dest_url, options,
progress_callback,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DoMoveFileLocal(const FileSystemURL& src_url,
const FileSystemURL& dest_url,
CopyOrMoveOptionSet options,
StatusCallback callback) {
async_file_util_->MoveFileLocal(
std::move(operation_context_), src_url, dest_url, options,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DoCopyInForeignFile(
const base::FilePath& src_local_disk_file_path,
const FileSystemURL& dest_url,
StatusCallback callback) {
async_file_util_->CopyInForeignFile(
std::move(operation_context_), src_local_disk_file_path, dest_url,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DoTruncate(const FileSystemURL& url,
StatusCallback callback,
int64_t length) {
async_file_util_->Truncate(
std::move(operation_context_), url, length,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DoOpenFile(const FileSystemURL& url,
OpenFileCallback callback,
uint32_t file_flags) {
async_file_util_->CreateOrOpen(
std::move(operation_context_), url, file_flags,
base::BindOnce(&DidOpenFile, file_system_context_, weak_ptr_,
std::move(callback)));
}
void FileSystemOperationImpl::DidEnsureFileExistsExclusive(
StatusCallback callback,
base::File::Error rv,
bool created) {
if (rv == base::File::FILE_OK && !created) {
std::move(callback).Run(base::File::FILE_ERROR_EXISTS);
} else {
DidFinishOperation(std::move(callback), rv);
}
}
void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive(
StatusCallback callback,
base::File::Error rv,
bool /* created */) {
DidFinishOperation(std::move(callback), rv);
}
void FileSystemOperationImpl::DidFinishOperation(StatusCallback callback,
base::File::Error rv) {
if (!cancel_callback_.is_null()) {
StatusCallback cancel_callback = std::move(cancel_callback_);
std::move(callback).Run(rv);
// Return OK only if we succeeded to stop the operation.
std::move(cancel_callback)
.Run(rv == base::File::FILE_ERROR_ABORT
? base::File::FILE_OK
: base::File::FILE_ERROR_INVALID_OPERATION);
} else {
std::move(callback).Run(rv);
}
}
void FileSystemOperationImpl::DidDirectoryExists(
StatusCallback callback,
base::File::Error rv,
const base::File::Info& file_info) {
if (rv == base::File::FILE_OK && !file_info.is_directory)
rv = base::File::FILE_ERROR_NOT_A_DIRECTORY;
std::move(callback).Run(rv);
}
void FileSystemOperationImpl::DidFileExists(StatusCallback callback,
base::File::Error rv,
const base::File::Info& file_info) {
if (rv == base::File::FILE_OK && file_info.is_directory)
rv = base::File::FILE_ERROR_NOT_A_FILE;
std::move(callback).Run(rv);
}
void FileSystemOperationImpl::DidDeleteRecursively(const FileSystemURL& url,
StatusCallback callback,
base::File::Error rv) {
if (rv == base::File::FILE_ERROR_INVALID_OPERATION) {
// Recursive removal is not supported on this platform.
DCHECK(!recursive_operation_delegate_);
recursive_operation_delegate_ = std::make_unique<RemoveOperationDelegate>(
file_system_context(), url,
base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_ptr_,
std::move(callback)));
recursive_operation_delegate_->RunRecursively();
return;
}
std::move(callback).Run(rv);
}
void FileSystemOperationImpl::DidWrite(
const FileSystemURL& url,
const WriteCallback& write_callback,
base::File::Error rv,
int64_t bytes,
FileWriterDelegate::WriteProgressStatus write_status) {
const bool complete =
(write_status != FileWriterDelegate::SUCCESS_IO_PENDING);
if (complete && write_status != FileWriterDelegate::ERROR_WRITE_NOT_STARTED) {
DCHECK(operation_context_);
operation_context_->change_observers()->Notify(
&FileChangeObserver::OnModifyFile, url);
}
StatusCallback cancel_callback = std::move(cancel_callback_);
write_callback.Run(rv, bytes, complete);
if (!cancel_callback.is_null())
std::move(cancel_callback).Run(base::File::FILE_OK);
}
void FileSystemOperationImpl::CheckOperationType(OperationType type) {
CHECK_EQ(type, type_);
CHECK(!operation_called_);
operation_called_ = true;
}
} // namespace storage