| // Copyright 2020 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_MINFS_BUFFER_VIEW_H_ |
| #define SRC_STORAGE_MINFS_BUFFER_VIEW_H_ |
| |
| #include <lib/fit/function.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <variant> |
| |
| #include <storage/buffer/block_buffer.h> |
| |
| #include "src/storage/minfs/block_utils.h" |
| |
| namespace minfs { |
| |
| // Wraps either a regular pointer or a BlockBuffer. This exists because the mapped address for a |
| // storage::BlockBuffer isn't stable. In particular, a BlockBuffer that happens to be a resizeable |
| // VMO, can have its mapping change when it grows. When that happens, we don't want a BufferView to |
| // be invalidated, so we wrap a BlockBuffer and always call through to get the current mapped |
| // address. |
| class BufferPtr { |
| public: |
| BufferPtr() : ptr_(std::in_place_index<0>, nullptr) {} |
| |
| BufferPtr(const BufferPtr&) = default; |
| BufferPtr& operator=(const BufferPtr&) = default; |
| |
| static BufferPtr FromMemory(void* buffer) { |
| return BufferPtr(Ptr(std::in_place_index<0>, buffer)); |
| } |
| |
| static BufferPtr FromBlockBuffer(storage::BlockBuffer* buffer) { |
| return BufferPtr(Ptr(std::in_place_index<1>, buffer)); |
| } |
| |
| void* get() const { |
| if (std::holds_alternative<void*>(ptr_)) { |
| return std::get<void*>(ptr_); |
| } else { |
| return std::get<storage::BlockBuffer*>(ptr_)->Data(0); |
| } |
| } |
| |
| private: |
| using Ptr = std::variant<void*, storage::BlockBuffer*>; |
| |
| explicit BufferPtr(Ptr ptr) : ptr_(ptr) {} |
| |
| std::variant<void*, storage::BlockBuffer*> ptr_; |
| }; |
| |
| // BaseBufferView and BufferView are views of a buffer, a contiguous range in memory. It can be |
| // mutable or immutable. It keeps track of the use of mutable methods to record whether or not it is |
| // dirty. A flusher object is provided for flushing the buffer and is called via the Flush method if |
| // the buffer is deemed dirty. If no flusher is provided, the view is considered immutable. The |
| // underlying buffer can be memory, or it can be a BlockBuffer which we specialise for, in case |
| // BlockBuffer is resized, in which case its mapped address can change. |
| class BaseBufferView { |
| public: |
| using Flusher = fit::function<zx::result<>(BaseBufferView* view)>; |
| |
| BaseBufferView() = default; |
| |
| explicit BaseBufferView(BufferPtr buffer, size_t offset, size_t length) |
| : buffer_(buffer), offset_(offset), length_(length) {} |
| explicit BaseBufferView(BufferPtr buffer, size_t offset, size_t length, Flusher flusher) |
| : buffer_(buffer), offset_(offset), length_(length), flusher_(std::move(flusher)) {} |
| |
| // Movable, but not copyable. |
| BaseBufferView(BaseBufferView&& other) noexcept { *this = std::move(other); } |
| BaseBufferView& operator=(BaseBufferView&& other) noexcept; |
| |
| ~BaseBufferView(); |
| |
| bool IsValid() const { return data() != nullptr; } |
| size_t length() const { return length_; } |
| size_t offset() const { return offset_; } |
| ByteRange GetByteRange() const { return ByteRange(offset_, offset_ + length_); } |
| bool dirty() const { return dirty_; } |
| void set_dirty(bool v) { |
| ZX_ASSERT(data() != nullptr); |
| ZX_ASSERT(flusher_); |
| dirty_ = v; |
| } |
| |
| // Does nothing if the buffer is not dirty. The buffer is always marked clean after calling flush; |
| // it is up to the caller to handle errors appropriately. |
| [[nodiscard]] zx::result<> Flush(); |
| |
| protected: |
| // N.B. Take care with the 'as' methods and alignment. On some architectures, unaligned access is |
| // a problem, so if you're trying to access, say, a uint32_t at offset 5, you'll have an issue. |
| |
| // Returns const T&. |
| template <typename T> |
| const T& as() const { |
| ZX_ASSERT(data() != nullptr); |
| ZX_ASSERT(sizeof(T) <= length_); |
| return *reinterpret_cast<T*>(data()); |
| } |
| |
| // Returns T&. |
| template <typename T> |
| T& as_mut() { |
| ZX_ASSERT(data() != nullptr); |
| ZX_ASSERT(sizeof(T) <= length_); |
| ZX_ASSERT(flusher_); |
| dirty_ = true; |
| return *reinterpret_cast<T*>(data()); |
| } |
| |
| private: |
| void* data() const { return static_cast<uint8_t*>(buffer_.get()) + offset_; } |
| |
| BufferPtr buffer_; |
| size_t offset_ = 0; |
| size_t length_ = 0; |
| bool dirty_ = false; |
| Flusher flusher_; |
| }; |
| |
| // BufferView is a typed version of BaseBufferView which will make it appear to be an array of |
| // objects of type T. |
| template <typename T> |
| class BufferView : public BaseBufferView { |
| public: |
| BufferView() = default; |
| |
| // |buffer| needs to be aligned sufficiently for T. |
| BufferView(BufferPtr buffer, size_t index, size_t count) |
| : BaseBufferView(buffer, sizeof(T) * index, sizeof(T) * count) {} |
| BufferView(BufferPtr buffer, size_t index, size_t count, Flusher flusher) |
| : BaseBufferView(buffer, sizeof(T) * index, sizeof(T) * count, std::move(flusher)) {} |
| |
| // Movable, but not copyable. |
| BufferView(BufferView&& other) = default; |
| BufferView& operator=(BufferView&& other) = default; |
| |
| const T* data() const { return &as<T>(); } |
| size_t count() const { return length() / sizeof(T); } |
| |
| // Non mutating accessors. |
| const T& operator*() const { return as<T>(); } |
| const T& operator[](size_t index) const { |
| ZX_ASSERT(index < count()); |
| return (&as<T>())[index]; |
| } |
| |
| // Mutating accessors. |
| T& mut_ref() { return as_mut<T>(); } |
| T& mut_ref(size_t index) { |
| ZX_ASSERT(index < count()); |
| return (&as_mut<T>())[index]; |
| } |
| }; |
| |
| } // namespace minfs |
| |
| #endif // SRC_STORAGE_MINFS_BUFFER_VIEW_H_ |