| // Copyright 2018 The Chromium OS 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 "arc/vm/mojo_proxy/local_file.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/task_runner_util.h> |
| |
| #include "arc/vm/mojo_proxy/file_descriptor_util.h" |
| |
| namespace arc { |
| |
| LocalFile::LocalFile(base::ScopedFD fd, |
| bool can_send_fds, |
| base::OnceClosure error_handler, |
| scoped_refptr<base::TaskRunner> blocking_task_runner) |
| : fd_(std::move(fd)), |
| can_send_fds_(can_send_fds), |
| error_handler_(std::move(error_handler)), |
| blocking_task_runner_(blocking_task_runner) {} |
| |
| LocalFile::~LocalFile() { |
| // Asynchronous tasks running on the blocking task runner may be using the FD. |
| // Post a task to destruct the FD on the task runner after all tasks finish. |
| if (blocking_task_runner_) { |
| blocking_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce([](base::ScopedFD fd) {}, base::Passed(std::move(fd_)))); |
| } |
| } |
| |
| LocalFile::ReadResult LocalFile::Read() { |
| // Get the amont of readable data (for pipes or stream sockets) or the size of |
| // the next datagram (for datagram sockets). |
| int buffer_size = 0; |
| if (HANDLE_EINTR(ioctl(fd_.get(), FIONREAD, &buffer_size)) < 0) { |
| int error_code = errno; |
| PLOG(ERROR) << "ioctl(FIONREAD) failed"; |
| return {error_code, std::string(), {}}; |
| } |
| |
| // Caller is responsible to call this function only when FD is readable. |
| // FD is readable && buffer_size==0 means it reached EOF. |
| if (buffer_size == 0) |
| return {0, std::string(), {}}; |
| |
| // Read data. |
| std::string buf(buffer_size, 0); |
| std::vector<base::ScopedFD> fds; |
| ssize_t size = can_send_fds_ |
| ? Recvmsg(fd_.get(), &buf[0], buf.size(), &fds) |
| : HANDLE_EINTR(read(fd_.get(), &buf[0], buf.size())); |
| if (size == -1) { |
| int error_code = errno; |
| PLOG(ERROR) << "Failed to read"; |
| return {error_code, std::string(), {}}; |
| } |
| buf.resize(size); |
| return {0 /* succeed */, std::move(buf), std::move(fds)}; |
| } |
| |
| bool LocalFile::Write(std::string blob, std::vector<base::ScopedFD> fds) { |
| pending_write_.emplace_back(Data{std::move(blob), std::move(fds)}); |
| if (!writable_watcher_) // TrySendMsg will be called later if watching. |
| TrySendMsg(); |
| return true; |
| } |
| |
| void LocalFile::Pread(uint64_t count, uint64_t offset, PreadCallback callback) { |
| base::PostTaskAndReplyWithResult( |
| blocking_task_runner_.get(), FROM_HERE, |
| base::BindOnce( |
| [](int fd, uint64_t count, uint64_t offset) { |
| arc_proxy::PreadResponse response; |
| std::string buffer; |
| buffer.resize(count); |
| int result = HANDLE_EINTR(pread(fd, &buffer[0], count, offset)); |
| if (result < 0) { |
| response.set_error_code(errno); |
| } else { |
| buffer.resize(result); |
| response.set_error_code(0); |
| response.set_blob(std::move(buffer)); |
| } |
| return response; |
| }, |
| fd_.get(), count, offset), |
| std::move(callback)); |
| } |
| |
| void LocalFile::Pwrite(std::string blob, |
| uint64_t offset, |
| PwriteCallback callback) { |
| base::PostTaskAndReplyWithResult( |
| blocking_task_runner_.get(), FROM_HERE, |
| base::BindOnce( |
| [](int fd, std::string blob, uint64_t offset) { |
| arc_proxy::PwriteResponse response; |
| int result = |
| HANDLE_EINTR(pwrite(fd, &blob[0], blob.size(), offset)); |
| if (result < 0) { |
| response.set_error_code(errno); |
| } else { |
| response.set_bytes_written(result); |
| } |
| return response; |
| }, |
| fd_.get(), std::move(blob), offset), |
| std::move(callback)); |
| } |
| |
| void LocalFile::Fstat(FstatCallback callback) { |
| base::PostTaskAndReplyWithResult(blocking_task_runner_.get(), FROM_HERE, |
| base::BindOnce( |
| [](int fd) { |
| arc_proxy::FstatResponse response; |
| struct stat st; |
| int result = fstat(fd, &st); |
| if (result < 0) { |
| response.set_error_code(errno); |
| } else { |
| response.set_error_code(0); |
| response.set_size(st.st_size); |
| } |
| return response; |
| }, |
| fd_.get()), |
| std::move(callback)); |
| } |
| |
| void LocalFile::TrySendMsg() { |
| DCHECK(!pending_write_.empty()); |
| for (; !pending_write_.empty(); pending_write_.pop_front()) { |
| auto& data = pending_write_.front(); |
| |
| while (data.blob_offset < data.blob.size()) { |
| const char* data_ptr = data.blob.data() + data.blob_offset; |
| const size_t data_size = data.blob.size() - data.blob_offset; |
| const ssize_t result = |
| data.fds.empty() ? HANDLE_EINTR(write(fd_.get(), data_ptr, data_size)) |
| : Sendmsg(fd_.get(), data_ptr, data_size, data.fds); |
| if (result == -1) { |
| if (errno == EAGAIN) { |
| // Will retry later. |
| if (!writable_watcher_) { |
| writable_watcher_ = base::FileDescriptorWatcher::WatchWritable( |
| fd_.get(), base::BindRepeating(&LocalFile::TrySendMsg, |
| weak_factory_.GetWeakPtr())); |
| } |
| return; |
| } |
| PLOG(ERROR) << "Failed to write"; |
| writable_watcher_.reset(); |
| std::move(error_handler_).Run(); // May result in deleting this object. |
| return; |
| } |
| data.fds.clear(); // To avoid sending FDs twice. |
| data.blob_offset += result; |
| } |
| } |
| // No pending data left. Stop watching. |
| writable_watcher_.reset(); |
| } |
| |
| } // namespace arc |