blob: 6a7e14f0ae7b43622d43fb7d8833c4963e2c95ad [file] [log] [blame]
// 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