[inspect] Add snapshot wrapper.
This change adds a new Snapshot object that handles reading a copy of a
Inspect VMO from a read-only VMO. By default, it verifies the included
header and generation count to load a consistent view of the VMO buffer.
TEST=runtests -t inspect-test
CF-216: #progress
Change-Id: I06107999232eac6032836ed181bddd632db1b509
diff --git a/system/ulib/inspect/include/lib/inspect/snapshot.h b/system/ulib/inspect/include/lib/inspect/snapshot.h
new file mode 100644
index 0000000..f1b5923
--- /dev/null
+++ b/system/ulib/inspect/include/lib/inspect/snapshot.h
@@ -0,0 +1,93 @@
+// 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.
+
+#pragma once
+
+#include <fbl/array.h>
+#include <fbl/function.h>
+#include <lib/inspect/block.h>
+#include <lib/zx/vmo.h>
+#include <unistd.h>
+#include <zircon/types.h>
+
+namespace inspect {
+
+// |Snapshot| parses an incoming VMO buffer and produces a snapshot of
+// the VMO contents. |Snapshot::Options| determines the behavior of
+// snapshotting if a concurrent write potentially occurred.
+//
+// Example:
+// fbl::unique_ptr<Snapshot> snapshot;
+// zx_status_t status = Snapshot::Create(std::move(vmo),
+// {.read_attempts = 1024, .skip_consistency_check = false},
+// &snapshot);
+//
+// Test Example:
+// zx_status_t status = Snapshot::Create(std::move(vmo),
+// {.read_attempts = 1024, .skip_consistency_check = false},
+// fbl::make_unique<TestCallback>(),
+// &snapshot);
+class Snapshot final {
+public:
+ struct Options {
+ // The number of attempts to read a consistent snapshot.
+ // Reading fails if the number of attempts exceeds this number.
+ int read_attempts;
+
+ // If true, skip checking the buffer for consistency.
+ bool skip_consistency_check;
+ };
+
+ // Type for observing reads on the VMO.
+ using ReadObserver = fbl::Function<void(uint8_t* buffer, size_t buffer_size)>;
+
+ // By default, ensure consistency of the incoming Inspect VMO and retry up to
+ // 1024 times.
+ static constexpr Options kDefaultOptions = {.read_attempts = 1024,
+ .skip_consistency_check = false};
+
+ // Create a new snapshot of the given VMO and default options.
+ static zx_status_t Create(zx::vmo vmo, Snapshot* out_snapshot);
+
+ // Create a new snapshot of the given VMO and given options.
+ static zx_status_t Create(zx::vmo vmo, Options options, Snapshot* out_snapshot);
+
+ // Create a new snapshot of the given VMO, given options, and the given read observer
+ // for observing snapshot operations.
+ static zx_status_t Create(zx::vmo vmo, Options options, ReadObserver read_observer,
+ Snapshot* out_snapshot);
+
+ Snapshot() = default;
+ ~Snapshot() = default;
+ Snapshot(Snapshot&&) = default;
+ Snapshot& operator=(Snapshot&&) = default;
+
+ operator bool() const { return buffer_.size() > 0; }
+
+ // Returns the start of the snapshot data, if valid.
+ const uint8_t* data() const { return buffer_.begin(); }
+
+ // Returns the size of the snapshot, if valid.
+ size_t size() const { return buffer_.size(); }
+
+ // Get a pointer to a block in the buffer by index.
+ // Returns nullptr if the index is out of bounds.
+ internal::Block* GetBlock(internal::BlockIndex index) const;
+
+private:
+ // Read from the VMO into a buffer.
+ static zx_status_t Read(zx::vmo& vmo, size_t size, uint8_t* buffer);
+
+ // Parse the header from a buffer and obtain the generation count.
+ static zx_status_t ParseHeader(uint8_t* buffer, uint64_t* out_generation_count);
+
+ // Take a new snapshot of the VMO with default options.
+ // If reading fails, the boolean value of the constructed |Snapshot| will be false.
+ explicit Snapshot(fbl::Array<uint8_t> buffer);
+
+ // The buffer storing the snapshot.
+ fbl::Array<uint8_t> buffer_;
+};
+
+} // namespace inspect
diff --git a/system/ulib/inspect/rules.mk b/system/ulib/inspect/rules.mk
index dd92fb8..102ec0e 100644
--- a/system/ulib/inspect/rules.mk
+++ b/system/ulib/inspect/rules.mk
@@ -13,6 +13,7 @@
MODULE_SRCS += \
$(LOCAL_DIR)/heap.cpp \
$(LOCAL_DIR)/scanner.cpp \
+ $(LOCAL_DIR)/snapshot.cpp \
MODULE_HEADER_DEPS := \
system/ulib/zircon-internal \
@@ -40,6 +41,7 @@
$(TEST_DIR)/main.cpp \
$(TEST_DIR)/heap_tests.cpp \
$(TEST_DIR)/scanner_tests.cpp \
+ $(TEST_DIR)/snapshot_tests.cpp \
MODULE_STATIC_LIBS := \
system/ulib/inspect \
diff --git a/system/ulib/inspect/snapshot.cpp b/system/ulib/inspect/snapshot.cpp
new file mode 100644
index 0000000..bb9cb7a
--- /dev/null
+++ b/system/ulib/inspect/snapshot.cpp
@@ -0,0 +1,114 @@
+// 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/inspect/block.h>
+#include <lib/inspect/snapshot.h>
+
+namespace inspect {
+
+using internal::Block;
+
+Snapshot::Snapshot(fbl::Array<uint8_t> buffer) : buffer_(std::move(buffer)) {}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Snapshot* out_snapshot) {
+ return Snapshot::Create(std::move(vmo), kDefaultOptions, out_snapshot);
+}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Options options, Snapshot* out_snapshot) {
+ return Snapshot::Create(std::move(vmo), std::move(options), nullptr, out_snapshot);
+}
+
+zx_status_t Snapshot::Create(zx::vmo vmo, Options options, ReadObserver read_observer,
+ Snapshot* out_snapshot) {
+ size_t tries_left = options.read_attempts;
+
+ zx_status_t status;
+ fbl::Array<uint8_t> buffer;
+
+ while (tries_left-- > 0) {
+ size_t size;
+ status = vmo.get_size(&size);
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (size < sizeof(internal::Block)) {
+ return ZX_ERR_OUT_OF_RANGE;
+ }
+ if (buffer.size() != size) {
+ buffer.reset(new uint8_t[size], size);
+ }
+
+ status = Snapshot::Read(vmo, sizeof(internal::Block), buffer.begin());
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (read_observer) {
+ read_observer(buffer.begin(), sizeof(internal::Block));
+ }
+
+ uint64_t generation;
+ status = Snapshot::ParseHeader(buffer.begin(), &generation);
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ if (!options.skip_consistency_check && generation % 2 != 0) {
+ continue;
+ }
+
+ status = Snapshot::Read(vmo, size, buffer.begin());
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (read_observer) {
+ read_observer(buffer.begin(), sizeof(size));
+ }
+
+ uint64_t new_generation;
+ status = Snapshot::ParseHeader(buffer.begin(), &new_generation);
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (!options.skip_consistency_check && generation != new_generation) {
+ continue;
+ }
+
+ size_t new_size;
+ if (vmo.get_size(&new_size) != ZX_OK) {
+ return ZX_ERR_INTERNAL;
+ }
+ if (new_size != size) {
+ continue;
+ }
+
+ *out_snapshot = Snapshot(std::move(buffer));
+
+ return ZX_OK;
+ }
+
+ return ZX_ERR_INTERNAL;
+}
+
+zx_status_t Snapshot::Read(zx::vmo& vmo, size_t size, uint8_t* buffer) {
+ memset(buffer, 0, size);
+ return vmo.read(buffer, 0, size);
+}
+
+zx_status_t Snapshot::ParseHeader(uint8_t* buffer, uint64_t* out_generation_count) {
+ Block* block = reinterpret_cast<Block*>(buffer);
+ if (memcmp(&block->header_data[4], kMagicNumber, 4) != 0) {
+ return ZX_ERR_INTERNAL;
+ }
+ *out_generation_count = block->payload.u64;
+ return ZX_OK;
+}
+
+internal::Block* Snapshot::GetBlock(internal::BlockIndex index) const {
+ if (index >= IndexForOffset(buffer_.size())) {
+ return nullptr;
+ }
+ return reinterpret_cast<internal::Block*>(buffer_.begin() + index * kMinOrderSize);
+}
+
+} // namespace inspect
diff --git a/system/ulib/inspect/test/snapshot_tests.cpp b/system/ulib/inspect/test/snapshot_tests.cpp
new file mode 100644
index 0000000..c88b142
--- /dev/null
+++ b/system/ulib/inspect/test/snapshot_tests.cpp
@@ -0,0 +1,194 @@
+// Copyright 2018 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/fzl/owned-vmo-mapper.h>
+#include <lib/inspect/block.h>
+#include <lib/inspect/snapshot.h>
+#include <unittest/unittest.h>
+
+namespace {
+
+using inspect::BlockType;
+using inspect::Snapshot;
+using inspect::internal::Block;
+using inspect::internal::HeaderBlockFields;
+
+bool ValidRead() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ memset(vmo.start(), 'a', 4096);
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ memcpy(&header->header_data[4], inspect::kMagicNumber, 4);
+ header->payload.u64 = 0;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+ EXPECT_EQ(ZX_OK, status);
+ EXPECT_EQ(4096, snapshot.size());
+
+ // Make sure that the data was actually fully copied to the snapshot.
+ uint8_t buf[snapshot.size() - sizeof(Block)];
+ memset(buf, 'a', snapshot.size() - sizeof(Block));
+ EXPECT_EQ(0, memcmp(snapshot.data() + sizeof(Block), buf, snapshot.size() - sizeof(Block)));
+
+ END_TEST;
+}
+
+bool InvalidWritePending() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ memcpy(&header->header_data[4], inspect::kMagicNumber, 4);
+ header->payload.u64 = 1;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+ EXPECT_EQ(ZX_ERR_INTERNAL, status);
+ EXPECT_FALSE(snapshot);
+
+ END_TEST;
+}
+
+bool ValidPendingSkipCheck() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ memcpy(&header->header_data[4], inspect::kMagicNumber, 4);
+ header->payload.u64 = 1;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(
+ std::move(dup), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
+ EXPECT_EQ(ZX_OK, status);
+ EXPECT_EQ(4096, snapshot.size());
+
+ END_TEST;
+}
+
+bool InvalidGenerationChange() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ memcpy(&header->header_data[4], inspect::kMagicNumber, 4);
+ header->payload.u64 = 0;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(
+ std::move(dup), Snapshot::kDefaultOptions,
+ [header] (uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
+
+ EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+ END_TEST;
+}
+
+bool ValidGenerationChangeSkipCheck() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ memcpy(&header->header_data[4], inspect::kMagicNumber, 4);
+ header->payload.u64 = 0;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(
+ std::move(dup), {.read_attempts = 100, .skip_consistency_check = true},
+ [header] (uint8_t* buffer, size_t buffer_size) { header->payload.u64 += 2; }, &snapshot);
+
+ EXPECT_EQ(ZX_OK, status);
+ EXPECT_EQ(4096, snapshot.size());
+
+ END_TEST;
+}
+
+bool InvalidBadMagicNumber() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ header->payload.u64 = 0;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(std::move(dup), &snapshot);
+
+ EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+ END_TEST;
+}
+
+bool InvalidBadMagicNumberSkipCheck() {
+ BEGIN_TEST;
+
+ fzl::OwnedVmoMapper vmo;
+ ASSERT_EQ(ZX_OK, vmo.CreateAndMap(4096, "test"));
+ Block* header = reinterpret_cast<Block*>(vmo.start());
+ header->header = HeaderBlockFields::Order::Make(0) |
+ HeaderBlockFields::Type::Make(BlockType::kHeader) |
+ HeaderBlockFields::Version::Make(0);
+ header->payload.u64 = 0;
+
+ zx::vmo dup;
+ ASSERT_EQ(ZX_OK, vmo.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
+ Snapshot snapshot;
+ zx_status_t status = Snapshot::Create(
+ std::move(dup), {.read_attempts = 100, .skip_consistency_check = true}, &snapshot);
+
+ EXPECT_EQ(ZX_ERR_INTERNAL, status);
+
+ END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(SnapshotTests)
+RUN_TEST(ValidRead)
+RUN_TEST(InvalidWritePending)
+RUN_TEST(ValidPendingSkipCheck)
+RUN_TEST(InvalidGenerationChange)
+RUN_TEST(ValidGenerationChangeSkipCheck)
+RUN_TEST(InvalidBadMagicNumber)
+RUN_TEST(InvalidBadMagicNumberSkipCheck)
+END_TEST_CASE(SnapshotTests)