blob: ac7d288f808453d15ba5878df404c4384ac1fe3c [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.
#ifndef SRC_STORAGE_LIB_VFS_CPP_PSEUDO_FILE_H_
#define SRC_STORAGE_LIB_VFS_CPP_PSEUDO_FILE_H_
#include <lib/fit/function.h>
#include <zircon/types.h>
#include <cstddef>
#include <string_view>
#include <fbl/macros.h>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include "vnode.h"
namespace fs {
// A pseudo-file is a file-like object whose content is generated and modified dynamically
// on-the-fly by invoking handler functions rather than being directly persisted as a sequence of
// bytes.
//
// This class is designed to allow programs to publish read-only, write-only, or read-write
// properties such as configuration options, debug flags, and dumps of internal state which may
// change dynamically.
//
// A pseudo-file is readable when it has a non-null |ReadHandler|. Typically the read handler will
// output a UTF-8 representation of some element of the program's state, or return an error if the
// requested information is not available. The read handler is not expected to have side-effects
// (but it can).
//
// A pseudo-file is writable when it has a non-null |WriteHandler|. Typically the write handler
// will parse the input in a UTF-8 representation and update the program's state in response, or
// return an error if the input is invalid.
//
// Although pseudo-files usually contain text, they can also be used for binary data.
//
// There is no guarantee that data written to the pseudo-file can be read back from the pseudo-file
// in the same form; it's not a real file after all.
//
// This is an abstract class. The concrete implementations are |BufferedPseudoFile| and
// |UnbufferedPseudoFile|.
class PseudoFile : public Vnode {
public:
// Handler called to read from the pseudo-file.
using ReadHandler = fit::function<zx_status_t(fbl::String* output)>;
// Handler called to write into the pseudo-file.
using WriteHandler = fit::function<zx_status_t(std::string_view input)>;
~PseudoFile() override;
// |Vnode| implementation:
fuchsia_io::NodeProtocolKinds GetProtocols() const final;
bool ValidateRights(fuchsia_io::Rights rights) const override;
zx::result<fs::VnodeAttributes> GetAttributes() const final;
protected:
PseudoFile(ReadHandler read_handler, WriteHandler write_handler);
ReadHandler const read_handler_;
WriteHandler const write_handler_;
DISALLOW_COPY_ASSIGN_AND_MOVE(PseudoFile);
};
// Buffered pseudo-file.
//
// This variant is optimized for incrementally reading and writing properties which are larger than
// can typically be read or written by the client in a single I/O transaction.
//
// In read mode, the pseudo-file invokes its read handler when the file is opened and retains the
// content in an output buffer which the client incrementally reads from and can seek within.
//
// In write mode, the client incrementally writes into and seeks within an input buffer which the
// pseudo-file delivers as a whole to the write handler when the file is closed. Truncation is also
// supported.
//
// Each client has its own separate output and input buffers. Writing into the output buffer does
// not affect the contents of the client's input buffer or that of any other client. Changes to the
// underlying state of the pseudo-file are not observed by the client until it closes and re-opens
// the file.
//
// This class is thread-safe.
class BufferedPseudoFile : public PseudoFile {
public:
// Construct with fbl::MakeRefCounted.
private:
friend fbl::internal::MakeRefCountedHelper<BufferedPseudoFile>;
friend fbl::RefPtr<BufferedPseudoFile>;
class Content final : public Vnode {
public:
// |Vnode| implementation:
fuchsia_io::NodeProtocolKinds GetProtocols() const final;
zx::result<fs::VnodeAttributes> GetAttributes() const final;
zx_status_t Read(void* data, size_t length, size_t offset, size_t* out_actual) final;
zx_status_t Write(const void* data, size_t length, size_t offset, size_t* out_actual) final;
zx_status_t Append(const void* data, size_t length, size_t* out_end, size_t* out_actual) final;
zx_status_t Truncate(size_t length) final;
private:
friend fbl::internal::MakeRefCountedHelper<Content>;
friend fbl::RefPtr<Content>;
Content(fbl::RefPtr<BufferedPseudoFile> file, fbl::String output);
~Content() override;
// Vnode protected implementation:
zx_status_t CloseNode() final;
void SetInputLength(size_t length);
fbl::RefPtr<BufferedPseudoFile> const file_;
fbl::String const output_;
char* input_data_ = nullptr;
size_t input_length_ = 0u;
};
// Creates a buffered pseudo-file.
//
// If the |read_handler| is null, then the pseudo-file is considered not readable. If the
// |write_handler| is null, then the pseudo-file is considered not writable. The
// |input_buffer_capacity| determines the maximum number of bytes which can be written to the
// pseudo-file's input buffer when it it opened for writing.
explicit BufferedPseudoFile(ReadHandler read_handler = nullptr,
WriteHandler write_handler = nullptr,
size_t input_buffer_capacity = 1024);
~BufferedPseudoFile() override;
// |Vnode| protected implementation:
zx_status_t OpenNode(fbl::RefPtr<Vnode>* out_redirect) final;
size_t const input_buffer_capacity_;
DISALLOW_COPY_ASSIGN_AND_MOVE(BufferedPseudoFile);
};
// Unbuffered pseudo-file.
//
// This variant is optimized for atomically reading and writing small properties. Unlike buffered
// pseudo-files, it is not necessary to re-open the pseudo-file to observe side-effects; the client
// can simply seek back to the zero offset and read or write again.
//
// Because reads and writes are not buffered, the maximum size of the property is limited to what
// will fit in a single I/O transaction. Unbuffered pseudo-files generally work best for properties
// which are likely to be polled or repeatedly modified and which are no larger than the nominal I/O
// buffer size used by the intended clients.
//
// As a conservative guideline, we recommend using |BufferedPseudoFile| instead for content larger
// than the system page size.
//
// In read mode, the pseudo-file invokes its read handler each time |Read()| is called with a seek
// offset of 0, returning at most as many bytes as the client requested and discarding the remainder
// (if any).
//
// Reading with a non-zero seek offset returns empty data, indicating end of file.
//
// In write mode, the pseudo-file invokes its write handler each time |Write()| with a seek offset
// of 0 is called, passing all of the bytes written by the client as the input string. Likewise,
// |Append()| invokes the write handler each time it is called and returns a new end of file offset
// of 0.
//
// Writing with a non-zero seek offset returns |ZX_ERR_NO_SPACE|, indicating an attempt to write
// data beyond what was accepted by the write handler.
//
// Truncating the length to zero and then closing it without an intervening write is equivalent to
// writing 0 bytes. Truncating to a non-zero length returns |ZX_ERR_INVALID_ARGS|.
//
// This class is thread-safe.
class UnbufferedPseudoFile : public PseudoFile {
public:
// Construct with fbl::MakeRefCounted.
private:
friend fbl::internal::MakeRefCountedHelper<UnbufferedPseudoFile>;
friend fbl::RefPtr<UnbufferedPseudoFile>;
class Content final : public Vnode {
public:
// |Vnode| implementation:
fuchsia_io::NodeProtocolKinds GetProtocols() const final;
zx::result<fs::VnodeAttributes> GetAttributes() const final;
zx_status_t Read(void* data, size_t length, size_t offset, size_t* out_actual) final;
zx_status_t Write(const void* data, size_t length, size_t offset, size_t* out_actual) final;
zx_status_t Append(const void* data, size_t length, size_t* out_end, size_t* out_actual) final;
zx_status_t Truncate(size_t length) final;
private:
friend fbl::internal::MakeRefCountedHelper<Content>;
friend fbl::RefPtr<Content>;
explicit Content(fbl::RefPtr<UnbufferedPseudoFile> file);
~Content() override;
// Vnode protected implementation.
zx_status_t OpenNode(fbl::RefPtr<Vnode>* out_redirect) final;
zx_status_t CloseNode() final;
fbl::RefPtr<UnbufferedPseudoFile> const file_;
bool truncated_since_last_successful_write_;
};
// Creates an unbuffered pseudo-file.
//
// If the |read_handler| is null, then the pseudo-file is considered not readable.
// If the |write_handler| is null, then the pseudo-file is considered not writable.
explicit UnbufferedPseudoFile(ReadHandler read_handler = nullptr,
WriteHandler write_handler = nullptr);
~UnbufferedPseudoFile() override;
// |Vnode| protected implementation:
zx_status_t OpenNode(fbl::RefPtr<Vnode>* out_redirect) final;
DISALLOW_COPY_ASSIGN_AND_MOVE(UnbufferedPseudoFile);
};
} // namespace fs
#endif // SRC_STORAGE_LIB_VFS_CPP_PSEUDO_FILE_H_