blob: ea79d3d03b44aefc26a94a349d5cc1e37b200334 [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.fidl2.h>
#include <fdio/io.h>
#include <fdio/remoteio.h>
#include <fdio/vfs.h>
#include <fs/trace.h>
#include <fs/vnode.h>
#include <zircon/assert.h>
#include <zx/handle.h>
#define ZXDEBUG 0
namespace fs {
namespace {
void WriteDescribeError(zx::channel channel, zx_status_t status) {
zxrio_describe_t msg;
memset(&msg, 0, sizeof(msg));
msg.op = ZXRIO_ON_OPEN;
msg.status = status;
channel.write(0, &msg, sizeof(zxrio_describe_t), nullptr, 0);
}
void Describe(const fbl::RefPtr<Vnode>& vn, uint32_t flags,
zxrio_describe_t* response, zx_handle_t* handle) {
response->op = ZXRIO_ON_OPEN;
zx_status_t r;
*handle = ZX_HANDLE_INVALID;
if (IsPathOnly(flags)) {
r = vn->Vnode::GetHandles(flags, handle, &response->extra.tag, &response->extra);
} else {
r = vn->GetHandles(flags, handle, &response->extra.tag, &response->extra);
}
response->status = r;
response->extra_ptr = reinterpret_cast<zxrio_object_info_t*>(r == ZX_OK ?
FIDL_ALLOC_PRESENT :
FIDL_ALLOC_ABSENT);
}
// Performs a path walk and opens a connection to another node.
void OpenAt(Vfs* vfs, fbl::RefPtr<Vnode> parent, zx::channel channel,
fbl::StringPiece path, uint32_t flags, uint32_t mode) {
// Filter out flags that are invalid when combined with REF_ONLY.
if (IsPathOnly(flags)) {
flags &= ZX_FS_FLAG_VNODE_REF_ONLY | ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_DESCRIBE;
}
bool describe = flags & ZX_FS_FLAG_DESCRIBE;
uint32_t open_flags = flags & (~ZX_FS_FLAG_DESCRIBE);
fbl::RefPtr<Vnode> vnode;
zx_status_t r = vfs->Open(fbl::move(parent), &vnode, path, &path, open_flags, mode);
if (r != ZX_OK) {
xprintf("vfs: open: r=%d\n", r);
} else if (!(open_flags & ZX_FS_FLAG_NOREMOTE) && vnode->IsRemote()) {
// Remote handoff to a remote filesystem node.
zxrio_msg_t msg;
#ifdef ZXRIO_FIDL
DirectoryOpenRequest* request = reinterpret_cast<DirectoryOpenRequest*>(&msg);
memset(request, 0, sizeof(DirectoryOpenRequest));
request->hdr.ordinal = ZXFIDL_OPEN;
request->flags = flags;
request->mode = mode;
request->path.size = path.length();
request->path.data = (char*) FIDL_ALLOC_PRESENT;
request->object = FIDL_HANDLE_PRESENT;
void* secondary =
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(request) +
FIDL_ALIGN(sizeof(DirectoryOpenRequest)));
memcpy(secondary, path.begin(), path.length());
#else
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());
#endif
vfs->ForwardMessageRemote(fbl::move(vnode), fbl::move(channel), &msg);
return;
}
if (describe) {
// Regardless of the error code, in the 'describe' case, we
// should respond to the client.
if (r != ZX_OK) {
WriteDescribeError(fbl::move(channel), r);
return;
}
zxrio_describe_t response;
memset(&response, 0, sizeof(response));
zx_handle_t extra = ZX_HANDLE_INVALID;
Describe(vnode, flags, &response, &extra);
uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
channel.write(0, &response, sizeof(zxrio_describe_t), &extra, hcount);
} else if (r != ZX_OK) {
return;
}
// If r == ZX_OK, then we hold a reference to vn from open.
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();
switch (status) {
case ZX_OK:
return ASYNC_WAIT_AGAIN;
case ERR_DISPATCHER_ASYNC:
return ASYNC_WAIT_FINISHED;
}
}
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(), &Connection::HandleMessageThunk, this);
}
zx_status_t Connection::HandleMessageThunk(zxrio_msg_t* msg, void* cookie) {
Connection* connection = static_cast<Connection*>(cookie);
return connection->HandleMessage(msg);
}
// Flags which can be modified by SetFlags
constexpr uint32_t kStatusFlags = ZX_FS_FLAG_APPEND;
zx_status_t Connection::HandleMessage(zxrio_msg_t* msg) {
uint32_t len = msg->datalen;
int32_t arg = msg->arg;
if (!ZXRIO_FIDL_MSG(msg->op)) {
msg->datalen = 0;
msg->hcount = 0;
}
switch (ZXRIO_OP(msg->op)) {
case ZXFIDL_OPEN:
case ZXRIO_OPEN: {
TRACE_DURATION("vfs", "ZXRIO_OPEN");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<DirectoryOpenRequest*>(msg);
uint32_t flags;
uint32_t mode;
char* path;
zx::channel channel;
if (fidl) {
flags = request->flags;
mode = request->mode;
path = request->path.data;
len = static_cast<uint32_t>(request->path.size);
channel.reset(request->object);
} else {
flags = arg;
mode = msg->arg2.mode;
path = (char*) msg->data;
channel.reset(msg->handle[0]);
}
bool describe = flags & ZX_FS_FLAG_DESCRIBE;
if ((len < 1) || (len > PATH_MAX)) {
if (describe) {
WriteDescribeError(fbl::move(channel), ZX_ERR_INVALID_ARGS);
}
} else if ((flags & ZX_FS_RIGHT_ADMIN) && !(flags_ & ZX_FS_RIGHT_ADMIN)) {
if (describe) {
WriteDescribeError(fbl::move(channel), ZX_ERR_ACCESS_DENIED);
}
} else {
path[len] = 0;
xprintf("vfs: open name='%s' flags=%d mode=%u\n", path, flags, mode);
OpenAt(vfs_, vnode_, fbl::move(channel),
fbl::StringPiece(path, len), flags, mode);
}
return ERR_DISPATCHER_INDIRECT;
}
case ZXFIDL_CLOSE:
case ZXRIO_CLOSE: {
TRACE_DURATION("vfs", "ZXRIO_CLOSE");
if (!IsPathOnly(flags_)) {
return vnode_->Close();
}
return ZX_OK;
}
case ZXFIDL_CLONE:
case ZXRIO_CLONE: {
TRACE_DURATION("vfs", "ZXRIO_CLONE");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<ObjectCloneRequest*>(msg);
zx::channel channel;
uint32_t flags;
if (fidl) {
channel.reset(request->object);
flags = request->flags;
} else {
channel.reset(msg->handle[0]); // take ownership
flags = arg;
}
fbl::RefPtr<Vnode> vn(vnode_);
zx_status_t status = OpenVnode(flags_, &vn);
bool describe = flags & ZX_FS_FLAG_DESCRIBE;
if (describe) {
zxrio_describe_t response;
memset(&response, 0, sizeof(response));
response.status = status;
zx_handle_t extra = ZX_HANDLE_INVALID;
if (status == ZX_OK) {
Describe(vnode_, flags_, &response, &extra);
}
uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
channel.write(0, &response, sizeof(zxrio_describe_t), &extra, hcount);
}
if (status == ZX_OK) {
vn->Serve(vfs_, fbl::move(channel), flags_);
}
return ERR_DISPATCHER_INDIRECT;
}
case ZXFIDL_READ:
case ZXRIO_READ: {
TRACE_DURATION("vfs", "ZXRIO_READ");
if (!IsReadable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<FileReadRequest*>(msg);
auto response = reinterpret_cast<FileReadResponse*>(msg);
void* data;
if (fidl) {
data = (void*)((uintptr_t)response + FIDL_ALIGN(sizeof(FileReadResponse)));
len = static_cast<uint32_t>(request->count);
} else {
data = msg->data;
len = arg;
}
size_t actual;
zx_status_t status = vnode_->Read(data, len, offset_, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
offset_ += actual;
if (fidl) {
response->data.count = actual;
} else {
msg->datalen = static_cast<uint32_t>(actual);
status = static_cast<zx_status_t>(actual);
}
}
return status;
}
case ZXFIDL_READ_AT:
case ZXRIO_READ_AT: {
TRACE_DURATION("vfs", "ZXRIO_READ_AT");
if (!IsReadable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<FileReadAtRequest*>(msg);
auto response = reinterpret_cast<FileReadAtResponse*>(msg);
void* data;
uint64_t offset;
if (fidl) {
data = (void*)((uintptr_t)response + FIDL_ALIGN(sizeof(FileReadAtResponse)));
len = static_cast<uint32_t>(request->count);
offset = request->offset;
} else {
data = msg->data;
len = arg;
offset = msg->arg2.off;
}
size_t actual;
zx_status_t status = vnode_->Read(data, len, offset, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
if (fidl) {
response->data.count = actual;
} else {
msg->datalen = static_cast<uint32_t>(actual);
status = static_cast<zx_status_t>(actual);
}
}
return status;
}
case ZXFIDL_WRITE:
case ZXRIO_WRITE: {
TRACE_DURATION("vfs", "ZXRIO_WRITE");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileWriteRequest* request = reinterpret_cast<FileWriteRequest*>(msg);
FileWriteResponse* response = reinterpret_cast<FileWriteResponse*>(msg);
void* data;
if (fidl) {
data = request->data.data;
len = static_cast<uint32_t>(request->data.count);
} else {
data = msg->data;
}
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual = 0;
zx_status_t status;
if (flags_ & ZX_FS_FLAG_APPEND) {
size_t end;
status = vnode_->Append(data, len, &end, &actual);
if (status == ZX_OK) {
offset_ = end;
}
} else {
status = vnode_->Write(data, len, offset_, &actual);
if (status == ZX_OK) {
offset_ += actual;
}
}
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
if (fidl) {
response->actual = actual;
return status;
} else {
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
}
case ZXFIDL_WRITE_AT:
case ZXRIO_WRITE_AT: {
TRACE_DURATION("vfs", "ZXRIO_WRITE_AT");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileWriteAtRequest* request = reinterpret_cast<FileWriteAtRequest*>(msg);
FileWriteAtResponse* response = reinterpret_cast<FileWriteAtResponse*>(msg);
void* data;
uint64_t offset;
if (fidl) {
data = request->data.data;
len = static_cast<uint32_t>(request->data.count);
offset = request->offset;
} else {
data = msg->data;
offset = msg->arg2.off;
}
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
size_t actual = 0;
zx_status_t status = vnode_->Write(data, len, offset, &actual);
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(len));
if (fidl) {
response->actual = actual;
return status;
} else {
return status == ZX_OK ? static_cast<zx_status_t>(actual) : status;
}
}
case ZXFIDL_SEEK:
case ZXRIO_SEEK: {
TRACE_DURATION("vfs", "ZXRIO_SEEK");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileSeekRequest* request = reinterpret_cast<FileSeekRequest*>(msg);
FileSeekResponse* response = reinterpret_cast<FileSeekResponse*>(msg);
static_assert(SEEK_SET == SeekOrigin_Start, "");
static_assert(SEEK_CUR == SeekOrigin_Current, "");
static_assert(SEEK_END == SeekOrigin_End, "");
off_t offset;
int whence;
if (fidl) {
offset = request->offset;
whence = request->start;
} else {
offset = msg->arg2.off;
whence = arg;
}
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 (whence) { case SEEK_SET:
if (offset < 0) {
return ZX_ERR_INVALID_ARGS;
}
n = offset;
break;
case SEEK_CUR:
n = offset_ + offset;
if (offset < 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 + offset;
if (offset < 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;
if (fidl) {
response->offset = offset_;
} else {
msg->arg2.off = offset_;
}
return ZX_OK;
}
case ZXFIDL_STAT:
case ZXRIO_STAT: {
TRACE_DURATION("vfs", "ZXRIO_STAT");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto response = reinterpret_cast<NodeGetAttrResponse*>(msg);
// TODO(smklein): Consider using "NodeAttributes" within
// ulib/fs, rather than vnattr_t.
// Alternatively modify vnattr_t to match "NodeAttributes"
vnattr_t attr;
zx_status_t r;
if ((r = vnode_->Getattr(&attr)) != ZX_OK) {
return r;
}
if (fidl) {
response->attributes.mode = attr.mode;
response->attributes.id = attr.inode;
response->attributes.content_size = attr.size;
response->attributes.storage_size = attr.blksize * attr.blkcount;
response->attributes.link_count = attr.nlink;
response->attributes.creation_time = attr.create_time;
response->attributes.modification_time = attr.modify_time;
return r;
}
memcpy(msg->data, &attr, sizeof(vnattr_t));
msg->datalen = sizeof(vnattr_t);
return msg->datalen;
}
case ZXFIDL_SETATTR:
case ZXRIO_SETATTR: {
TRACE_DURATION("vfs", "ZXRIO_SETATTR");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<NodeSetAttrRequest*>(msg);
// 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;
}
vnattr_t attr;
if (fidl) {
attr.valid = request->flags;
attr.create_time = request->attributes.creation_time;
attr.modify_time = request->attributes.modification_time;
} else {
memcpy(&attr, &msg->data, sizeof(attr));
}
return vnode_->Setattr(&attr);
}
case ZXFIDL_GET_FLAGS: {
TRACE_DURATION("vfs", "ZXFIDL_GET_FLAGS");
FileGetFlagsResponse* response = reinterpret_cast<FileGetFlagsResponse*>(msg);
response->flags = flags_ & (kStatusFlags | ZX_FS_RIGHTS | ZX_FS_FLAG_VNODE_REF_ONLY);
return ZX_OK;
}
case ZXFIDL_SET_FLAGS: {
TRACE_DURATION("vfs", "ZXFIDL_SET_FLAGS");
FileSetFlagsRequest* request = reinterpret_cast<FileSetFlagsRequest*>(msg);
flags_ = (flags_ & ~kStatusFlags) | (request->flags & kStatusFlags);
return ZX_OK;
}
case ZXRIO_FCNTL: {
TRACE_DURATION("vfs", "ZXRIO_FCNTL");
uint32_t cmd = msg->arg;
switch (cmd) {
case F_GETFL:
msg->arg2.mode = flags_ & (kStatusFlags | ZX_FS_RIGHTS | ZX_FS_FLAG_VNODE_REF_ONLY);
return ZX_OK;
case F_SETFL:
flags_ = (flags_ & ~kStatusFlags) | (msg->arg2.mode & kStatusFlags);
return ZX_OK;
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
case ZXFIDL_REWIND: {
TRACE_DURATION("vfs", "ZXRIO_REWIND");
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
dircookie_.Reset();
return ZX_OK;
}
case ZXFIDL_READDIR:
case ZXRIO_READDIR: {
TRACE_DURATION("vfs", "ZXRIO_READDIR");
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<DirectoryReadDirentsRequest*>(msg);
auto response = reinterpret_cast<DirectoryReadDirentsResponse*>(msg);
uint32_t max_out;
void* data;
if (fidl) {
data = (void*)((uintptr_t)response + FIDL_ALIGN(sizeof(DirectoryReadDirentsResponse)));
max_out = static_cast<uint32_t>(request->max_out);
} else {
max_out = arg;
if (msg->arg2.off == READDIR_CMD_RESET) {
dircookie_.Reset();
}
data = msg->data;
}
if (max_out > FDIO_CHUNK_SIZE) {
return ZX_ERR_INVALID_ARGS;
}
size_t actual;
zx_status_t r = vfs_->Readdir(vnode_.get(), &dircookie_, data, max_out, &actual);
if (r == ZX_OK) {
if (fidl) {
response->dirents.count = actual;
} else {
msg->datalen = static_cast<uint32_t>(actual);
r = static_cast<zx_status_t>(actual);
}
}
return r;
}
case ZXFIDL_IOCTL:
case ZXRIO_IOCTL:
case ZXRIO_IOCTL_1H: {
auto request = reinterpret_cast<NodeIoctlRequest*>(msg);
auto response = reinterpret_cast<NodeIoctlResponse*>(msg);
bool fidl = ZXRIO_FIDL_MSG(msg->op);
uint32_t op;
zx_handle_t* handles;
size_t hcount;
void* in;
size_t inlen;
void* out;
size_t outlen;
void* secondary = (void*)((uintptr_t)(msg) + FIDL_ALIGN(sizeof(NodeIoctlResponse)));
if (fidl) {
op = request->opcode;
handles = static_cast<zx_handle_t*>(request->handles.data);
hcount = request->handles.count;
in = request->in.data;
inlen = request->in.count;
out = secondary;
outlen = request->max_out;
} else {
op = msg->arg2.op;
handles = msg->handle;
hcount = ZXRIO_OP(msg->op) == ZXRIO_IOCTL_1H ? 1 : 0;
in = msg->data;
inlen = len;
out = msg->data;
outlen = arg;
}
zx::handle handle;
if (hcount == 1) {
handle.reset(handles[0]);
if (IOCTL_KIND(op) != IOCTL_KIND_SET_HANDLE) {
return ZX_ERR_INVALID_ARGS;
}
if (inlen < sizeof(zx_handle_t)) {
inlen = sizeof(zx_handle_t);
}
}
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
if ((inlen > FDIO_IOCTL_MAX_INPUT) || (outlen > FDIO_IOCTL_MAX_INPUT)) {
return ZX_ERR_INVALID_ARGS;
}
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.
size_t hsize = hcount * sizeof(zx_handle_t);
zx_handle_t h = handle.release();
memcpy(in_buf, &h, hsize);
memcpy(in_buf + hsize, (void*)((uintptr_t)in + hsize), inlen - hsize);
// Some ioctls operate on the connection only, and don't
// require a call to Vfs::Ioctl
bool do_ioctl = true;
size_t actual = 0;
zx_status_t status;
switch (op) {
case IOCTL_VFS_MOUNT_FS:
case IOCTL_VFS_MOUNT_MKDIR_FS:
// Mounting requires ADMIN privileges
if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
vfs_unmount_handle(h, 0);
return ZX_ERR_ACCESS_DENIED;
}
// If our permissions validate, fall through to the VFS ioctl
break;
case IOCTL_VFS_GET_TOKEN: {
// Ioctls which act on Connection
if (outlen != sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
zx::event returned_token;
status = vfs_->VnodeToToken(vnode_, &token_, &returned_token);
if (status == ZX_OK) {
actual = sizeof(zx_handle_t);
zx_handle_t* handleout = reinterpret_cast<zx_handle_t*>(out);
*handleout = returned_token.release();
}
do_ioctl = false;
break;
}
case IOCTL_VFS_UNMOUNT_NODE:
case IOCTL_VFS_UNMOUNT_FS:
case IOCTL_VFS_GET_DEVICE_PATH:
// Unmounting ioctls require Connection privileges
if (!(flags_ & ZX_FS_RIGHT_ADMIN)) {
return ZX_ERR_ACCESS_DENIED;
}
// If our permissions validate, fall through to the VFS ioctl
break;
}
if (do_ioctl) {
status = vfs_->Ioctl(vnode_, op, in_buf, inlen, out, outlen,
&actual);
if (status == ZX_ERR_NOT_SUPPORTED && hcount > 0) {
zx_handle_close(h);
}
}
if (status != ZX_OK) {
return status;
}
hcount = 0;
switch (IOCTL_KIND(op)) {
case IOCTL_KIND_DEFAULT:
break;
case IOCTL_KIND_GET_HANDLE:
hcount = 1;
break;
case IOCTL_KIND_GET_TWO_HANDLES:
hcount = 2;
break;
case IOCTL_KIND_GET_THREE_HANDLES:
hcount = 3;
break;
}
ZX_DEBUG_ASSERT(actual <= static_cast<size_t>(outlen));
if (fidl) {
response->handles.count = hcount;
response->handles.data = secondary;
response->out.count = actual;
response->out.data = secondary;
return ZX_OK;
} else {
msg->hcount = static_cast<uint32_t>(hcount);
memcpy(msg->handle, msg->data, hcount * sizeof(zx_handle_t));
msg->datalen = static_cast<uint32_t>(actual);
return static_cast<zx_status_t>(actual);
}
}
case ZXFIDL_TRUNCATE:
case ZXRIO_TRUNCATE: {
TRACE_DURATION("vfs", "ZXRIO_TRUNCATE");
if (!IsWritable(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
auto request = reinterpret_cast<FileTruncateRequest*>(msg);
uint64_t length;
if (fidl) {
length = request->length;
} else {
length = msg->arg2.off;
}
return vnode_->Truncate(length);
}
case ZXFIDL_RENAME:
case ZXFIDL_LINK:
case ZXRIO_RENAME:
case ZXRIO_LINK: {
TRACE_DURATION("vfs", (ZXRIO_OP(msg->op) == ZXRIO_RENAME ?
"ZXRIO_RENAME" : "ZXRIO_LINK"));
bool fidl = ZXRIO_FIDL_MSG(msg->op);
// These static assertions must all validate before DirectoryRenameRequest
// and DirectoryLinkRequest can be used interchangeably
static_assert(sizeof(DirectoryRenameRequest) == sizeof(DirectoryLinkRequest), "");
static_assert(sizeof(DirectoryRenameResponse) == sizeof(DirectoryLinkResponse), "");
static_assert(offsetof(DirectoryRenameRequest, src) == offsetof(DirectoryLinkRequest, src), "");
static_assert(offsetof(DirectoryRenameRequest, dst_parent_token) ==
offsetof(DirectoryLinkRequest, dst_parent_token), "");
static_assert(offsetof(DirectoryRenameRequest, dst) == offsetof(DirectoryLinkRequest, dst), "");
auto request = reinterpret_cast<DirectoryRenameRequest*>(msg);
// Regardless of success or failure, we'll close the client-provided
// vnode token handle.
zx::event token;
fbl::StringPiece oldStr, newStr;
if (fidl) {
token.reset(request->dst_parent_token);
if (request->src.size < 1 || request->dst.size < 1) {
return ZX_ERR_INVALID_ARGS;
}
oldStr.set(request->src.data, request->src.size);
newStr.set(request->dst.data, request->dst.size);
} else {
token.reset(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;
oldStr.set(oldname, strlen(oldname));
const char* newname = (const char*)msg->data + (oldStr.length() + 1);
newStr.set(newname, len - (oldStr.length() + 2));
if (data_end <= newname) {
return ZX_ERR_INVALID_ARGS;
}
}
switch (ZXRIO_OP(msg->op)) {
case ZXFIDL_RENAME:
case ZXRIO_RENAME: {
return vfs_->Rename(fbl::move(token), vnode_,
fbl::move(oldStr), fbl::move(newStr));
}
case ZXFIDL_LINK:
case ZXRIO_LINK: {
return vfs_->Link(fbl::move(token), vnode_,
fbl::move(oldStr), fbl::move(newStr));
}
}
assert(false);
}
case ZXFIDL_GET_VMO:
case ZXRIO_MMAP: {
TRACE_DURATION("vfs", "ZXRIO_MMAP");
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
uint32_t flags;
zx_handle_t* handle;
if (fidl) {
auto request = reinterpret_cast<FileGetVmoRequest*>(msg);
auto response = reinterpret_cast<FileGetVmoResponse*>(msg);
flags = request->flags;
handle = &response->vmo;
} else {
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);
flags = data->flags;
handle = &msg->handle[0];
}
if ((flags_ & ZX_FS_FLAG_APPEND) && flags & FDIO_MMAP_FLAG_WRITE) {
return ZX_ERR_ACCESS_DENIED;
} else if (!IsWritable(flags_) && (flags & FDIO_MMAP_FLAG_WRITE)) {
return ZX_ERR_ACCESS_DENIED;
} else if (!IsReadable(flags_)) {
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t status = vnode_->GetVmo(flags, handle);
if (!fidl && status == ZX_OK) {
msg->hcount = 1;
}
return status;
}
case ZXFIDL_SYNC:
case ZXRIO_SYNC: {
TRACE_DURATION("vfs", "ZXRIO_SYNC");
if (IsPathOnly(flags_)) {
return ZX_ERR_BAD_HANDLE;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
zx_txid_t txid = msg->txid;
Vnode::SyncCallback closure([this, txid, fidl](zx_status_t status) {
zxrio_msg_t msg;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.txid = txid;
msg.op = fidl ? ZXFIDL_SYNC : ZXRIO_SYNC;
zxrio_write_response(channel_.get(), status, &msg);
// Reset the wait object
ZX_ASSERT(wait_.Begin(vfs_->async()) == ZX_OK);
});
vnode_->Sync(fbl::move(closure));
return ERR_DISPATCHER_ASYNC;
}
case ZXFIDL_UNLINK:
case ZXRIO_UNLINK: {
TRACE_DURATION("vfs", "ZXRIO_UNLINK");
bool fidl = ZXRIO_FIDL_MSG(msg->op);
DirectoryUnlinkRequest* request = reinterpret_cast<DirectoryUnlinkRequest*>(msg);
char* data;
uint32_t datalen;
if (fidl) {
data = request->path.data;
datalen = static_cast<uint32_t>(request->path.size);
} else {
data = reinterpret_cast<char*>(msg->data);
datalen = len;
}
return vfs_->Unlink(vnode_, fbl::StringPiece(data, datalen));
}
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