@@ -36,6 +36,7 @@ const IMAGE_DELTA_VERSION: u32 = 1;
3636const DEFAULT_IMAGE_DELTA_CHUNK_SIZE_MIB : u32 = 4 ;
3737const MIN_IMAGE_DELTA_CHUNK_SIZE_MIB : u32 = 1 ;
3838const MAX_IMAGE_DELTA_CHUNK_SIZE_MIB : u32 = 64 ;
39+ const IMAGE_SIDECAR_EXTENSIONS : [ & str ; 3 ] = [ "aux" , "hwmodel" , "machineid" ] ;
3940
4041static 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+
12811334fn 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