@@ -138,8 +138,12 @@ pub struct ApplyArgs {
138138 pub bundle : PathBuf ,
139139
140140 /// Mounted root path to apply operations under.
141- #[ arg( long) ]
142- pub root : PathBuf ,
141+ #[ arg( long, conflicts_with = "image" , required_unless_present = "image" ) ]
142+ pub root : Option < PathBuf > ,
143+
144+ /// Raw VM disk image path to mount/apply/detach automatically.
145+ #[ arg( long, conflicts_with = "root" , required_unless_present = "root" ) ]
146+ pub image : Option < PathBuf > ,
143147}
144148
145149/// Typed bundle manifest.
@@ -923,10 +927,62 @@ fn patch_state_path() -> PathBuf {
923927}
924928
925929fn apply_with_state_path ( args : ApplyArgs , patch_state_path : & Path ) -> anyhow:: Result < ( ) > {
926- let manifest = verify_bundle ( & args. bundle ) ?;
930+ match ( args. root . as_ref ( ) , args. image . as_ref ( ) ) {
931+ ( Some ( root) , None ) => apply_with_root ( & args. bundle , root, patch_state_path) ,
932+ ( None , Some ( image) ) => apply_with_image ( & args. bundle , image, patch_state_path) ,
933+ _ => bail ! ( "exactly one apply target is required: --root <path> or --image <path>" ) ,
934+ }
935+ }
936+
937+ fn apply_with_image ( bundle : & Path , image : & Path , patch_state_path : & Path ) -> anyhow:: Result < ( ) > {
938+ let image = expand_home ( image) ;
939+ if !image. exists ( ) {
940+ bail ! ( "disk image not found: {}" , image. display( ) ) ;
941+ }
942+
943+ let manifest = verify_bundle ( bundle) ?;
927944 validate_patch_target_base_policy ( & manifest) ?;
928- let root = fs:: canonicalize ( & args. root )
929- . with_context ( || format ! ( "failed to resolve apply root {}" , args. root. display( ) ) ) ?;
945+ super :: vm_base:: verify_image_for_base_id ( & image, & manifest. target_base_id ) . with_context (
946+ || {
947+ format ! (
948+ "pinned base verification failed before applying patch to image {}" ,
949+ image. display( )
950+ )
951+ } ,
952+ ) ?;
953+
954+ let disk = crate :: provision:: attach_and_mount ( & image) . with_context ( || {
955+ format ! (
956+ "failed to attach and mount image {} before patch apply" ,
957+ image. display( )
958+ )
959+ } ) ?;
960+
961+ let result =
962+ apply_verified_manifest_with_root ( manifest, bundle, & disk. mount_point , patch_state_path) ;
963+ let detach_result = disk. detach ( ) ;
964+
965+ result?;
966+ detach_result?;
967+
968+ println ! ( "Patch apply completed for image {}" , image. display( ) ) ;
969+ Ok ( ( ) )
970+ }
971+
972+ fn apply_with_root ( bundle : & Path , root : & Path , patch_state_path : & Path ) -> anyhow:: Result < ( ) > {
973+ let manifest = verify_bundle ( bundle) ?;
974+ validate_patch_target_base_policy ( & manifest) ?;
975+ apply_verified_manifest_with_root ( manifest, bundle, root, patch_state_path)
976+ }
977+
978+ fn apply_verified_manifest_with_root (
979+ manifest : PatchBundleManifest ,
980+ bundle : & Path ,
981+ root_arg : & Path ,
982+ patch_state_path : & Path ,
983+ ) -> anyhow:: Result < ( ) > {
984+ let root = fs:: canonicalize ( root_arg)
985+ . with_context ( || format ! ( "failed to resolve apply root {}" , root_arg. display( ) ) ) ?;
930986 if !root. is_dir ( ) {
931987 bail ! ( "apply root {} is not a directory" , root. display( ) ) ;
932988 }
@@ -953,7 +1009,7 @@ fn apply_with_state_path(args: ApplyArgs, patch_state_path: &Path) -> anyhow::Re
9531009 ) ;
9541010 }
9551011
956- let paths = BundlePaths :: from_bundle_dir ( & args . bundle ) ;
1012+ let paths = BundlePaths :: from_bundle_dir ( bundle) ;
9571013 let payload_by_digest = load_payload_archive ( & paths. payload ) ?;
9581014 validate_apply_preflight ( & manifest, & payload_by_digest) ?;
9591015 apply_operations_transactional ( & root, & manifest, & payload_by_digest) ?;
@@ -1233,6 +1289,16 @@ fn decode_pem_private_key(raw: &[u8]) -> anyhow::Result<Vec<u8>> {
12331289 . context ( "private key PEM body is not valid base64" )
12341290}
12351291
1292+ fn expand_home ( path : & Path ) -> PathBuf {
1293+ let s = path. to_string_lossy ( ) ;
1294+ if s. starts_with ( "~/" ) {
1295+ if let Ok ( home) = std:: env:: var ( "HOME" ) {
1296+ return PathBuf :: from ( format ! ( "{}{}" , home, & s[ 1 ..] ) ) ;
1297+ }
1298+ }
1299+ path. to_path_buf ( )
1300+ }
1301+
12361302fn verify_bundle ( bundle_dir : & Path ) -> anyhow:: Result < PatchBundleManifest > {
12371303 let paths = BundlePaths :: from_bundle_dir ( bundle_dir) ;
12381304
@@ -2257,7 +2323,8 @@ mod tests {
22572323 apply_with_state_path (
22582324 ApplyArgs {
22592325 bundle : bundle. to_path_buf ( ) ,
2260- root : root. to_path_buf ( ) ,
2326+ root : Some ( root. to_path_buf ( ) ) ,
2327+ image : None ,
22612328 } ,
22622329 patch_state_path,
22632330 )
0 commit comments