blob: 20f3a9c7794793d4183cb002b8c973cdf6f9a5b2 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <fs/connection.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fdio/debug.h>
#include <fdio/io.h>
#include <fdio/remoteio.h>
#include <fdio/vfs.h>
#include <fs/vnode.h>
#include <zircon/assert.h>
#define MXDEBUG 0
namespace fs {
namespace {
void WriteErrorReply(zx::channel channel, zx_status_t status) {
struct {
zx_status_t status;
uint32_t type;
} reply = {status, 0};
channel.write(0, &reply, ZXRIO_OBJECT_MINSIZE, nullptr, 0);
}
zx_status_t HandoffOpenTransaction(zx_handle_t srv, zx::channel channel,
fbl::StringPiece path, uint32_t flags, uint32_t mode) {
zxrio_msg_t msg;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = ZXRIO_OPEN;
msg.arg = flags;
msg.arg2.mode = mode;
msg.datalen = static_cast<uint32_t>(path.length());
memcpy(msg.data, path.begin(), path.length());
return zxrio_txn_handoff(srv, channel.release(), &msg);
}
// Performs a path walk and opens a connection to another node.
void OpenAt(Vfs* vfs, fbl::RefPtr<Vnode> parent,
zxrio_msg_t* msg, zx::channel channel,
fbl::StringPiece path, uint32_t flags, uint32_t mode) {
// Filter out flags that are invalid when combined with O_PATH
if (IsPathOnly(flags)) {
flags &= O_PATH | O_DIRECTORY | O_NOFOLLOW | O_PIPELINE;
}
// The pipeline directive instructs the VFS layer to open the vnode
// immediately, rather than describing the VFS object to the caller.
// We check it early so we can throw away the protocol part of flags.
bool pipeline = flags & O_PIPELINE;
uint32_t open_flags = flags & (~O_PIPELINE);
size_t hcount = 0;
fbl::RefPtr<Vnode> vnode;
zx_status_t r = vfs->Open(fbl::move(parent), &vnode, path, &path, open_flags, mode);
zxrio_object_t obj;
memset(&obj, 0, sizeof(obj));
if (r != ZX_OK) {
xprintf("vfs: open: r=%d\n", r);
} else if (!(open_flags & O_NOREMOTE) && vnode->IsRemote()) {
// Remote handoff to a remote filesystem node.
//
// TODO(smklein): There exists a race between multiple threads
// opening a "dead" connection, where the second thread may
// try to send a txn_handoff_open to a closed handle.
// See ZX-1161 for more details.
r = HandoffOpenTransaction(vnode->GetRemote(), fbl::move(channel), path, flags, mode);
if (r == ZX_ERR_PEER_CLOSED) {
printf("VFS: Remote filesystem channel closed, unmounting\n");
zx::channel c;
vfs->UninstallRemote(vnode, &c);
}
return;
} else if (IsPathOnly(open_flags)) {
vnode->Vnode::GetHandles(flags, obj.handle, &hcount, &obj.type, obj.extra, &obj.esize);
} else {
// Acquire the handles to the VFS object
r = vnode->GetHandles(flags, obj.handle, &hcount, &obj.type, obj.extra, &obj.esize);
if (r != ZX_OK) {
vnode->Close();
}
}
// If r == ZX_OK, then we hold a reference to vn from open.
// Otherwise, vn is closed, and we're simply responding to the client.
if (pipeline && hcount > 0) {
// If a pipeline open was requested, but extra handles are required, then
// we cannot complete the open in a pipelined fashion.
while (hcount-- > 0) {
zx_handle_close(obj.handle[hcount]);
}
vnode->Close();
return;
}
if (!pipeline) {
// Describe the VFS object to the caller in the non-pipelined case.
obj.status = r;
obj.hcount = static_cast<uint32_t>(hcount);
channel.write(0, &obj, static_cast<uint32_t>(ZXRIO_OBJECT_MINSIZE + obj.esize),
obj.handle, obj.hcount);
}
if (r != ZX_OK) {
return;
}
// We don't care about the result because we are handing off the channel.
if (IsPathOnly(open_flags)) {
vnode->Vnode::Serve(vfs, fbl::move(channel), open_flags);
} else {
vnode->Serve(vfs, fbl::move(channel), open_flags);
}
}
} // namespace
Connection::Connection(Vfs* vfs, fbl::RefPtr<Vnode> vnode,
zx::channel channel, uint32_t flags)
: vfs_(vfs), vnode_(fbl::move(vnode)), channel_(fbl::move(channel)),
wait_(ZX_HANDLE_INVALID, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
ASYNC_FLAG_HANDLE_SHUTDOWN),
flags_(flags) {
ZX_DEBUG_ASSERT(vfs);
ZX_DEBUG_ASSERT(vnode_);
ZX_DEBUG_ASSERT(channel_);
wait_.set_handler([this](async_t* async, zx_status_t status,
const zx_packet_signal_t* signal) {
ZX_DEBUG_ASSERT(is_waiting());
// Handle the message.
if (status == ZX_OK && (signal->observed & ZX_CHANNEL_READABLE)) {
status = CallHandler();
if (status == ZX_OK) {
return ASYNC_WAIT_AGAIN;
}
}
wait_.set_object(ZX_HANDLE_INVALID);
// Give the dispatcher a chance to clean up.
if (status != ERR_DISPATCHER_DONE) {
CallHandler();
}
// Tell the VFS that the connection closed remotely.
// This might have the side-effect of destroying this object.
vfs_->OnConnectionClosedRemotely(this);
return ASYNC_WAIT_FINISHED;
});
}
Connection::~Connection() {
// Stop waiting and clean up if still connected.
if (is_waiting()) {
zx_status_t status = wait_.Cancel(vfs_->async());
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Could not cancel wait: status=%d", status);
wait_.set_object(ZX_HANDLE_INVALID);
CallHandler();
}
// Release the token associated with this connection's vnode since the connection
// will be releasing the vnode's reference once this function returns.
if (token_) {
vfs_->TokenDiscard(fbl::move(token_));
}
}
zx_status_t Connection::Serve() {
ZX_DEBUG_ASSERT(!is_waiting());
wait_.set_object(channel_.get());
zx_status_t status = wait_.Begin(vfs_->async());
if (status != ZX_OK) {
wait_.set_object(ZX_HANDLE_INVALID);
}
return status;
}
zx_status_t Connection::CallHandler() {
return zxrio_handler(channel_.get(), (void*)&Connection::HandleMessageThunk, this);
}
zx_status_t Connection::HandleMessageThunk(zxrio_msg_t* msg, void* cookie) {
Connection* connection = static_cast<Connection*>(cookie);
return connection->HandleMessage(msg);
}
zx_status_t Connection::HandleMessage(zxrio_msg_t* msg) {
uint32_t len = msg->datalen;
int32_t arg = msg->arg;
msg->datalen = 0;
// ensure handle count specified by opcode matches reality
if (msg->hcount != ZXRIO_HC(msg->op)) {
for (unsigned i = 0; i < msg->hcount; i++) {
zx_handle_close(msg->handle[i]);
}
return ZX_ERR_IO;
}
msg->hcount = 0;
switch (ZXRIO_OP(msg->op)) {
case ZXRIO_OPEN: {
char* path = (char*)msg->data;
zx::channel channel(msg->handle[0]); // take ownership
if ((len < 1) || (len > PATH_MAX)) {
WriteErrorReply(fbl::move(channel), ZX_ERR_INVALID_ARGS);
} else if ((arg & O_ADMIN) && !(flags_ & O_ADMIN)) {
WriteErrorReply(fbl::move(channel), ZX_ERR_ACCESS_DENIED);
} else {
path[len] = 0;
xprintf("vfs: open name='%s' flags=%d mode=%u\n", path, arg, msg->arg2.mode);
OpenAt(vfs_, vnode_, msg, fbl::move(channel),
fbl::StringPiece(path, len), arg, msg->arg2.mode);
}
return ERR_DISPATCHER_INDIRECT;
}
case ZXRIO_CLOSE: {
if (!IsPathOnly(flags_)) {
return vnode_->Close();
}
return ZX_OK;
}
case ZXRIO_CLONE: {
zx::channel channel(msg->handle[0]); // take ownership
if (!(arg & O_PIPELINE)) {
zxrio_object_t obj;
memset(&obj, 0, ZXRIO_OBJECT_MINSIZE);
obj.type = FDIO_PROTOCOL_REMOTE;
channel.write(0, &obj, ZXRIO_OBJECT_MINSIZE, 0, 0);
}
vnode_->Serve(vfs_, fbl::move(channel), flags_);
return ERR_DISPATCHER_INDIRECT;
}
case ZXRIO_READ: {
if (!IsReadable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual;
zx_status_t status = vnode_->Read(msg->data, arg, offset_, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(arg));
offset_ += actual;
msg->arg2.off = offset_;
msg->datalen = static_cast<uint32_t>(actual);
}
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
case ZXRIO_READ_AT: {
if (!IsReadable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual;
zx_status_t status = vnode_->Read(msg->data, arg, msg->arg2.off, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(arg));
msg->datalen = static_cast<uint32_t>(actual);
}
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
case ZXRIO_WRITE: {
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual;
zx_status_t status;
if (flags_ & O_APPEND) {
size_t end;
status = vnode_->Append(msg->data, len, &end, &actual);
if (status == ZX_OK) {
offset_ = end;
msg->arg2.off = offset_;
}
} else {
status = vnode_->Write(msg->data, len, offset_, &actual);
if (status == ZX_OK) {
offset_ += actual;
msg->arg2.off = offset_;
}
}
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
case ZXRIO_WRITE_AT: {
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual;
zx_status_t status = vnode_->Write(msg->data, len, msg->arg2.off, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
return static_cast<zx_status_t>(actual);
}
return status;
}
case ZXRIO_SEEK: {
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
vnattr_t attr;
zx_status_t r;
if ((r = vnode_->Getattr(&attr)) < 0) {
return r;
}
size_t n;
switch (arg) {
case SEEK_SET:
if (msg->arg2.off < 0) {
return ZX_ERR_INVALID_ARGS;
}
n = msg->arg2.off;
break;
case SEEK_CUR:
n = offset_ + msg->arg2.off;
if (msg->arg2.off < 0) {
// if negative seek
if (n > offset_) {
// wrapped around. attempt to seek before start
return ZX_ERR_INVALID_ARGS;
}
} else {
// positive seek
if (n < offset_) {
// wrapped around. overflow
return ZX_ERR_INVALID_ARGS;
}
}
break;
case SEEK_END:
n = attr.size + msg->arg2.off;
if (msg->arg2.off < 0) {
// if negative seek
if (n > attr.size) {
// wrapped around. attempt to seek before start
return ZX_ERR_INVALID_ARGS;
}
} else {
// positive seek
if (n < attr.size) {
// wrapped around
return ZX_ERR_INVALID_ARGS;
}
}
break;
default:
return ZX_ERR_INVALID_ARGS;
}
offset_ = n;
msg->arg2.off = offset_;
return ZX_OK;
}
case ZXRIO_STAT: {
zx_status_t r;
msg->datalen = sizeof(vnattr_t);
if ((r = vnode_->Getattr((vnattr_t*)msg->data)) < 0) {
return r;
}
return msg->datalen;
}
case ZXRIO_SETATTR: {
// TODO(smklein): Prevent read-only files from setting attributes,
// but allow attribute-setting on mutable directories.
// For context: ZX-1262, ZX-1065
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
zx_status_t r = vnode_->Setattr((vnattr_t*)msg->data);
return r;
}
case ZXRIO_FCNTL: {
uint32_t cmd = msg->arg;
constexpr uint32_t kStatusFlags = O_APPEND;
switch (cmd) {
case F_GETFL:
msg->arg2.mode = flags_ & (kStatusFlags | O_ACCMODE);
return ZX_OK;
case F_SETFL:
flags_ = (flags_ & ~kStatusFlags) | (msg->arg2.mode & kStatusFlags);
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
case ZXRIO_READDIR: {
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
if (arg > FDIO_CHUNK_SIZE) {
return ZX_ERR_INVALID_ARGS;
}
if (msg->arg2.off == READDIR_CMD_RESET) {
dircookie_.Reset();
}
size_t actual;
zx_status_t r = vfs_->Readdir(vnode_.get(), &dircookie_, msg->data, arg, &actual);
if (r == ZX_OK) {
msg->datalen = static_cast<uint32_t>(actual);
}
return r < 0 ? r : msg->datalen;
}
case ZXRIO_IOCTL_1H: {
if (IsPathOnly(flags_)) {
zx_handle_close(msg->handle[0]);
return ZX_ERR_BAD_HANDLE;
}
if ((len > FDIO_IOCTL_MAX_INPUT) ||
(arg > (ssize_t)sizeof(msg->data)) ||
(IOCTL_KIND(msg->arg2.op) != IOCTL_KIND_SET_HANDLE)) {
zx_handle_close(msg->handle[0]);
return ZX_ERR_INVALID_ARGS;
}
if (len < sizeof(zx_handle_t)) {
len = sizeof(zx_handle_t);
}
char in_buf[FDIO_IOCTL_MAX_INPUT];
// The sending side copied the handle into msg->handle[0]
// so that it would be sent via channel_write(). Here we
// copy the local version back into the space in the buffer
// that the original occupied.
memcpy(in_buf, msg->handle, sizeof(zx_handle_t));
memcpy(in_buf + sizeof(zx_handle_t), msg->data + sizeof(zx_handle_t),
len - sizeof(zx_handle_t));
switch (msg->arg2.op) {
case IOCTL_VFS_MOUNT_FS:
case IOCTL_VFS_MOUNT_MKDIR_FS:
// Mounting requires ADMIN privileges
if (!(flags_ & O_ADMIN)) {
vfs_unmount_handle(msg->handle[0], 0);
zx_handle_close(msg->handle[0]);
return ZX_ERR_ACCESS_DENIED;
}
// If our permissions validate, fall through to the VFS ioctl
}
size_t actual = 0;
zx_status_t status = vfs_->Ioctl(vnode_, msg->arg2.op,
in_buf, len, msg->data, arg,
&actual);
if (status == ZX_ERR_NOT_SUPPORTED) {
zx_handle_close(msg->handle[0]);
}
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
case ZXRIO_IOCTL: {
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
if (len > FDIO_IOCTL_MAX_INPUT ||
(arg > (ssize_t)sizeof(msg->data)) ||
(IOCTL_KIND(msg->arg2.op) == IOCTL_KIND_SET_HANDLE)) {
return ZX_ERR_INVALID_ARGS;
}
char in_buf[FDIO_IOCTL_MAX_INPUT];
memcpy(in_buf, msg->data, len);
size_t actual = 0;
switch (msg->arg2.op) {
case IOCTL_VFS_GET_TOKEN: {
// Ioctls which act on Connection
if (arg != sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
zx::event returned_token;
zx_status_t status = vfs_->VnodeToToken(vnode_, &token_, &returned_token);
if (status == ZX_OK) {
actual = sizeof(zx_handle_t);
zx_handle_t* out = reinterpret_cast<zx_handle_t*>(msg->data);
*out = returned_token.release();
}
break;
}
case IOCTL_VFS_UNMOUNT_NODE:
case IOCTL_VFS_UNMOUNT_FS:
case IOCTL_VFS_GET_DEVICE_PATH:
// Unmounting ioctls require Connection privileges
if (!(flags_ & O_ADMIN)) {
return ZX_ERR_ACCESS_DENIED;
}
// If our permissions validate, fall through to the VFS ioctl
default:
zx_status_t status = vfs_->Ioctl(vnode_, msg->arg2.op,
in_buf, len, msg->data, arg,
&actual);
if (status != ZX_OK) {
return status;
}
}
switch (IOCTL_KIND(msg->arg2.op)) {
case IOCTL_KIND_DEFAULT:
break;
case IOCTL_KIND_GET_HANDLE:
msg->hcount = 1;
memcpy(msg->handle, msg->data, sizeof(zx_handle_t));
break;
case IOCTL_KIND_GET_TWO_HANDLES:
msg->hcount = 2;
memcpy(msg->handle, msg->data, 2 * sizeof(zx_handle_t));
break;
case IOCTL_KIND_GET_THREE_HANDLES:
msg->hcount = 3;
memcpy(msg->handle, msg->data, 3 * sizeof(zx_handle_t));
break;
}
msg->arg2.off = 0;
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(arg));
msg->datalen = static_cast<uint32_t>(actual);
return static_cast<uint32_t>(actual);
}
case ZXRIO_TRUNCATE: {
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
if (msg->arg2.off < 0) {
return ZX_ERR_INVALID_ARGS;
}
return vnode_->Truncate(msg->arg2.off);
}
case ZXRIO_RENAME:
case ZXRIO_LINK: {
// Regardless of success or failure, we'll close the client-provided
// vnode token handle.
zx::event token(msg->handle[0]);
if (len < 4) { // At least one byte for src + dst + null terminators
return ZX_ERR_INVALID_ARGS;
}
char* data_end = (char*)(msg->data + len - 1);
*data_end = '\0';
const char* oldname = (const char*)msg->data;
fbl::StringPiece oldStr(oldname, strlen(oldname));
const char* newname = (const char*)msg->data + (oldStr.length() + 1);
fbl::StringPiece newStr(newname, len - (oldStr.length() + 2));
if (data_end <= newname) {
return ZX_ERR_INVALID_ARGS;
}
switch (ZXRIO_OP(msg->op)) {
case ZXRIO_RENAME:
return vfs_->Rename(fbl::move(token), vnode_,
fbl::move(oldStr), fbl::move(newStr));
case ZXRIO_LINK:
return vfs_->Link(fbl::move(token), vnode_,
fbl::move(oldStr), fbl::move(newStr));
}
assert(false);
}
case ZXRIO_MMAP: {
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
if (len != sizeof(zxrio_mmap_data_t)) {
return ZX_ERR_INVALID_ARGS;
}
zxrio_mmap_data_t* data = reinterpret_cast<zxrio_mmap_data_t*>(msg->data);
if ((flags_ & O_APPEND) && data->flags & FDIO_MMAP_FLAG_WRITE) {
return ZX_ERR_ACCESS_DENIED;
} else if (!IsWritable(flags_) && (data->flags & FDIO_MMAP_FLAG_WRITE)) {
return ZX_ERR_ACCESS_DENIED;
} else if (!IsReadable(flags_)) {
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t status = vnode_->Mmap(data->flags, data->length, &data->offset,
&msg->handle[0]);
if (status == ZX_OK) {
msg->hcount = 1;
}
return status;
}
case ZXRIO_SYNC: {
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
return vnode_->Sync();
}
case ZXRIO_UNLINK:
return vfs_->Unlink(vnode_, fbl::StringPiece((const char*)msg->data, len));
default:
// close inbound handles so they do not leak
for (unsigned i = 0; i < ZXRIO_HC(msg->op); i++) {
zx_handle_close(msg->handle[i]);
}
return ZX_ERR_NOT_SUPPORTED;
}
}
} // namespace fs