123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- // Copyright 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "remoting/host/file_transfer/local_file_operations.h"
- #include <cstdint>
- #include "base/bind.h"
- #include "base/files/file_proxy.h"
- #include "base/files/file_util.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/path_service.h"
- #include "base/sequence_checker.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/task/task_runner_util.h"
- #include "base/task/thread_pool.h"
- #include "base/threading/sequenced_task_runner_handle.h"
- #include "build/build_config.h"
- #include "remoting/base/result.h"
- #include "remoting/host/file_transfer/ensure_user.h"
- #include "remoting/host/file_transfer/file_chooser.h"
- #include "remoting/host/file_transfer/get_desktop_directory.h"
- #include "remoting/protocol/file_transfer_helpers.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace remoting {
- namespace {
- constexpr char kTempFileExtension[] = ".part";
- remoting::protocol::FileTransfer_Error_Type FileErrorToResponseErrorType(
- base::File::Error file_error) {
- switch (file_error) {
- case base::File::FILE_ERROR_ACCESS_DENIED:
- return remoting::protocol::FileTransfer_Error_Type_PERMISSION_DENIED;
- case base::File::FILE_ERROR_NO_SPACE:
- return remoting::protocol::FileTransfer_Error_Type_OUT_OF_DISK_SPACE;
- default:
- return remoting::protocol::FileTransfer_Error_Type_IO_ERROR;
- }
- }
- scoped_refptr<base::SequencedTaskRunner> CreateFileTaskRunner() {
- #if BUILDFLAG(IS_WIN)
- // On Windows, we use user impersonation to write files as the currently
- // logged-in user, while the process as a whole runs as SYSTEM. Since user
- // impersonation is per-thread on Windows, we need a dedicated thread to
- // ensure that no other code is accidentally run with the wrong privileges.
- return base::ThreadPool::CreateSingleThreadTaskRunner(
- {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
- base::SingleThreadTaskRunnerThreadMode::DEDICATED);
- #else
- return base::ThreadPool::CreateSequencedTaskRunner(
- {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
- #endif
- }
- class LocalFileReader : public FileOperations::Reader {
- public:
- explicit LocalFileReader(
- scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
- LocalFileReader(const LocalFileReader&) = delete;
- LocalFileReader& operator=(const LocalFileReader&) = delete;
- ~LocalFileReader() override;
- // FileOperations::Reader implementation.
- void Open(OpenCallback callback) override;
- void ReadChunk(std::size_t size, ReadCallback callback) override;
- const base::FilePath& filename() const override;
- std::uint64_t size() const override;
- FileOperations::State state() const override;
- private:
- void OnEnsureUserResult(OpenCallback callback,
- protocol::FileTransferResult<absl::monostate> result);
- void OnFileChooserResult(OpenCallback callback, FileChooser::Result result);
- void OnOpenResult(OpenCallback callback, base::File::Error error);
- void OnGetInfoResult(OpenCallback callback,
- base::File::Error error,
- const base::File::Info& info);
- void OnReadResult(ReadCallback callback,
- base::File::Error error,
- const char* data,
- int bytes_read);
- void SetState(FileOperations::State state);
- base::FilePath filename_;
- std::uint64_t size_ = 0;
- std::uint64_t offset_ = 0;
- FileOperations::State state_ = FileOperations::kCreated;
- scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
- scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
- std::unique_ptr<FileChooser> file_chooser_;
- absl::optional<base::FileProxy> file_proxy_;
- SEQUENCE_CHECKER(sequence_checker_);
- base::WeakPtrFactory<LocalFileReader> weak_ptr_factory_{this};
- };
- class LocalFileWriter : public FileOperations::Writer {
- public:
- LocalFileWriter();
- LocalFileWriter(const LocalFileWriter&) = delete;
- LocalFileWriter& operator=(const LocalFileWriter&) = delete;
- ~LocalFileWriter() override;
- // FileOperations::Writer implementation.
- void Open(const base::FilePath& filename, Callback callback) override;
- void WriteChunk(std::vector<std::uint8_t> data, Callback callback) override;
- void Close(Callback callback) override;
- FileOperations::State state() const override;
- private:
- void Cancel();
- // Callbacks for Open().
- void OnGetTargetDirectoryResult(
- base::FilePath filename,
- Callback callback,
- protocol::FileTransferResult<base::FilePath> target_directory_result);
- void CreateTempFile(Callback callback, base::FilePath temp_filepath);
- void OnCreateResult(Callback callback, base::File::Error error);
- void OnWriteResult(std::vector<std::uint8_t> data,
- Callback callback,
- base::File::Error error,
- int bytes_written);
- // Callbacks for Close().
- void OnCloseResult(Callback callback, base::File::Error error);
- void MoveToDestination(Callback callback,
- base::FilePath destination_filepath);
- void OnMoveResult(Callback callback, bool success);
- void SetState(FileOperations::State state);
- FileOperations::State state_ = FileOperations::kCreated;
- base::FilePath destination_filepath_;
- base::FilePath temp_filepath_;
- std::uint64_t bytes_written_ = 0;
- scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
- absl::optional<base::FileProxy> file_proxy_;
- SEQUENCE_CHECKER(sequence_checker_);
- base::WeakPtrFactory<LocalFileWriter> weak_ptr_factory_{this};
- };
- LocalFileReader::LocalFileReader(
- scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
- : ui_task_runner_(std::move(ui_task_runner)) {}
- LocalFileReader::~LocalFileReader() = default;
- void LocalFileReader::Open(OpenCallback callback) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(FileOperations::kCreated, state_);
- SetState(FileOperations::kBusy);
- file_task_runner_ = CreateFileTaskRunner();
- file_proxy_.emplace(file_task_runner_.get());
- base::PostTaskAndReplyWithResult(
- file_task_runner_.get(), FROM_HERE, base::BindOnce(&EnsureUserContext),
- base::BindOnce(&LocalFileReader::OnEnsureUserResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileReader::ReadChunk(std::size_t size, ReadCallback callback) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(FileOperations::kReady, state_);
- SetState(FileOperations::kBusy);
- file_proxy_->Read(
- offset_, size,
- base::BindOnce(&LocalFileReader::OnReadResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- const base::FilePath& LocalFileReader::filename() const {
- return filename_;
- }
- uint64_t LocalFileReader::size() const {
- return size_;
- }
- FileOperations::State LocalFileReader::state() const {
- return state_;
- }
- void LocalFileReader::OnEnsureUserResult(
- FileOperations::Reader::OpenCallback callback,
- protocol::FileTransferResult<absl::monostate> result) {
- if (!result) {
- SetState(FileOperations::kFailed);
- std::move(callback).Run(std::move(result.error()));
- return;
- }
- file_chooser_ = FileChooser::Create(
- ui_task_runner_,
- base::BindOnce(&LocalFileReader::OnFileChooserResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- file_chooser_->Show();
- }
- void LocalFileReader::OnFileChooserResult(OpenCallback callback,
- FileChooser::Result result) {
- file_chooser_.reset();
- if (!result) {
- SetState(FileOperations::kFailed);
- std::move(callback).Run(std::move(result.error()));
- return;
- }
- filename_ = result->BaseName();
- file_proxy_->CreateOrOpen(
- *result, base::File::FLAG_OPEN | base::File::FLAG_READ,
- base::BindOnce(&LocalFileReader::OnOpenResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileReader::OnOpenResult(OpenCallback callback,
- base::File::Error error) {
- if (error != base::File::FILE_OK) {
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- file_proxy_->GetInfo(base::BindOnce(&LocalFileReader::OnGetInfoResult,
- weak_ptr_factory_.GetWeakPtr(),
- std::move(callback)));
- }
- void LocalFileReader::OnGetInfoResult(OpenCallback callback,
- base::File::Error error,
- const base::File::Info& info) {
- if (error != base::File::FILE_OK) {
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- size_ = info.size;
- SetState(FileOperations::kReady);
- std::move(callback).Run(kSuccessTag);
- }
- void LocalFileReader::OnReadResult(ReadCallback callback,
- base::File::Error error,
- const char* data,
- int bytes_read) {
- if (error != base::File::FILE_OK) {
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- offset_ += bytes_read;
- SetState(bytes_read > 0 ? FileOperations::kReady : FileOperations::kComplete);
- // The read buffer is provided and owned by FileProxy, so there's no way to
- // avoid a copy, here.
- std::move(callback).Run(std::vector<std::uint8_t>(data, data + bytes_read));
- }
- void LocalFileReader::SetState(FileOperations::State state) {
- switch (state) {
- case FileOperations::kCreated:
- NOTREACHED(); // Can never return to initial state.
- break;
- case FileOperations::kReady:
- DCHECK_EQ(FileOperations::kBusy, state_);
- break;
- case FileOperations::kBusy:
- DCHECK(state_ == FileOperations::kCreated ||
- state_ == FileOperations::kReady);
- break;
- case FileOperations::kComplete:
- DCHECK_EQ(FileOperations::kBusy, state_);
- break;
- case FileOperations::kFailed:
- // Any state can change to kFailed.
- break;
- }
- state_ = state;
- }
- LocalFileWriter::LocalFileWriter() {}
- LocalFileWriter::~LocalFileWriter() {
- Cancel();
- }
- void LocalFileWriter::Open(const base::FilePath& filename, Callback callback) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(FileOperations::kCreated, state_);
- SetState(FileOperations::kBusy);
- file_task_runner_ = CreateFileTaskRunner();
- file_proxy_.emplace(file_task_runner_.get());
- base::PostTaskAndReplyWithResult(
- file_task_runner_.get(), FROM_HERE, base::BindOnce([] {
- return EnsureUserContext().AndThen(
- [](absl::monostate) { return GetDesktopDirectory(); });
- }),
- base::BindOnce(&LocalFileWriter::OnGetTargetDirectoryResult,
- weak_ptr_factory_.GetWeakPtr(), filename,
- std::move(callback)));
- }
- void LocalFileWriter::WriteChunk(std::vector<std::uint8_t> data,
- Callback callback) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(FileOperations::kReady, state_);
- SetState(FileOperations::kBusy);
- // TODO(rkjnsn): Under what circumstances can posting the task fail? Is it
- // worth checking for? If so, what should we do in that case,
- // given that callback is moved into the task and not returned
- // on error?
- // Ensure buffer pointer is obtained before data is moved.
- const std::uint8_t* buffer = data.data();
- const std::size_t size = data.size();
- file_proxy_->Write(bytes_written_, reinterpret_cast<const char*>(buffer),
- size,
- base::BindOnce(&LocalFileWriter::OnWriteResult,
- weak_ptr_factory_.GetWeakPtr(),
- std::move(data), std::move(callback)));
- }
- void LocalFileWriter::Close(Callback callback) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(FileOperations::kReady, state_);
- SetState(FileOperations::kBusy);
- file_proxy_->Close(base::BindOnce(&LocalFileWriter::OnCloseResult,
- weak_ptr_factory_.GetWeakPtr(),
- std::move(callback)));
- }
- void LocalFileWriter::Cancel() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (state_ == FileOperations::kCreated ||
- state_ == FileOperations::kComplete ||
- state_ == FileOperations::kFailed) {
- return;
- }
- // Ensure we don't receive further callbacks.
- weak_ptr_factory_.InvalidateWeakPtrs();
- // Drop FileProxy, which will close the underlying file on the file
- // sequence after any possible pending operation is complete.
- file_proxy_.reset();
- // And finally, queue deletion of the temp file.
- if (!temp_filepath_.empty()) {
- file_task_runner_->PostTask(FROM_HERE,
- base::GetDeleteFileCallback(temp_filepath_));
- }
- SetState(FileOperations::kFailed);
- }
- FileOperations::State LocalFileWriter::state() const {
- return state_;
- }
- void LocalFileWriter::OnGetTargetDirectoryResult(
- base::FilePath filename,
- Callback callback,
- protocol::FileTransferResult<base::FilePath> target_directory) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (!target_directory) {
- LOG(ERROR) << "Failed to get target directory.";
- SetState(FileOperations::kFailed);
- std::move(callback).Run(std::move(target_directory.error()));
- return;
- }
- destination_filepath_ =
- target_directory.success().Append(filename.BaseName());
- // Don't store in temp_filepath_ until we have the final path to make sure
- // Cancel can never delete the wrong file.
- base::FilePath temp_filepath =
- destination_filepath_.AddExtensionASCII(kTempFileExtension);
- PostTaskAndReplyWithResult(
- file_task_runner_.get(), FROM_HERE,
- base::BindOnce(&base::GetUniquePath, temp_filepath),
- base::BindOnce(&LocalFileWriter::CreateTempFile,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileWriter::CreateTempFile(Callback callback,
- base::FilePath temp_filepath) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (temp_filepath.empty()) {
- LOG(ERROR) << "Failed to get unique path number.";
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
- return;
- }
- temp_filepath_ = std::move(temp_filepath);
- // FLAG_WIN_SHARE_DELETE allows the file to be marked as deleted on Windows
- // while the handle is still open. (Other OS's allow this by default.) This
- // allows Cancel to clean up the temporary file even if there are writes
- // pending.
- file_proxy_->CreateOrOpen(
- temp_filepath_,
- base::File::FLAG_CREATE | base::File::FLAG_WRITE |
- base::File::FLAG_WIN_SHARE_DELETE,
- base::BindOnce(&LocalFileWriter::OnCreateResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileWriter::OnCreateResult(Callback callback,
- base::File::Error error) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (error != base::File::FILE_OK) {
- LOG(ERROR) << "Creating temp file failed with error: " << error;
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- SetState(FileOperations::kReady);
- // Now that the temp file has been created successfully, we could lock it
- // using base::File::Lock(), but this would not prevent the file from being
- // deleted. When the file is deleted, WriteChunk() will continue to write to
- // the file as if the file was still there, and an error will occur when
- // calling base::Move() to move the temp file. Chrome exhibits the same
- // behavior with its downloads.
- std::move(callback).Run(kSuccessTag);
- }
- void LocalFileWriter::OnWriteResult(std::vector<std::uint8_t> data,
- Callback callback,
- base::File::Error error,
- int bytes_written) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (error != base::File::FILE_OK) {
- LOG(ERROR) << "Write failed with error: " << error;
- Cancel();
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- SetState(FileOperations::kReady);
- bytes_written_ += bytes_written;
- // bytes_written should never be negative if error is FILE_OK.
- if (static_cast<std::size_t>(bytes_written) != data.size()) {
- // Write already makes a "best effort" to write all of the data, so this
- // probably means that an error occurred. Unfortunately, the only way to
- // find out what went wrong is to try again.
- // TODO(rkjnsn): Would it be better just to return a generic error, here?
- WriteChunk(
- std::vector<std::uint8_t>(data.begin() + bytes_written, data.end()),
- std::move(callback));
- return;
- }
- std::move(callback).Run(kSuccessTag);
- }
- void LocalFileWriter::OnCloseResult(Callback callback,
- base::File::Error error) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (error != base::File::FILE_OK) {
- LOG(ERROR) << "Close failed with error: " << error;
- Cancel();
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, FileErrorToResponseErrorType(error), error));
- return;
- }
- base::PostTaskAndReplyWithResult(
- file_task_runner_.get(), FROM_HERE,
- base::BindOnce(&base::GetUniquePath, destination_filepath_),
- base::BindOnce(&LocalFileWriter::MoveToDestination,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileWriter::MoveToDestination(Callback callback,
- base::FilePath destination_filepath) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (destination_filepath.empty()) {
- LOG(ERROR) << "Failed to get unique path number.";
- SetState(FileOperations::kFailed);
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
- return;
- }
- destination_filepath_ = std::move(destination_filepath);
- PostTaskAndReplyWithResult(
- file_task_runner_.get(), FROM_HERE,
- base::BindOnce(&base::Move, temp_filepath_, destination_filepath_),
- base::BindOnce(&LocalFileWriter::OnMoveResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- void LocalFileWriter::OnMoveResult(Callback callback, bool success) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (success) {
- SetState(FileOperations::kComplete);
- std::move(callback).Run(kSuccessTag);
- } else {
- LOG(ERROR) << "Failed to move file to final destination.";
- Cancel();
- std::move(callback).Run(protocol::MakeFileTransferError(
- FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
- }
- }
- void LocalFileWriter::SetState(FileOperations::State state) {
- switch (state) {
- case FileOperations::kCreated:
- NOTREACHED(); // Can never return to initial state.
- break;
- case FileOperations::kReady:
- DCHECK(state_ == FileOperations::kBusy);
- break;
- case FileOperations::kBusy:
- DCHECK(state_ == FileOperations::kCreated ||
- state_ == FileOperations::kReady);
- break;
- case FileOperations::kComplete:
- DCHECK(state_ == FileOperations::kBusy);
- break;
- case FileOperations::kFailed:
- // Any state can change to kFailed.
- break;
- }
- state_ = state;
- }
- } // namespace
- LocalFileOperations::LocalFileOperations(
- scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
- : ui_task_runner_(std::move(ui_task_runner)) {}
- LocalFileOperations::~LocalFileOperations() = default;
- std::unique_ptr<FileOperations::Reader> LocalFileOperations::CreateReader() {
- return std::make_unique<LocalFileReader>(ui_task_runner_);
- }
- std::unique_ptr<FileOperations::Writer> LocalFileOperations::CreateWriter() {
- return std::make_unique<LocalFileWriter>();
- }
- } // namespace remoting
|