blob: fdfef2a5112ac60b0580f18a2d5e4afdf9fe0431 [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.
use {
crate::device::Device,
anyhow::{anyhow, Context, Error},
fidl_fuchsia_hardware_block_partition::Guid,
fidl_fuchsia_hardware_block_volume::{VolumeManagerMarker, VolumeProxy},
fuchsia_component::client::connect_to_protocol_at_path,
fuchsia_zircon as zx,
std::cmp,
};
// Number of bits required for the VSlice address space.
const SLICE_ENTRY_VSLICE_BITS: u64 = 32;
// Maximum number of VSlices that can be addressed.
const MAX_VSLICES: u64 = 1 << (SLICE_ENTRY_VSLICE_BITS - 1);
const DEFAULT_VOLUME_PERCENTAGE: u64 = 10;
const DEFAULT_VOLUME_SIZE: u64 = 24 * 1024 * 1024;
pub async fn resize_volume(volume_proxy: &VolumeProxy, target_bytes: u64) -> Result<u64, Error> {
// Free existing slices (except the first).
// Note while physical slices in FVM are 1-indexed, virtual slices (used here) are 0-indexed.
// The reason we start at slice 1 here is because FVM requires that the first slice always be
// allocated -- we can't shrink it away.
let mut slice = 1;
while slice < (MAX_VSLICES - 1) {
// Note also that query_slices responds with an extent that is either allocated or free,
// but not a mix of both. The last unallocated extent always has a slice count that runs
// the total to MAX_VSLICES - 1.
let (status, vslices, response_count) =
volume_proxy.query_slices(&[slice]).await.context("Transport error on query_slices")?;
zx::Status::ok(status).context("query_slices failed")?;
if response_count == 0 {
break;
}
for i in 0..response_count {
if vslices[i as usize].allocated {
let status = volume_proxy
.shrink(slice, vslices[i as usize].count)
.await
.context("Transport error on shrink")?;
zx::Status::ok(status)?;
}
slice += vslices[i as usize].count;
}
}
let (status, volume_manager_info, _volume_info) =
volume_proxy.get_volume_info().await.context("Transport error on get_volume_info")?;
zx::Status::ok(status).context("get_volume_info failed")?;
let manager = volume_manager_info.ok_or(anyhow!("Expected volume manager info"))?;
let slice_size = manager.slice_size;
// Note we add one here to include the 0th slice that we cannot shrink away.
let slices_available = manager.slice_count - manager.assigned_slice_count + 1;
let mut slice_count = if target_bytes == 0 {
// If a size is not specified, limit the size of the data partition so as not to use up all
// FVM's space (thus limiting blobfs growth). 10% or 24MiB (whichever is larger) should be
// enough.
let default_slices = cmp::max(
manager.slice_count * DEFAULT_VOLUME_PERCENTAGE / 100,
DEFAULT_VOLUME_SIZE / slice_size,
);
tracing::info!("Using default size of {:?}", default_slices * slice_size);
cmp::min(slices_available, default_slices)
} else {
(target_bytes + slice_size - 1) / slice_size
};
if slices_available < slice_count {
tracing::info!(
"Only {:?} slices available. Some functionality may be missing",
slices_available
);
slice_count = slices_available;
}
if slice_count > 1 {
let status = volume_proxy
.extend(1, slice_count - 1)
.await
.context("Transport error on extend call")?;
zx::Status::ok(status).with_context(|| {
format!("Failed to extend partition (slice count: {:?})", slice_count)
})?;
}
return Ok(slice_count * slice_size);
}
pub async fn set_partition_max_bytes(
device: &mut dyn Device,
max_byte_size: u64,
) -> Result<(), Error> {
if max_byte_size == 0 {
return Ok(());
}
let index =
device.topological_path().rfind("/fvm").ok_or(anyhow!("fvm is not in the device path"))?;
// The 4 is from the 4 characters in "/fvm"
let fvm_path = &device.topological_path()[..index + 4];
let fvm_proxy = connect_to_protocol_at_path::<VolumeManagerMarker>(&fvm_path)
.context("Failed to connect to fvm volume manager")?;
let (status, info) = fvm_proxy.get_info().await.context("Transport error in get_info call")?;
zx::Status::ok(status).context("get_info call failed")?;
let info = info.ok_or(anyhow!("Expected info"))?;
let slice_size = info.slice_size;
let max_slice_count = (max_byte_size + slice_size - 1) / slice_size;
let instance_guid =
Guid { value: *device.partition_instance().await.context("Expected partition instance")? };
let status = fvm_proxy
.set_partition_limit(&instance_guid, max_slice_count)
.await
.context("Transport error on set_partition_limit")?;
zx::Status::ok(status).context("set_partition_limit failed")?;
Ok(())
}
#[cfg(test)]
mod tests {
use {
crate::volume::{resize_volume, MAX_VSLICES},
anyhow::Error,
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_hardware_block_volume::{
VolumeManagerInfo, VolumeMarker, VolumeRequest, VsliceRange,
},
fuchsia_zircon as zx,
futures::{pin_mut, select, FutureExt, StreamExt},
};
const SLICE_SIZE: u64 = 16384;
const SLICE_COUNT: u64 = 5000;
const MAXIMUM_SLICE_COUNT: u64 = 5500;
const RANGE_ALLOCATED: u64 = 1234;
async fn check_resize_volume(
target_bytes: u64,
assigned_slice_count: u64,
expected_extend_slice_count: u64,
) -> Result<u64, Error> {
let (proxy, mut stream) = create_proxy_and_stream::<VolumeMarker>().unwrap();
let mock_device = async {
while let Some(request) = stream.next().await {
match request {
Ok(VolumeRequest::QuerySlices { responder, start_slices }) => {
let mut slices = [VsliceRange { allocated: false, count: 0 }; 16];
slices[0] = VsliceRange { allocated: true, count: RANGE_ALLOCATED };
let count = if start_slices[0] == 1 { 1 } else { 0 };
responder.send(zx::sys::ZX_OK, &slices, count).unwrap();
}
Ok(VolumeRequest::Shrink { responder, start_slice, slice_count }) => {
assert_eq!(start_slice, 1);
assert_eq!(slice_count, RANGE_ALLOCATED);
responder.send(zx::sys::ZX_OK).unwrap();
}
Ok(VolumeRequest::GetVolumeInfo { responder }) => {
responder
.send(
zx::sys::ZX_OK,
Some(&VolumeManagerInfo {
slice_size: SLICE_SIZE,
slice_count: SLICE_COUNT,
assigned_slice_count: assigned_slice_count,
maximum_slice_count: MAXIMUM_SLICE_COUNT,
max_virtual_slice: MAX_VSLICES,
}),
None,
)
.unwrap();
}
Ok(VolumeRequest::Extend { responder, start_slice, slice_count }) => {
assert_eq!(start_slice, 1);
assert_eq!(slice_count, expected_extend_slice_count);
responder.send(zx::sys::ZX_OK).unwrap();
}
_ => {
println!("Unexpected request: {:?}", request);
unreachable!()
}
}
}
}
.fuse();
pin_mut!(mock_device);
select! {
_ = mock_device => unreachable!(),
matches = resize_volume(&proxy, target_bytes).fuse() => matches,
}
}
#[fuchsia::test]
async fn test_target_bytes_zero_slice_count_equals_default_slices() {
let target_bytes = 0;
let assigned_slice_count = 3000;
let expected_extend_slice_count = 1535;
assert_eq!(
check_resize_volume(target_bytes, assigned_slice_count, expected_extend_slice_count)
.await
.unwrap(),
// Add one because extend ignores free allocated slice per volume
(expected_extend_slice_count + 1) * SLICE_SIZE
);
}
#[fuchsia::test]
async fn test_target_bytes_zero_slice_count_equals_slices_available() {
let target_bytes = 0;
let assigned_slice_count = 4000;
let expected_extend_slice_count = 1000;
assert_eq!(
check_resize_volume(target_bytes, assigned_slice_count, expected_extend_slice_count)
.await
.unwrap(),
// Add one because extend ignores free allocated slice per volume
(expected_extend_slice_count + 1) * SLICE_SIZE
);
}
#[fuchsia::test]
async fn test_slice_count_less_than_slice_available() {
let target_bytes = SLICE_SIZE * 2500;
let assigned_slice_count = 3000;
let expected_extend_slice_count = 2000;
assert_eq!(
check_resize_volume(target_bytes, assigned_slice_count, expected_extend_slice_count)
.await
.unwrap(),
// Add one because extend ignores free allocated slice per volume
(expected_extend_slice_count + 1) * SLICE_SIZE
);
}
}