blob: ad62fdac529d61d4b942acb8e627aaf4b076285f [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/vsock_proxy/socket_stream.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <tuple>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/posix/unix_domain_socket_linux.h>
#include "arc/vm/vsock_proxy/file_descriptor_util.h"
#include "arc/vm/vsock_proxy/vsock_proxy.h"
namespace arc {
SocketStream::SocketStream(base::ScopedFD socket_fd, VSockProxy* proxy)
: socket_fd_(std::move(socket_fd)), proxy_(proxy) {
// TODO(hidehiko): Re-enable DCHECK on production.
// Currently nullptr for proxy is allowed for unit testing purpose.
// DCHECK(proxy);
}
SocketStream::~SocketStream() = default;
bool SocketStream::Read(arc_proxy::Message* message) {
char buf[4096];
std::vector<base::ScopedFD> fds;
ssize_t size =
base::UnixDomainSocket::RecvMsg(socket_fd_.get(), buf, sizeof(buf), &fds);
if (size == -1) {
PLOG(ERROR) << "Failed to recieve a message";
return false;
}
if (size == 0 && fds.empty()) {
LOG(INFO) << "EOF Found";
// Set 0 length data to indicate the other side of the FD is closed.
message->Clear();
return true;
}
// Validate file descriptor type before registering to VSockProxy.
std::vector<arc_proxy::FileDescriptor::Type> fd_types;
fd_types.reserve(fds.size());
for (const auto& fd : fds) {
struct stat st;
if (fstat(fd.get(), &st) == -1) {
PLOG(ERROR) << "Failed to fstat";
return false;
}
if (S_ISFIFO(st.st_mode)) {
int flags = fcntl(fd.get(), F_GETFL, 0);
if (flags < 0) {
PLOG(ERROR) << "Failed to find file status flags";
return false;
}
switch (flags & O_ACCMODE) {
case O_RDONLY:
fd_types.push_back(arc_proxy::FileDescriptor::FIFO_READ);
break;
case O_WRONLY:
fd_types.push_back(arc_proxy::FileDescriptor::FIFO_WRITE);
break;
default:
PLOG(ERROR) << "Unsupported access mode: " << (flags & O_ACCMODE);
return false;
}
continue;
}
if (S_ISSOCK(st.st_mode)) {
fd_types.push_back(arc_proxy::FileDescriptor::SOCKET);
continue;
}
LOG(ERROR) << "Unsupported FD type: " << st.st_mode;
return false;
}
DCHECK_EQ(fds.size(), fd_types.size());
// Build returning message.
message->set_data(buf, size);
for (size_t i = 0; i < fds.size(); ++i) {
uint64_t handle = proxy_->RegisterFileDescriptor(
std::move(fds[i]), fd_types[i], 0 /* generate handle */);
auto* transferred_fd = message->add_transferred_fd();
transferred_fd->set_handle(handle);
transferred_fd->set_type(fd_types[i]);
}
return true;
}
bool SocketStream::Write(const arc_proxy::Message& message) {
// First, create file descriptors for the received message.
std::vector<base::ScopedFD> transferred_fds;
transferred_fds.reserve(message.transferred_fd().size());
for (const auto& transferred_fd : message.transferred_fd()) {
base::ScopedFD local_fd;
base::ScopedFD remote_fd;
switch (transferred_fd.type()) {
case arc_proxy::FileDescriptor::FIFO_READ: {
auto created = CreatePipe();
if (!created)
return false;
std::tie(remote_fd, local_fd) = std::move(*created);
break;
}
case arc_proxy::FileDescriptor::FIFO_WRITE: {
auto created = CreatePipe();
if (!created)
return false;
std::tie(local_fd, remote_fd) = std::move(*created);
break;
}
case arc_proxy::FileDescriptor::SOCKET: {
auto created = CreateSocketPair();
if (!created)
return false;
std::tie(local_fd, remote_fd) = std::move(*created);
break;
}
default:
LOG(ERROR) << "Unsupported FD type: " << transferred_fd.type();
return false;
}
proxy_->RegisterFileDescriptor(std::move(local_fd), transferred_fd.type(),
transferred_fd.handle());
transferred_fds.emplace_back(std::move(remote_fd));
}
std::vector<int> fds;
fds.reserve(transferred_fds.size());
for (const auto& fd : transferred_fds)
fds.push_back(fd.get());
if (!base::UnixDomainSocket::SendMsg(socket_fd_.get(), message.data().data(),
message.data().size(), fds)) {
PLOG(ERROR) << "Failed to send message";
return false;
}
return true;
}
} // namespace arc