| // Copyright 2019 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 <lib/fdio/vfs.h> |
| #include <lib/vfs/cpp/flags.h> |
| #include <lib/vfs/cpp/internal/file_connection.h> |
| #include <lib/vfs/cpp/pseudo_file.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <sstream> |
| |
| namespace vfs { |
| |
| PseudoFile::PseudoFile(size_t max_file_size, ReadHandler read_handler, WriteHandler write_handler) |
| : read_handler_(std::move(read_handler)), |
| write_handler_(std::move(write_handler)), |
| max_file_size_(max_file_size) { |
| ZX_DEBUG_ASSERT(read_handler_ != nullptr); |
| } |
| |
| PseudoFile::~PseudoFile() = default; |
| |
| zx_status_t PseudoFile::CreateConnection(uint32_t flags, |
| std::unique_ptr<vfs::internal::Connection>* connection) { |
| std::vector<uint8_t> output; |
| if (Flags::IsReadable(flags)) { |
| zx_status_t status = read_handler_(&output, max_file_size_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (output.size() > max_file_size_) { |
| return ZX_ERR_FILE_BIG; |
| } |
| } |
| *connection = std::make_unique<PseudoFile::Content>(this, flags, std::move(output)); |
| return ZX_OK; |
| } |
| |
| zx_status_t PseudoFile::GetAttr(fuchsia::io::NodeAttributes* out_attributes) const { |
| out_attributes->mode = fuchsia::io::MODE_TYPE_FILE; |
| if (read_handler_ != nullptr) |
| out_attributes->mode |= V_IRUSR; |
| if (write_handler_) |
| out_attributes->mode |= V_IWUSR; |
| out_attributes->id = fuchsia::io::INO_UNKNOWN; |
| out_attributes->content_size = 0; |
| out_attributes->storage_size = 0; |
| out_attributes->link_count = 1; |
| out_attributes->creation_time = 0; |
| out_attributes->modification_time = 0; |
| return ZX_OK; |
| } |
| |
| NodeKind::Type PseudoFile::GetKind() const { |
| auto kind = File::GetKind() | NodeKind::kReadable; |
| if (write_handler_ != nullptr) { |
| kind |= NodeKind::kWritable | NodeKind::kCanTruncate; |
| } |
| return kind; |
| } |
| |
| uint64_t PseudoFile::GetLength() { |
| // this should never be called |
| ZX_ASSERT(false); |
| |
| return 0u; |
| } |
| |
| size_t PseudoFile::GetCapacity() { |
| // this should never be called |
| ZX_DEBUG_ASSERT(false); |
| |
| return max_file_size_; |
| } |
| |
| PseudoFile::Content::Content(PseudoFile* file, uint32_t flags, std::vector<uint8_t> content) |
| : Connection(flags), file_(file), buffer_(std::move(content)), flags_(flags) { |
| SetInputLength(buffer_.size()); |
| } |
| |
| PseudoFile::Content::~Content() { TryFlushIfRequired(); } |
| |
| zx_status_t PseudoFile::Content::TryFlushIfRequired() { |
| if (!dirty_) { |
| return ZX_OK; |
| } |
| dirty_ = false; |
| return file_->write_handler_(std::move(buffer_)); |
| } |
| |
| zx_status_t PseudoFile::Content::PreClose(Connection* connection) { return TryFlushIfRequired(); } |
| |
| NodeKind::Type PseudoFile::Content::GetKind() const { return file_->GetKind(); } |
| |
| zx_status_t PseudoFile::Content::ReadAt(uint64_t count, uint64_t offset, |
| std::vector<uint8_t>* out_data) { |
| if (offset >= buffer_.size()) { |
| return ZX_OK; |
| } |
| size_t actual = std::min(buffer_.size() - offset, count); |
| out_data->resize(actual); |
| std::copy_n(buffer_.begin() + offset, actual, out_data->begin()); |
| return ZX_OK; |
| } |
| |
| zx_status_t PseudoFile::Content::GetAttr(fuchsia::io::NodeAttributes* out_attributes) const { |
| auto status = file_->GetAttr(out_attributes); |
| if (status == ZX_OK) { |
| out_attributes->content_size = buffer_.size(); |
| } |
| return status; |
| } |
| |
| zx_status_t PseudoFile::Content::WriteAt(std::vector<uint8_t> data, uint64_t offset, |
| uint64_t* out_actual) { |
| if (offset >= file_->max_file_size_) { |
| *out_actual = 0u; |
| return ZX_OK; |
| } |
| |
| size_t actual = std::min(data.size(), file_->max_file_size_ - offset); |
| if (actual == 0) { |
| *out_actual = 0u; |
| return ZX_OK; |
| } |
| |
| dirty_ = true; |
| if (actual + offset > buffer_.size()) { |
| SetInputLength(offset + actual); |
| } |
| |
| std::copy_n(data.begin(), actual, buffer_.begin() + offset); |
| *out_actual = actual; |
| return ZX_OK; |
| } |
| |
| zx_status_t PseudoFile::Content::Truncate(uint64_t length) { |
| if (length > file_->max_file_size_) { |
| return ZX_ERR_NO_SPACE; |
| } |
| |
| dirty_ = true; |
| SetInputLength(length); |
| return ZX_OK; |
| } |
| |
| uint64_t PseudoFile::Content::GetLength() { return buffer_.size(); } |
| |
| size_t PseudoFile::Content::GetCapacity() { return file_->max_file_size_; } |
| |
| void PseudoFile::Content::SetInputLength(size_t length) { |
| ZX_ASSERT_MSG(length <= file_->max_file_size_, "Should not happen. Please report a bug."); |
| |
| buffer_.resize(length); |
| } |
| |
| zx_status_t PseudoFile::Content::BindInternal(zx::channel request, async_dispatcher_t* dispatcher) { |
| std::unique_ptr<Connection> connection; |
| zx_status_t status = CreateConnection(flags_, &connection); |
| if (status != ZX_OK) { |
| SendOnOpenEventOnError(flags_, std::move(request), status); |
| return status; |
| } |
| status = connection->Bind(std::move(request), dispatcher); |
| |
| AddConnection(std::move(connection)); |
| |
| // only one connection allowed per content |
| ZX_DEBUG_ASSERT(GetConnectionCount() == 1); |
| |
| return status; |
| } |
| |
| std::unique_ptr<vfs::internal::Connection> PseudoFile::Content::Close(Connection* connection) { |
| File::Close(connection); |
| return file_->Close(this); |
| } |
| |
| void PseudoFile::Content::Clone(uint32_t flags, uint32_t parent_flags, zx::channel request, |
| async_dispatcher_t* dispatcher) { |
| file_->Clone(flags, parent_flags, std::move(request), dispatcher); |
| } |
| |
| void PseudoFile::Content::SendOnOpenEvent(zx_status_t status) { |
| // not needed as underlying connection should do this |
| } |
| |
| } // namespace vfs |