Skip to content

Commit eded163

Browse files
Copy image sidecars during delta create/apply
1 parent 83bcd6c commit eded163

1 file changed

Lines changed: 85 additions & 2 deletions

File tree

crates/vz-cli/src/commands/vm_patch.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const IMAGE_DELTA_VERSION: u32 = 1;
3636
const DEFAULT_IMAGE_DELTA_CHUNK_SIZE_MIB: u32 = 4;
3737
const MIN_IMAGE_DELTA_CHUNK_SIZE_MIB: u32 = 1;
3838
const MAX_IMAGE_DELTA_CHUNK_SIZE_MIB: u32 = 64;
39+
const IMAGE_SIDECAR_EXTENSIONS: [&str; 3] = ["aux", "hwmodel", "machineid"];
3940

4041
static TEMP_FILE_COUNTER: AtomicU64 = AtomicU64::new(0);
4142

@@ -1008,7 +1009,7 @@ fn create_delta(args: CreateDeltaArgs) -> anyhow::Result<()> {
10081009
let workspace = TempWorkspace::new("vz-image-delta-create")?;
10091010
let patched_image = workspace.path().join("patched.img");
10101011

1011-
clone_or_copy_file(&base_image, &patched_image)?;
1012+
clone_or_copy_image_with_sidecars(&base_image, &patched_image, true)?;
10121013
let state_path = workspace.path().join("patch-state.json");
10131014
apply_with_state_path(
10141015
ApplyArgs {
@@ -1278,6 +1279,58 @@ fn clone_or_copy_file(source: &Path, destination: &Path) -> anyhow::Result<()> {
12781279
Ok(())
12791280
}
12801281

1282+
fn clone_or_copy_image_with_sidecars(
1283+
source_image: &Path,
1284+
destination_image: &Path,
1285+
require_sidecars: bool,
1286+
) -> anyhow::Result<()> {
1287+
clone_or_copy_file(source_image, destination_image).with_context(|| {
1288+
format!(
1289+
"failed to copy image {} to {}",
1290+
source_image.display(),
1291+
destination_image.display()
1292+
)
1293+
})?;
1294+
1295+
for extension in IMAGE_SIDECAR_EXTENSIONS {
1296+
let source_sidecar = source_image.with_extension(extension);
1297+
let destination_sidecar = destination_image.with_extension(extension);
1298+
1299+
match fs::symlink_metadata(&source_sidecar) {
1300+
Ok(metadata) => {
1301+
if !metadata.file_type().is_file() {
1302+
bail!(
1303+
"image sidecar {} must be a regular file",
1304+
source_sidecar.display()
1305+
);
1306+
}
1307+
clone_or_copy_file(&source_sidecar, &destination_sidecar).with_context(|| {
1308+
format!(
1309+
"failed to copy sidecar {} to {}",
1310+
source_sidecar.display(),
1311+
destination_sidecar.display()
1312+
)
1313+
})?;
1314+
}
1315+
Err(err) if err.kind() == ErrorKind::NotFound => {
1316+
if require_sidecars {
1317+
bail!(
1318+
"required image sidecar not found: {}",
1319+
source_sidecar.display()
1320+
);
1321+
}
1322+
}
1323+
Err(err) => {
1324+
return Err(err).with_context(|| {
1325+
format!("failed to inspect sidecar {}", source_sidecar.display())
1326+
});
1327+
}
1328+
}
1329+
}
1330+
1331+
Ok(())
1332+
}
1333+
12811334
fn create_image_delta_file(
12821335
base_image: &Path,
12831336
target_image: &Path,
@@ -1414,7 +1467,7 @@ fn apply_image_delta_file(
14141467
);
14151468
}
14161469

1417-
clone_or_copy_file(base_image, output_image)?;
1470+
clone_or_copy_image_with_sidecars(base_image, output_image, true)?;
14181471
let mut output = OpenOptions::new()
14191472
.write(true)
14201473
.read(true)
@@ -2947,6 +3000,14 @@ mod tests {
29473000
)
29483001
}
29493002

3003+
fn write_test_image_sidecars(image_path: &Path) {
3004+
fs::write(image_path.with_extension("aux"), b"aux-sidecar").expect("write aux sidecar");
3005+
fs::write(image_path.with_extension("hwmodel"), b"hwmodel-sidecar")
3006+
.expect("write hwmodel sidecar");
3007+
fs::write(image_path.with_extension("machineid"), b"machineid-sidecar")
3008+
.expect("write machineid sidecar");
3009+
}
3010+
29503011
#[test]
29513012
fn verify_bundle_valid_path() {
29523013
let bundle = create_valid_bundle();
@@ -3679,6 +3740,7 @@ mod tests {
36793740

36803741
fs::write(&base, &base_bytes).expect("write base");
36813742
fs::write(&target, &target_bytes).expect("write target");
3743+
write_test_image_sidecars(&base);
36823744

36833745
let header =
36843746
create_image_delta_file(&base, &target, &delta, 128 * 1024).expect("create delta");
@@ -3699,6 +3761,7 @@ mod tests {
36993761

37003762
fs::write(&base, b"base-original").expect("write base");
37013763
fs::write(&target, b"base-modified").expect("write target");
3764+
write_test_image_sidecars(&base);
37023765
create_image_delta_file(&base, &target, &delta, 64 * 1024).expect("create delta");
37033766

37043767
fs::write(&base, b"base-tampered").expect("tamper base");
@@ -3717,11 +3780,31 @@ mod tests {
37173780

37183781
fs::write(&base, b"abc").expect("write base");
37193782
fs::write(&target, b"abd").expect("write target");
3783+
write_test_image_sidecars(&base);
37203784
fs::write(&output, b"existing").expect("write existing output");
37213785
create_image_delta_file(&base, &target, &delta, 64 * 1024).expect("create delta");
37223786

37233787
let err = apply_image_delta_file(&base, &delta, &output)
37243788
.expect_err("existing output should fail");
37253789
assert!(format!("{err:#}").contains("output image already exists"));
37263790
}
3791+
3792+
#[test]
3793+
fn image_delta_apply_rejects_missing_required_sidecar() {
3794+
let dir = tempdir().expect("create temp dir");
3795+
let base = dir.path().join("base.img");
3796+
let target = dir.path().join("target.img");
3797+
let delta = dir.path().join("patch.vzdelta");
3798+
let output = dir.path().join("output.img");
3799+
3800+
fs::write(&base, b"abc").expect("write base");
3801+
fs::write(&target, b"abd").expect("write target");
3802+
write_test_image_sidecars(&base);
3803+
fs::remove_file(base.with_extension("machineid")).expect("remove machineid sidecar");
3804+
create_image_delta_file(&base, &target, &delta, 64 * 1024).expect("create delta");
3805+
3806+
let err = apply_image_delta_file(&base, &delta, &output)
3807+
.expect_err("missing sidecar should fail");
3808+
assert!(format!("{err:#}").contains("required image sidecar not found"));
3809+
}
37273810
}

0 commit comments

Comments
 (0)