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