blob: b4d21b891a6c6d9db16afcbed7e5bc81a47a5e85 [file]
// Copyright 2026 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.
use anyhow::{Context, Error, anyhow, bail, ensure};
use blob_pager_and_verifier::{BlobPagerAndVerifier, DeliveryQueueProcessor, TestVmoProvider};
use blob_writer::BlobWriter;
use block_client::BlockClient as _;
use delivery_blob::{CompressionMode, Type1Blob, Type3Blob};
use fidl::endpoints::{DiscoverableProtocolMarker as _, create_proxy};
use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions, VolumesMarker};
use fidl_fuchsia_fxfs::{BlobCreatorMarker, BlobReaderMarker};
use fidl_fuchsia_io as fio;
use fidl_fuchsia_storage_block as fblock;
use fidl_fuchsia_storage_mapping::MappingProviderMarker;
use fidl_fuchsia_storage_partitions as fpartitions;
use fs_management::filesystem::{
BlockConnector as _, DirBasedBlockConnector, Filesystem as FsManagementFilesystem,
};
use fs_management::{Fvm, Fxfs};
use fuchsia_async as fasync;
use fuchsia_component::client::{
Service, connect_to_protocol, connect_to_protocol_at_dir_root, connect_to_protocol_at_dir_svc,
};
use fuchsia_merkle::Hash;
use fuchsia_runtime::vmar_root_self;
use std::hint::black_box;
use std::slice::from_raw_parts;
use std::sync::Arc;
use storage_profile::{BlobMessage, Message as _};
use test_case::test_case;
use test_vmo_backed_block_server::{
InitialContents, VmoBackedServer, VmoBackedServerConnector, VmoBackedServerOptions,
};
struct TestBlob {
data: Vec<u8>,
delivery_data: Vec<u8>,
hash: Hash,
}
impl TestBlob {
fn new_uncompressed(data: Vec<u8>) -> Self {
let hash = fuchsia_merkle::root_from_slice(&data);
let delivery_data = Type1Blob::generate(&data, CompressionMode::Never);
Self { data, delivery_data, hash }
}
fn new_type1_compressed(data: Vec<u8>) -> Self {
let hash = fuchsia_merkle::root_from_slice(&data);
let delivery_data = Type1Blob::generate(&data, CompressionMode::Always);
Self { data, delivery_data, hash }
}
fn new_type3_compressed(data: Vec<u8>) -> Self {
let hash = fuchsia_merkle::root_from_slice(&data);
let delivery_data = Type3Blob::generate(&data, CompressionMode::Always);
Self { data, delivery_data, hash }
}
}
struct TestPartitionEnv {
partition: Arc<fpartitions::PartitionServiceProxy>,
serving: fs_management::filesystem::ServingMultiVolumeFilesystem,
volume: fs_management::filesystem::ServingVolume,
blob_creator: fidl_fuchsia_fxfs::BlobCreatorProxy,
blob_reader: fidl_fuchsia_fxfs::BlobReaderProxy,
mapping_provider: fidl_fuchsia_storage_mapping::MappingProviderProxy,
mapper_proxy: fblock::MapperProxy,
_gpt: Option<fs_management::filesystem::ServingMultiVolumeFilesystem>,
}
impl TestPartitionEnv {
async fn new() -> Result<Self, Error> {
Self::new_with_options(true).await
}
async fn new_with_options(use_driver_blob_paging: bool) -> Result<Self, Error> {
log::info!("Connecting to partition service...");
let (partition_service, partitions_manager, _gpt) = match (
Service::open(fpartitions::PartitionServiceMarker),
connect_to_protocol::<fpartitions::PartitionsManagerMarker>(),
) {
(Ok(service), Ok(manager))
if service.clone().enumerate().await.is_ok_and(|list| !list.is_empty()) =>
{
log::info!("Using system GPT partition service on block device.");
(service, manager, None)
}
_ => {
log::warn!(
"=============================================================================="
);
log::warn!(
"WARNING: No system GPT partition service found! \
Falling back to VmoBackedServer!"
);
log::warn!(
"=============================================================================="
);
let block_size = 4096u64;
let block_count = 256 * 1024 * 1024 / block_size;
let vmo = zx::Vmo::create(block_count * block_size)?;
let block_server = Arc::new(
VmoBackedServer::from_vmo(block_size as u32, vmo)
.context("Failed to create VmoBackedServer")?,
);
let gpt_fs = FsManagementFilesystem::new(
VmoBackedServerConnector::new(block_server),
fs_management::Gpt { ..fs_management::Gpt::dynamic_child() },
);
let serving = gpt_fs.serve_multi_volume().await.context("Failed to start GPT")?;
let partitions_admin =
fuchsia_component::client::connect_to_protocol_at_dir_root::<
fpartitions::PartitionsAdminMarker,
>(serving.exposed_dir())
.context("Failed to connect to PartitionsAdmin")?;
let num_blocks = block_matcher::DEFAULT_BENCHMARK_FVM_SIZE_BYTES / block_size;
let initial_partitions = vec![fpartitions::PartitionEntry {
name: fs_management::format::constants::BENCHMARK_FVM_VOLUME_NAME.to_string(),
type_guid: fblock::Guid {
value: fs_management::format::constants::BENCHMARK_FVM_TYPE_GUID,
},
instance_guid: fblock::Guid { value: [1u8; 16] },
start_block: 4,
num_blocks,
flags: 0,
}];
partitions_admin
.reset_partition_table(&initial_partitions)
.await
.context("FIDL error on reset_partition_table")?
.map_err(zx::Status::err_from_raw)
.context("Failed to reset partition table")?;
let partitions = Service::open_from_dir(
serving.exposed_dir(),
fpartitions::PartitionServiceMarker,
)
.context("Failed to open PartitionService from GPT")?;
let manager = fuchsia_component::client::connect_to_protocol_at_dir_root::<
fpartitions::PartitionsManagerMarker,
>(serving.exposed_dir())
.context("Failed to connect to PartitionsManager")?;
(partitions, manager, Some(serving))
}
};
let partition = Arc::new(
block_matcher::find_or_create_test_partition(
partition_service,
partitions_manager,
block_matcher::DEFAULT_BENCHMARK_FVM_SIZE_BYTES,
)
.await
.context("Failed to find or create test partition")?,
);
log::info!("Formatting Fxfs on test partition...");
let mut fs = FsManagementFilesystem::new(
partition.clone(),
fs_management::Fxfs { allow_type3_blobs: true, ..Default::default() },
);
fs.format().await.context("Failed to format Fxfs")?;
log::info!("Serving Fxfs multi-volume...");
let serving = fs.serve_multi_volume().await.context("Failed to serve Fxfs")?;
let volume = serving
.create_volume(
"blob",
CreateOptions::default(),
MountOptions {
as_blob: Some(true),
use_driver_blob_paging: Some(use_driver_blob_paging),
..Default::default()
},
)
.await
.context("Failed to create blob volume")?;
let blob_creator =
connect_to_protocol_at_dir_svc::<BlobCreatorMarker>(volume.exposed_dir())
.context("Failed to connect to BlobCreator")?;
let blob_reader = connect_to_protocol_at_dir_svc::<BlobReaderMarker>(volume.exposed_dir())
.context("Failed to connect to BlobReader")?;
let mapping_provider =
connect_to_protocol_at_dir_svc::<MappingProviderMarker>(volume.exposed_dir())
.context("Failed to connect to MappingProvider")?;
let mapper_proxy = partition.connect_to_mapper()?;
Ok(Self {
partition,
serving,
volume,
blob_creator,
blob_reader,
mapping_provider,
mapper_proxy,
_gpt,
})
}
async fn write_blob(&self, blob: &TestBlob) -> Result<(), Error> {
let writer_client_end = self
.blob_creator
.create(&blob.hash.into(), false)
.await
.context("FIDL error on BlobCreator.Create")?
.map_err(|e| anyhow!("CreateBlob error: {e:?}"))
.context("Failed to create blob")?;
let writer_proxy = writer_client_end.into_proxy();
let mut blob_writer = BlobWriter::create(writer_proxy, blob.delivery_data.len() as u64)
.await
.context("Failed to create BlobWriter")?;
blob_writer.write(&blob.delivery_data).await.context("Failed to write blob data")?;
Ok(())
}
async fn shutdown(self) -> Result<(), Error> {
self.serving.shutdown().await.context("Failed to shutdown Fxfs")?;
Ok(())
}
}
#[fuchsia::test(threads = 4)]
async fn test_mapper_no_verification_on_gpt_partition() -> Result<(), Error> {
let env = TestPartitionEnv::new().await?;
log::info!("Setting up Pager, Port, and Delivery Queue...");
let pager = Arc::new(zx::Pager::create(zx::PagerOptions::empty()).unwrap());
let port = zx::Port::create();
let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE).unwrap();
let vmo_provider = Arc::new(TestVmoProvider::new(
pager.clone(),
delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
));
let delivery_receiver = vmo_fifo::Receiver::<mapping::RawDeliveryCommand>::new(
delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
)
.context("Failed to create delivery queue receiver")?;
let _delivery_processor = DeliveryQueueProcessor::spawn(
delivery_receiver,
vmo_provider.clone(),
delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
)
.context("Failed to spawn DeliveryQueueProcessor")?;
log::info!("Opening mapping sessions...");
let (mapping_session_proxy, mapping_session_server) = fidl::endpoints::create_proxy();
let mapping_vmo = env
.mapping_provider
.open_session(mapping_session_server)
.await
.context("FIDL error on open_session")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open mapping session")?;
let (mapper_session_proxy, mapper_session_server) = fidl::endpoints::create_proxy();
env.mapper_proxy
.open_session(
mapper_session_server,
mapping_vmo,
Some(port.duplicate_handle(zx::Rights::SAME_RIGHTS)?),
Some(delivery_queue),
)
.await
.context("FIDL error on Mapper.OpenSession")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open mapper session on partition")?;
let test_blobs = vec![
TestBlob::new_uncompressed(b"Short uncompressed blob payload".to_vec()),
TestBlob::new_uncompressed(vec![0x42u8; 3500]),
TestBlob::new_uncompressed(vec![0x77u8; 32 * 1024]),
TestBlob::new_type1_compressed(vec![0xAAu8; 64 * 1024]),
TestBlob::new_type3_compressed(vec![0x55u8; 128 * 1024]),
];
for (idx, blob) in test_blobs.iter().enumerate() {
log::info!("Writing test blob {idx} (size: {} bytes)...", blob.data.len());
env.write_blob(blob).await?;
let key = (idx + 1) as u64;
log::info!("Opening blob {idx} (key {key}) in mapping session...");
let uncompressed_size = mapping_session_proxy
.open(key, blob.hash.as_bytes())
.await
.context("FIDL error on MappingSession.Open")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open blob in mapping session")?;
assert_eq!(uncompressed_size, blob.data.len() as u64);
log::info!("Creating paged VMO for blob {idx} (key {key})...");
let paged_vmo = pager
.create_vmo(zx::VmoOptions::empty(), &port, key, uncompressed_size)
.context("Failed to create paged VMO")?;
vmo_provider.register_vmo(key, paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?);
log::info!("Reading blob {idx} data back from paged VMO...");
let expected_len = blob.data.len();
let paged_vmo_reader = paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
let reader_thread = std::thread::spawn(move || {
let mut read_buf = vec![0u8; expected_len];
paged_vmo_reader.read(&mut read_buf, 0).expect("paged vmo read failed");
read_buf
});
let read_data = reader_thread.join().unwrap();
assert_eq!(&read_data, &blob.data, "Blob {idx} data readback mismatch!");
vmo_provider.unregister_vmo(key);
mapping_session_proxy
.close(key)
.await
.context("FIDL error on MappingSession.Close")?
.map_err(zx::Status::err_from_raw)
.context("Failed to close blob in mapping session")?;
log::info!("Blob {idx} verified and closed successfully.");
}
log::info!("Closing mapper session...");
mapper_session_proxy
.close()
.await
.context("FIDL error on MapperSession.Close")?
.map_err(zx::Status::err_from_raw)
.context("Failed to close mapper session")?;
env.shutdown().await?;
log::info!("All blobs read successfully without verification on GPT partition!");
Ok(())
}
#[fuchsia::test(threads = 4)]
async fn test_mapper_with_verification_on_gpt_partition() -> Result<(), Error> {
let env = TestPartitionEnv::new().await?;
log::info!("Initializing BlobPagerAndVerifier...");
let blob_pager = BlobPagerAndVerifier::new(&env.mapping_provider, &env.mapper_proxy)
.await
.context("Failed to create BlobPagerAndVerifier")?;
let test_blobs = vec![
TestBlob::new_uncompressed(b"Short uncompressed blob payload".to_vec()),
TestBlob::new_uncompressed(vec![0x42u8; 3500]),
TestBlob::new_uncompressed(vec![0x77u8; 32 * 1024]),
TestBlob::new_type1_compressed(vec![0xAAu8; 64 * 1024]),
TestBlob::new_type1_compressed(vec![0xBBu8; 200 * 1024]),
TestBlob::new_type3_compressed(vec![0x55u8; 128 * 1024]),
TestBlob::new_type3_compressed(vec![0x66u8; 300 * 1024]),
];
for (idx, blob) in test_blobs.iter().enumerate() {
log::info!("Writing verified test blob {idx} (size: {} bytes)...", blob.data.len());
env.write_blob(blob).await?;
log::info!("Creating verified paged VMO for blob {idx}...");
let root_hash = <[u8; 32]>::from(blob.hash);
let paged_vmo = blob_pager
.create_vmo(&root_hash)
.await
.context("Failed to create paged VMO via BlobPagerAndVerifier")?;
log::info!("Reading blob {idx} data back from verified paged VMO...");
let expected_len = blob.data.len();
let paged_vmo_reader = paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
let reader_thread = std::thread::spawn(move || {
let mut read_buf = vec![0u8; expected_len];
paged_vmo_reader.read(&mut read_buf, 0).expect("paged vmo read failed");
read_buf
});
let read_data = reader_thread.join().unwrap();
assert_eq!(&read_data, &blob.data, "Blob {idx} data readback mismatch!");
drop(paged_vmo);
log::info!("Blob {idx} verified and closed successfully.");
}
drop(blob_pager);
env.shutdown().await?;
log::info!("All blobs verified successfully over mapper on GPT partition!");
Ok(())
}
#[fuchsia::test(threads = 4)]
async fn test_mapper_with_verification_corrupted_block_on_gpt_partition() -> Result<(), Error> {
let env = TestPartitionEnv::new().await?;
log::info!("Initializing BlobPagerAndVerifier...");
let blob_pager = BlobPagerAndVerifier::new(&env.mapping_provider, &env.mapper_proxy)
.await
.context("Failed to create BlobPagerAndVerifier")?;
log::info!("Testing verification failure on corrupted disk blocks...");
let original_data = vec![0x33u8; 32 * 1024];
let target_blob = TestBlob::new_uncompressed(original_data);
env.write_blob(&target_blob).await?;
// Open a temporary mapping session to discover the device block offset of the blob.
let (temp_session, temp_server) = fidl::endpoints::create_proxy();
let temp_mapping_vmo = env
.mapping_provider
.open_session(temp_server)
.await
.context("FIDL error on temp open_session")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open temp mapping session")?;
let temp_key = 1u64;
let _uncompressed_size = temp_session
.open(temp_key, target_blob.hash.as_bytes())
.await
.context("FIDL error on temp_session.Open")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open test blob in temp mapping session")?;
let mut receiver = vmo_fifo::Receiver::<mapping::RawMappingCommand>::new(
temp_mapping_vmo,
mapping::PENDING_COMMANDS_CAPACITY,
)
.context("Failed to create receiver for temp mapping vmo")?;
let msg = receiver.peek().context("Failed to peek temp mapping message")?;
let cmd = mapping::MappingCommand::try_from(*msg)
.map_err(|e| anyhow!("Invalid mapping command: {e:?}"))?;
let (offset, extent_count) = match cmd {
mapping::MappingCommand::Mappings { offset, extent_count, .. } => (offset, extent_count),
other => bail!("Expected Mappings command, got {other:?}"),
};
ensure!(extent_count > 0, "Expected at least one extent for test blob");
let payload_slice = msg.payload_slice(offset, 8);
let mut packed_bytes = [0u8; 8];
payload_slice.copy_to_slice(&mut packed_bytes);
let packed_extent = u64::from_le_bytes(packed_bytes);
let device_block_offset = packed_extent & 0xffff_ffff;
drop(msg);
drop(receiver);
temp_session
.close(temp_key)
.await
.context("FIDL error on temp_session.Close")?
.map_err(zx::Status::err_from_raw)
.context("Failed to close temp session")?;
log::info!("Corrupting physical block {device_block_offset} on disk...");
let block_proxy = env.partition.connect_block()?.into_proxy();
let block_client = block_client::RemoteBlockClient::new(block_proxy)
.await
.context("Failed to create RemoteBlockClient")?;
let garbage = vec![0xDEu8; 4096];
block_client
.write_at(block_client::BufferSlice::Memory(&garbage), device_block_offset * 4096)
.await
.context("Failed to overwrite block with garbage")?;
block_client.flush().await.context("Failed to flush corrupted block")?;
log::info!("Creating verified paged VMO for blob with corrupted disk block...");
let root_hash = <[u8; 32]>::from(target_blob.hash);
let paged_vmo = blob_pager
.create_vmo(&root_hash)
.await
.context("Failed to create paged VMO for test blob")?;
let paged_vmo_reader = paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
let reader_thread = std::thread::spawn(move || {
let mut read_buf = vec![0u8; 4096];
paged_vmo_reader.read(&mut read_buf, 0)
});
let read_result = reader_thread.join().unwrap();
assert_eq!(
read_result.expect_err("Paging corrupted block must fail verification!"),
zx::Status::IO_DATA_INTEGRITY
);
drop(paged_vmo);
log::info!("Corrupted block correctly failed verification with IO_DATA_INTEGRITY.");
drop(blob_pager);
env.shutdown().await?;
log::info!("Corrupted block verification failure test completed successfully!");
Ok(())
}
#[derive(Copy, Clone, Debug)]
enum PagingBackend {
FxBlob,
DriverBlobPager,
}
// Verifies that unlinking an open blob does not deallocate its extents while held open in a mapping
// session or BlobReader.
#[test_case(PagingBackend::FxBlob; "fxblob")]
#[test_case(PagingBackend::DriverBlobPager; "driver_blob_pager")]
#[fuchsia::test(threads = 4)]
async fn test_unlink_open_blob(backend: PagingBackend) -> Result<(), Error> {
let env = TestPartitionEnv::new_with_options(matches!(backend, PagingBackend::DriverBlobPager))
.await?;
let blob_pager = match backend {
PagingBackend::DriverBlobPager => {
log::info!("Initialising BlobPagerAndVerifier...");
Some(
BlobPagerAndVerifier::new(&env.mapping_provider, &env.mapper_proxy)
.await
.context("Failed to create BlobPagerAndVerifier")?,
)
}
PagingBackend::FxBlob => None,
};
let blob_a = TestBlob::new_uncompressed(vec![0xAAu8; 65536]);
env.write_blob(&blob_a).await?;
log::info!(backend:?; "Opening Blob A...");
let paged_vmo = match backend {
PagingBackend::FxBlob => env
.blob_reader
.get_vmo(&blob_a.hash.into())
.await
.context("FIDL error on BlobReader.GetVmo")?
.map_err(zx::Status::err_from_raw)
.context("Failed to get VMO from BlobReader")?,
PagingBackend::DriverBlobPager => blob_pager
.as_ref()
.unwrap()
.create_vmo(&blob_a.hash.into())
.await
.context("Failed to create paged VMO in BlobPagerAndVerifier")?,
};
log::info!("Unlinking Blob A while its VMO is held open...");
let unlink_res = env
.volume
.root()
.unlink(&format!("{}", blob_a.hash), &fio::UnlinkOptions::default())
.await
.context("FIDL error on unlink")?;
unlink_res.map_err(zx::Status::err_from_raw).context("Failed to unlink Blob A")?;
log::info!("Syncing filesystem to flush graveyard entries...");
env.volume
.root()
.sync()
.await
.context("FIDL error on sync")?
.map_err(zx::Status::err_from_raw)
.context("Failed to sync filesystem")?;
log::info!("Writing Blob B to attempt block reuse...");
let blob_b = TestBlob::new_uncompressed(vec![0xBBu8; blob_a.data.len()]);
env.write_blob(&blob_b).await?;
log::info!("Reading Blob A from paged VMO...");
let expected_len = blob_a.data.len();
let paged_vmo_reader = paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
let reader_thread = std::thread::spawn(move || {
let mut read_buf = vec![0u8; expected_len];
paged_vmo_reader.read(&mut read_buf, 0).map(|_| read_buf)
});
let read_data = reader_thread.join().unwrap().context("read failed on paged VMO")?;
ensure!(read_data == blob_a.data, "Paged VMO read corrupted data");
drop(paged_vmo);
drop(blob_pager);
env.shutdown().await?;
log::info!(
backend:?;
"Successfully verified unlinked blob remained valid and readable if blob remains opened"
);
Ok(())
}
#[fuchsia::test(threads = 4)]
async fn test_profile_record_and_replay() -> Result<(), Error> {
let env = TestPartitionEnv::new().await?;
let blob_a = TestBlob::new_type1_compressed(vec![0xAAu8; 64 * 1024]);
let blob_b = TestBlob::new_uncompressed(vec![0xBBu8; 32 * 1024]);
let blob_c = TestBlob::new_type3_compressed(vec![0xCCu8; 200 * 1024]);
env.write_blob(&blob_a).await?;
env.write_blob(&blob_b).await?;
env.write_blob(&blob_c).await?;
// 1. Start `.boot` profile recording from `BlobPagerAndVerifier` and read all three blobs.
let blob_pager_1 = BlobPagerAndVerifier::new(&env.mapping_provider, &env.mapper_proxy)
.await
.context("Failed to create BlobPagerAndVerifier (run 1)")?;
let recording_1 = blob_pager_1
.start_profile(".boot")
.await
.context("Failed to start profile recording (run 1)")?;
for blob in [&blob_a, &blob_b, &blob_c] {
let vmo = blob_pager_1.create_vmo(&blob.hash.into()).await?;
let expected_len = blob.data.len();
let reader_thread = std::thread::spawn(move || {
let mut buf = vec![0u8; expected_len];
vmo.read(&mut buf, 0).expect("paged vmo read failed");
buf
});
assert_eq!(reader_thread.join().expect("reader_thread panicked"), blob.data);
}
recording_1.stop().await.context("Failed to stop profile recording (run 1)")?;
drop(blob_pager_1);
// 2. Create a fresh `BlobPagerAndVerifier` (empty cache) and start a second `.boot` profile
// run. It replays `blob_a`, `blob_b`, and `blob_c` into `blob_pager_2`'s VMO cache.
let blob_pager_2 = BlobPagerAndVerifier::new(&env.mapping_provider, &env.mapper_proxy)
.await
.context("Failed to create BlobPagerAndVerifier (run 2)")?;
let recording_2 = blob_pager_2
.start_profile(".boot")
.await
.context("Failed to start profile recording (run 2)")?;
// Open `blob_a` and `blob_c` as a client during run 2 without reading them, so only
// background replay's `ZX_VMO_OP_PREFETCH` faults their pages through the driver. Leave
// `blob_b` unopened during run 2 so it is filtered out of the new `.boot` recording.
let _vmo_a = blob_pager_2.create_vmo(&blob_a.hash.into()).await?;
let _vmo_c = blob_pager_2.create_vmo(&blob_c.hash.into()).await?;
// Stopping `recording_2` waits for replay threads to finish prefetching all replayed blobs,
// stops driver recording, filters out unopened `blob_b`, and commits the new `.boot` profile
// containing only `blob_a` and `blob_c`.
recording_2.stop().await.context("Failed to stop profile recording (run 2)")?;
drop(_vmo_a);
drop(_vmo_c);
drop(blob_pager_2);
// 3. Call `MappingSession.GetProfile(".boot")` to verify that the persisted `.boot` profile
// from run 2 contains `blob_a` and both 128 KiB readahead windows of `blob_c`, and filtered
// out `blob_b`.
let (mapping_session, mapping_session_server) = fidl::endpoints::create_proxy();
let _mapping_vmo = env
.mapping_provider
.open_session(mapping_session_server)
.await
.context("FIDL error on open_session (run 3)")?
.map_err(zx::Status::err_from_raw)
.context("Failed to open mapping session (run 3)")?;
let replay_vmo = mapping_session
.get_profile(".boot")
.await
.context("FIDL error on get_profile (run 3)")?
.map_err(zx::Status::err_from_raw)
.context("Failed to get profile (run 3)")?;
let messages: Vec<_> = BlobMessage::decode_from_vmo(replay_vmo, 4096)
.context("Failed to decode replay_vmo in run 3")?
.collect();
assert_eq!(messages.len(), 3);
assert!(messages.contains(&BlobMessage { id: blob_a.hash, offset: 0 }));
assert!(messages.contains(&BlobMessage { id: blob_c.hash, offset: 0 }));
assert!(messages.contains(&BlobMessage { id: blob_c.hash, offset: 128 * 1024 }));
assert!(!messages.iter().any(|m| m.id == blob_b.hash));
env.shutdown().await?;
Ok(())
}
#[fuchsia::test(threads = 4)]
async fn test_mapper_close_during_read_on_fvm() -> Result<(), Error> {
let block_size = 4096u64;
let block_count = 256 * 1024 * 1024 / block_size;
let vmo = zx::Vmo::create(block_count * block_size)?;
let block_server = Arc::new(
VmoBackedServerOptions {
block_size: block_size as u32,
initial_contents: InitialContents::FromVmo(vmo),
max_jitter_usec: Some(5000),
..Default::default()
}
.build()
.context("Failed to create VmoBackedServer")?,
);
let mut fvm_fs = FsManagementFilesystem::new(
VmoBackedServerConnector::new(block_server),
Fvm { slice_size: 8 * 1024 * 1024, ..Fvm::dynamic_child() },
);
fvm_fs.format().await.context("Failed to format FVM")?;
let fvm = fvm_fs.serve_multi_volume().await.context("Failed to serve FVM")?;
let volumes = connect_to_protocol_at_dir_root::<VolumesMarker>(fvm.exposed_dir())
.context("Failed to connect to FVM Volumes")?;
let (volume_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
volumes
.create(
"fxfs",
server_end,
CreateOptions {
initial_size: Some(64 * 1024 * 1024),
type_guid: Some([1u8; 16]),
guid: Some([2u8; 16]),
..Default::default()
},
MountOptions::default(),
)
.await
.context("FIDL error creating FVM volume")?
.map_err(zx::Status::err_from_raw)
.context("Failed to create FVM volume")?;
let connector = Arc::new(DirBasedBlockConnector::new(
volume_dir,
format!("svc/{}", fblock::BlockMarker::PROTOCOL_NAME),
));
let mut fxfs = FsManagementFilesystem::new(
connector.clone(),
Fxfs { allow_type3_blobs: true, ..Default::default() },
);
fxfs.format().await.context("Failed to format Fxfs")?;
let serving = fxfs.serve_multi_volume().await.context("Failed to serve Fxfs")?;
let volume = serving
.create_volume(
"blob",
CreateOptions::default(),
MountOptions { as_blob: Some(true), ..Default::default() },
)
.await
.context("Failed to create blob volume")?;
let blob_creator = connect_to_protocol_at_dir_svc::<BlobCreatorMarker>(volume.exposed_dir())?;
let mapping_provider =
connect_to_protocol_at_dir_svc::<MappingProviderMarker>(volume.exposed_dir())?;
let block_proxy = connector.connect_block()?.into_proxy();
let (mapper_proxy, mapper_server) = create_proxy::<fblock::MapperMarker>();
block_proxy
.connect_mapper(mapper_server)
.await
.context("FIDL error on ConnectMapper")?
.map_err(zx::Status::err_from_raw)
.context("ConnectMapper failed")?;
drop(block_proxy);
let blob = TestBlob::new_type1_compressed(vec![0xAAu8; 128 * 1024]);
let writer_client_end = blob_creator
.create(&blob.hash.into(), false)
.await?
.map_err(|e| anyhow!("CreateBlob error: {e:?}"))?;
let mut blob_writer =
BlobWriter::create(writer_client_end.into_proxy(), blob.delivery_data.len() as u64).await?;
blob_writer.write(&blob.delivery_data).await?;
let root_hash = <[u8; 32]>::from(blob.hash);
let blob_pager = BlobPagerAndVerifier::new(&mapping_provider, &mapper_proxy).await?;
let paged_vmo = blob_pager.create_vmo(&root_hash).await?;
fasync::unblock(move || {
let len = 128 * 1024;
let addr = vmar_root_self()
.map(0, &paged_vmo, 0, len, zx::VmarFlags::PERM_READ)
.expect("vmar map failed");
// SAFETY: `addr` is mapped for `len` bytes and remains mapped until `unmap` below.
let slice = unsafe { from_raw_parts(addr as *const u8, len) };
// Faulting at 0 triggers a 128 KiB readahead that flushes the first 96 KiB early for
// verification, waking this thread before 96..128 KiB is supplied. Faulting immediately at
// 96 KiB queues a second pager read that is still in flight when `blob_pager` is dropped
// below.
black_box(slice[0]);
black_box(slice[96 * 1024]);
// SAFETY: Unmaps the region mapped above after `slice` is no longer used.
unsafe {
let _ = vmar_root_self().unmap(addr, len);
}
})
.await;
drop(blob_pager);
serving.shutdown().await.context("Failed to shutdown Fxfs")?;
drop(connector);
volumes
.remove("fxfs")
.await
.context("FIDL error removing FVM volume")?
.map_err(zx::Status::err_from_raw)
.context("Failed to destroy FVM volume")?;
Ok(())
}