| // 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 |