blob: 8177d5523a0ddda841a8bf8fed4e7b17b94b40b4 [file] [log] [blame]
// Copyright 2022 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 <assert.h>
#include <lib/zx/pager.h>
#include <lib/zx/stream.h>
#include <lib/zx/vmo.h>
#include <zxtest/zxtest.h>
#include "helpers.h"
namespace {
// Tests that a reference can see parent writes and vice versa.
TEST(VmoReference, Write) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Write to the parent.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
// The reference should see the write.
uint8_t buf;
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
// Write to the reference.
data = 0xbb;
ASSERT_OK(ref.write(&data, 0, sizeof(data)));
// The parent should see the write.
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
}
// Tests that the ZERO_CHILDREN signal is activated on reference creation.
TEST(VmoReference, ZeroChildren) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Currently the parent has no children, so ZX_VMO_ZERO_CHILDREN should be set.
zx_signals_t pending;
ASSERT_OK(vmo.wait_one(ZX_VMO_ZERO_CHILDREN, zx::time::infinite(), &pending));
ASSERT_EQ(pending & ZX_VMO_ZERO_CHILDREN, ZX_VMO_ZERO_CHILDREN);
// Create a reference.
zx::vmo child;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &child));
// Currently the parent has one child, so ZX_VMO_ZERO_CHILDREN should be
// cleared. Since child VMO creation is synchronous, this signal must already
// be clear.
ASSERT_EQ(ZX_ERR_TIMED_OUT,
vmo.wait_one(ZX_VMO_ZERO_CHILDREN, zx::time::infinite_past(), &pending));
ASSERT_EQ(pending & ZX_VMO_ZERO_CHILDREN, 0);
// Close the child reference.
child.reset();
// Closing the child doesn't strictly guarantee that ZX_VMO_ZERO_CHILDREN is set
// immediately, but it should be set very soon if not already.
ASSERT_OK(vmo.wait_one(ZX_VMO_ZERO_CHILDREN, zx::time::infinite(), &pending));
ASSERT_EQ(pending & ZX_VMO_ZERO_CHILDREN, ZX_VMO_ZERO_CHILDREN);
}
// Tests SNAPSHOT clone creation against a reference.
TEST(VmoReference, ChildSnapshot) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Create a child of the reference.
zx::vmo child;
ASSERT_OK(ref.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, zx_system_get_page_size(), &child));
zx_info_vmo_t parent_info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
zx_info_vmo_t child_info;
ASSERT_OK(child.get_info(ZX_INFO_VMO, &child_info, sizeof(child_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, child_info.parent_koid);
// The child should logically be a snapshot child of the root vmo.
uint8_t buf;
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
// Write the page in the root again.
uint8_t new_data = 0xbb;
ASSERT_OK(vmo.write(&new_data, 0, sizeof(new_data)));
// The new write should not be visible to the child.
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
// Write the page in the child.
data = 0xcc;
ASSERT_OK(child.write(&data, 0, sizeof(data)));
// The write should not be visible in the root.
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
// Verify contents of both children.
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
}
// Tests SNAPSHOT_AT_LEAST_ON_WRITE creation against a reference.
TEST(VmoReference, ChildSnapshotAtLeastOnWrite) {
zx::vmo vmo;
zx::pager pager;
ASSERT_OK(zx::pager::create(0, &pager));
zx::port port;
ASSERT_OK(zx::port::create(0, &port));
ASSERT_OK(pager.create_vmo(0, port, 0, zx_system_get_page_size(), &vmo));
zx::vmo aux;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &aux));
ASSERT_OK(pager.supply_pages(vmo, 0, zx_system_get_page_size(), aux, 0));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Create a child of the reference.
zx::vmo child;
ASSERT_OK(ref.create_child(ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE, 0, zx_system_get_page_size(),
&child));
zx_info_vmo_t parent_info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
zx_info_vmo_t child_info;
ASSERT_OK(child.get_info(ZX_INFO_VMO, &child_info, sizeof(child_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, child_info.parent_koid);
// The child should logically be a CoW child of the root vmo.
uint8_t buf;
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
// Write the page in the root again.
uint8_t new_data = 0xbb;
ASSERT_OK(vmo.write(&new_data, 0, sizeof(new_data)));
// The new write should be visible to the child.
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
// Write the page in the child.
data = 0xcc;
ASSERT_OK(child.write(&data, 0, sizeof(data)));
// The write should not be visible in the root.
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
// Verify contents of both children.
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
}
// Tests SLICE creation against a reference.
TEST(VmoReference, ChildSlice) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Create a child of the reference.
zx::vmo child;
ASSERT_OK(ref.create_child(ZX_VMO_CHILD_SLICE, 0, zx_system_get_page_size(), &child));
zx_info_vmo_t parent_info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
zx_info_vmo_t child_info;
ASSERT_OK(child.get_info(ZX_INFO_VMO, &child_info, sizeof(child_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, child_info.parent_koid);
// The child should logically be a slice of the root vmo.
uint8_t buf;
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
// Write the page in the parent again.
uint8_t new_data = 0xbb;
ASSERT_OK(vmo.write(&new_data, 0, sizeof(new_data)));
// The new write should be visible to the child.
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
// Write the page in the child.
data = 0xcc;
ASSERT_OK(child.write(&data, 0, sizeof(data)));
// The write should be visible in the parent.
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
// Verify contents of both children.
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
ASSERT_OK(child.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
}
// Tests nested references.
TEST(VmoReference, NestedChild) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Create a reference of the reference.
zx::vmo child1;
ASSERT_OK(ref.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &child1));
zx_info_vmo_t parent_info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
zx_info_vmo_t child_info;
ASSERT_OK(child1.get_info(ZX_INFO_VMO, &child_info, sizeof(child_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, child_info.parent_koid);
// Create a reference of the nested reference.
zx::vmo child2;
ASSERT_OK(child1.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &child2));
ASSERT_OK(child1.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
ASSERT_OK(child2.get_info(ZX_INFO_VMO, &child_info, sizeof(child_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, child_info.parent_koid);
// Both nested children should logically be references of the root vmo.
uint8_t buf;
ASSERT_OK(child1.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
ASSERT_OK(child2.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
// Write the page in the parent again.
uint8_t new_data = 0xbb;
ASSERT_OK(vmo.write(&new_data, 0, sizeof(new_data)));
// The new write should be visible to the nested children.
ASSERT_OK(child1.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
ASSERT_OK(child2.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, new_data);
EXPECT_NE(buf, data);
// Write the page in the last level child.
data = 0xcc;
ASSERT_OK(child2.write(&data, 0, sizeof(data)));
// The write should be visible in the parent.
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
// Verify contents of all children.
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
ASSERT_OK(child1.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(buf, data);
EXPECT_NE(buf, new_data);
}
// Tests child creation against a reference that is unsupported for the parent.
TEST(VmoReference, UnsupportedChild) {
zx::vmo vmo;
zx::pager pager;
ASSERT_OK(zx::pager::create(0, &pager));
zx::port port;
ASSERT_OK(zx::port::create(0, &port));
ASSERT_OK(pager.create_vmo(0, port, 0, zx_system_get_page_size(), &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Creating a snapshot clone of the parent should fail.
zx::vmo child;
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED,
vmo.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, zx_system_get_page_size(), &child));
// Creating a snapshot clone of the reference should also fail.
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED,
vmo.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, zx_system_get_page_size(), &child));
}
// Tests updates to mappings created against a reference.
TEST(VmoReference, MappingUpdate) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Map the reference.
zx_vaddr_t ptr;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, ref, 0,
zx_system_get_page_size(), &ptr));
auto unmap = fit::defer([&]() {
// Cleanup the mapping we created.
zx::vmar::root_self()->unmap(ptr, zx_system_get_page_size());
});
// Can see initial contents through the mapping.
auto buf = reinterpret_cast<uint8_t*>(ptr);
EXPECT_EQ(data, *buf);
// Modify the VMO and verify the change is visible through the mapping.
data = 0xbb;
ASSERT_OK(ref.write(&data, 0, sizeof(data)));
EXPECT_EQ(data, *buf);
// Decommit the page.
ASSERT_OK(ref.op_range(ZX_VMO_OP_DECOMMIT, 0, zx_system_get_page_size(), nullptr, 0));
// Both the parent and the child should have no pages populated.
zx_info_vmo_t info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
ASSERT_OK(vmo.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The mapping should now read zeros.
EXPECT_EQ(0u, *buf);
}
// Tests attributed pages for references.
TEST(VmoReference, AttributedCounts) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Commit a page in the parent.
const uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
// The parent should see the page populated while the reference does not.
zx_info_vmo_t info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
ASSERT_OK(vmo.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(zx_system_get_page_size(), info.populated_bytes);
// The reference should see the page.
uint8_t buf;
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
// Drop the parent.
vmo.reset();
// Committed pages still not attributed to the reference.
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The reference can read the parent's page though.
buf = 0;
ASSERT_OK(ref.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
}
// Tests resizing a reference and resizing the parent.
TEST(VmoReference, Resize) {
const uint64_t kInitialSize = 4 * zx_system_get_page_size();
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(kInitialSize, ZX_VMO_RESIZABLE, &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE | ZX_VMO_CHILD_RESIZABLE, 0, 0, &ref));
// Resize the parent.
const uint64_t kVmoSize = 2 * zx_system_get_page_size();
const uint64_t kVmoContentSize = 2 * zx_system_get_page_size() - sizeof(uint64_t);
ASSERT_OK(vmo.set_size(kVmoContentSize));
uint64_t size;
ASSERT_OK(vmo.get_size(&size));
EXPECT_EQ(kVmoSize, size);
ASSERT_OK(vmo.get_prop_content_size(&size));
EXPECT_EQ(kVmoContentSize, size);
// The reference should see the resize.
ASSERT_OK(ref.get_size(&size));
EXPECT_EQ(kVmoSize, size);
ASSERT_OK(ref.get_prop_content_size(&size));
EXPECT_EQ(kVmoContentSize, size);
// Resize the reference.
const uint64_t kRefSize = 3 * zx_system_get_page_size();
const uint64_t kRefContentSize = 3 * zx_system_get_page_size() - sizeof(uint64_t);
ASSERT_OK(ref.set_size(kRefContentSize));
ASSERT_OK(ref.get_size(&size));
EXPECT_EQ(kRefSize, size);
ASSERT_OK(ref.get_prop_content_size(&size));
EXPECT_EQ(kRefContentSize, size);
// The parent should see the resize.
ASSERT_OK(vmo.get_size(&size));
EXPECT_EQ(kRefSize, size);
ASSERT_OK(vmo.get_prop_content_size(&size));
EXPECT_EQ(kRefContentSize, size);
}
// Tests resizing using a non-resizable reference handle.
TEST(VmoReference, UnsupportedResize) {
const uint64_t kInitialSize = 4 * zx_system_get_page_size();
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(kInitialSize, ZX_VMO_RESIZABLE, &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Cannot resize a non-resizable reference.
ASSERT_EQ(ZX_ERR_ACCESS_DENIED, ref.set_size(zx_system_get_page_size()));
// The reference should still see the parent's resize.
const uint64_t kVmoSize = 2 * zx_system_get_page_size();
const uint64_t kVmoContentSize = 2 * zx_system_get_page_size() - sizeof(uint64_t);
ASSERT_OK(vmo.set_size(kVmoContentSize));
uint64_t size;
ASSERT_OK(vmo.get_size(&size));
EXPECT_EQ(kVmoSize, size);
ASSERT_OK(vmo.get_prop_content_size(&size));
EXPECT_EQ(kVmoContentSize, size);
// The reference should see the resize.
ASSERT_OK(ref.get_size(&size));
EXPECT_EQ(kVmoSize, size);
ASSERT_OK(ref.get_prop_content_size(&size));
EXPECT_EQ(kVmoContentSize, size);
// Cannot create a resizable reference of a non-resizable VMO.
zx::vmo noresize;
ASSERT_OK(zx::vmo::create(kInitialSize, 0, &noresize));
zx::vmo noresizeref;
ASSERT_EQ(
ZX_ERR_NOT_SUPPORTED,
noresize.create_child(ZX_VMO_CHILD_REFERENCE | ZX_VMO_CHILD_RESIZABLE, 0, 0, &noresizeref));
// Can create a non-resizable reference of a non-resizable VMO.
ASSERT_OK(noresize.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &noresizeref));
// Cannot resize a non-resizable reference of a non-resizable VMO.
ASSERT_EQ(ZX_ERR_UNAVAILABLE, noresize.set_size(zx_system_get_page_size()));
}
// Tests creating streams against references to the same VMO.
TEST(VmoReference, Streams) {
const uint64_t kVmoSize = zx_system_get_page_size();
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(kVmoSize, 0, &vmo));
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref1;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref1));
// Create a stream against a reference.
zx::stream stream1;
ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, ref1, 0, &stream1));
uint8_t buffer[1] = {};
zx_iovec_t vec = {
.buffer = buffer,
.capacity = sizeof(buffer),
};
// Should be able to read previously written VMO contents.
uint64_t actual = 0;
ASSERT_OK(stream1.readv(0, &vec, 1, &actual));
EXPECT_EQ(actual, vec.capacity);
EXPECT_EQ(data, buffer[0]);
zx::vmo ref2;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref2));
// Create a stream against a reference.
zx::stream stream2;
ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, ref2, 0, &stream2));
data = 0xbb;
buffer[0] = data;
actual = 0;
ASSERT_OK(stream2.writev(0, &vec, 1, &actual));
EXPECT_EQ(actual, vec.capacity);
// Should have been able to write to the parent VMO.
uint8_t buf;
ASSERT_OK(vmo.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
// The references should see the write too.
ASSERT_OK(ref1.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
ASSERT_OK(ref2.read(&buf, 0, sizeof(buf)));
EXPECT_EQ(data, buf);
// The other stream should see the write too.
ASSERT_OK(stream1.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr));
actual = 0;
ASSERT_OK(stream1.readv(0, &vec, 1, &actual));
EXPECT_EQ(actual, vec.capacity);
EXPECT_EQ(data, buffer[0]);
}
// Tests mapping update after dropping the reference.
TEST(VmoReference, MappingUpdateAfterDroppingRef) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Map the VMO.
zx_vaddr_t ptr;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0,
zx_system_get_page_size(), &ptr));
auto unmap = fit::defer([&]() {
// Cleanup the mapping we created.
zx::vmar::root_self()->unmap(ptr, zx_system_get_page_size());
});
// Can see initial contents through the mapping.
auto buf = reinterpret_cast<uint8_t*>(ptr);
EXPECT_EQ(data, *buf);
// Modify the VMO and verify the change is visible through the mapping.
data = 0xbb;
ASSERT_OK(ref.write(&data, 0, sizeof(data)));
EXPECT_EQ(data, *buf);
// Drop the reference.
ref.reset();
// Decommit the page.
ASSERT_OK(vmo.op_range(ZX_VMO_OP_DECOMMIT, 0, zx_system_get_page_size(), nullptr, 0));
zx_info_vmo_t info;
ASSERT_OK(vmo.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The mapping should now read zeros.
EXPECT_EQ(0u, *buf);
}
// Tests mapping update after dropping the parent.
TEST(VmoReference, MappingUpdateAfterDroppingParent) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Map the reference.
zx_vaddr_t ptr;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, ref, 0,
zx_system_get_page_size(), &ptr));
auto unmap = fit::defer([&]() {
// Cleanup the mapping we created.
zx::vmar::root_self()->unmap(ptr, zx_system_get_page_size());
});
// Can see initial contents through the mapping.
auto buf = reinterpret_cast<uint8_t*>(ptr);
EXPECT_EQ(data, *buf);
// Modify the VMO and verify the change is visible through the mapping.
data = 0xbb;
ASSERT_OK(ref.write(&data, 0, sizeof(data)));
EXPECT_EQ(data, *buf);
// Drop the parent.
vmo.reset();
// Decommit the page.
ASSERT_OK(ref.op_range(ZX_VMO_OP_DECOMMIT, 0, zx_system_get_page_size(), nullptr, 0));
zx_info_vmo_t info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The mapping should now read zeros.
EXPECT_EQ(0u, *buf);
}
// Tests mapping udpate after dropping a nested reference.
TEST(VmoReference, MappingUpdateNestedChildDestroy) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
// Create a nested reference.
zx::vmo nested;
ASSERT_OK(ref.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &nested));
// Map the nested reference.
zx_vaddr_t ptr;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, nested, 0,
zx_system_get_page_size(), &ptr));
auto unmap = fit::defer([&]() {
// Cleanup the mapping we created.
zx::vmar::root_self()->unmap(ptr, zx_system_get_page_size());
});
// Can see initial contents through the mapping.
auto buf = reinterpret_cast<uint8_t*>(ptr);
EXPECT_EQ(data, *buf);
// Modify the VMO and verify the change is visible through the mapping.
data = 0xbb;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
EXPECT_EQ(data, *buf);
// Drop the first level reference.
ref.reset();
// Decommit the page.
ASSERT_OK(vmo.op_range(ZX_VMO_OP_DECOMMIT, 0, zx_system_get_page_size(), nullptr, 0));
zx_info_vmo_t info;
ASSERT_OK(vmo.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The mapping should now read zeros.
EXPECT_EQ(0u, *buf);
}
// Tests mapping update after dropping the parent in a nested reference hierarchy.
TEST(VmoReference, MappingUpdateNestedRootDestroy) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
// Write a non-zero page.
uint8_t data = 0xaa;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
// Create two references.
zx::vmo ref1;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref1));
zx::vmo ref2;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref2));
// Create a nested reference.
zx::vmo nested;
ASSERT_OK(ref1.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &nested));
// Map the references.
zx_vaddr_t ptr1;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, ref1, 0,
zx_system_get_page_size(), &ptr1));
auto unmap1 = fit::defer([&]() {
// Cleanup the mappings we created.
zx::vmar::root_self()->unmap(ptr1, zx_system_get_page_size());
});
zx_vaddr_t ptr2;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, ref2, 0,
zx_system_get_page_size(), &ptr2));
auto unmap2 = fit::defer([&]() {
// Cleanup the mappings we created.
zx::vmar::root_self()->unmap(ptr2, zx_system_get_page_size());
});
// Map the nested reference.
zx_vaddr_t ptr3;
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, nested, 0,
zx_system_get_page_size(), &ptr3));
auto unmap3 = fit::defer([&]() {
// Cleanup the mappings we created.
zx::vmar::root_self()->unmap(ptr3, zx_system_get_page_size());
});
// Can see initial contents through all the mappings.
auto buf = reinterpret_cast<uint8_t*>(ptr1);
EXPECT_EQ(data, *buf);
buf = reinterpret_cast<uint8_t*>(ptr2);
EXPECT_EQ(data, *buf);
buf = reinterpret_cast<uint8_t*>(ptr3);
EXPECT_EQ(data, *buf);
// Modify the VMO and verify the change is visible through the mappings.
data = 0xbb;
ASSERT_OK(vmo.write(&data, 0, sizeof(data)));
buf = reinterpret_cast<uint8_t*>(ptr1);
EXPECT_EQ(data, *buf);
buf = reinterpret_cast<uint8_t*>(ptr2);
EXPECT_EQ(data, *buf);
buf = reinterpret_cast<uint8_t*>(ptr3);
EXPECT_EQ(data, *buf);
// Drop the root VMO.
vmo.reset();
// Decommit the page.
ASSERT_OK(nested.op_range(ZX_VMO_OP_DECOMMIT, 0, zx_system_get_page_size(), nullptr, 0));
zx_info_vmo_t info;
ASSERT_OK(nested.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
EXPECT_EQ(0u, info.populated_bytes);
// The mappings should now read zeros.
buf = reinterpret_cast<uint8_t*>(ptr1);
EXPECT_EQ(0u, *buf);
buf = reinterpret_cast<uint8_t*>(ptr2);
EXPECT_EQ(0u, *buf);
buf = reinterpret_cast<uint8_t*>(ptr3);
EXPECT_EQ(0u, *buf);
}
// Tests that a reference is not reported as a CoW child.
TEST(VmoReference, GetInfo) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo));
zx::vmo ref;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &ref));
zx_info_vmo_t parent_info;
ASSERT_OK(vmo.get_info(ZX_INFO_VMO, &parent_info, sizeof(parent_info), nullptr, nullptr));
zx_info_vmo_t ref_info;
ASSERT_OK(ref.get_info(ZX_INFO_VMO, &ref_info, sizeof(ref_info), nullptr, nullptr));
EXPECT_EQ(parent_info.koid, ref_info.parent_koid);
EXPECT_EQ(0u, ref_info.flags & ZX_INFO_VMO_IS_COW_CLONE);
}
} // namespace