77use std:: collections:: HashMap ;
88use std:: sync:: Arc ;
99
10- use agent:: Agent ;
10+ use agent:: { Agent , Storage } ;
1111use anyhow:: { anyhow, Context , Result } ;
1212use common:: {
1313 error:: Error ,
@@ -26,7 +26,8 @@ use oci_spec::runtime as oci;
2626
2727use oci:: { LinuxResources , Process as OCIProcess } ;
2828use resource:: {
29- cdi_devices:: container_device:: annotate_container_devices, ResourceManager , ResourceUpdateOp ,
29+ cdi_devices:: { container_device:: annotate_container_devices, ContainerDevice } ,
30+ ResourceManager , ResourceUpdateOp ,
3031} ;
3132use tokio:: sync:: RwLock ;
3233
@@ -202,6 +203,15 @@ impl Container {
202203 . resource_manager
203204 . handler_devices ( & config. container_id , linux)
204205 . await ?;
206+
207+ // Handle annotation-based block device mounts.
208+ // Devices listed in the annotation are mounted as filesystems by the agent
209+ // rather than passed as raw block devices, so filter them out first.
210+ let ( container_devices, annotation_storages) =
211+ handle_block_mount_annotation ( & updated_annotations, container_devices, & mut spec)
212+ . context ( "handle block mount annotation" ) ?;
213+ storages. extend ( annotation_storages) ;
214+
205215 let devices_agent = annotate_container_devices ( & mut spec, container_devices)
206216 . context ( "annotate container devices failed" ) ?;
207217
@@ -643,6 +653,115 @@ impl Container {
643653 }
644654}
645655
656+ const BLOCK_DEVICE_MOUNTS_ANNOTATION : & str = "io.katacontainers.volume.block-mounts" ;
657+
658+ #[ derive( Debug , serde:: Deserialize ) ]
659+ struct BlockMountConfig {
660+ mount : String ,
661+ #[ serde( default ) ]
662+ fstype : String ,
663+ #[ serde( default ) ]
664+ options : Vec < String > ,
665+ }
666+
667+ // Parses the block mount annotation and processes any matching container devices.
668+ //
669+ // Devices listed in the annotation are meant to be mounted as filesystems by the agent
670+ // (not passed as raw block devices). This function:
671+ // 1. Splits container_devices into annotated and remaining sets.
672+ // 2. Creates a Storage object for each annotated device.
673+ // 3. Adds a bind mount in the OCI spec from the guest storage path to the destination.
674+ // 4. Removes matching devices from spec.linux.devices.
675+ //
676+ // Returns (remaining_devices, new_storages).
677+ fn handle_block_mount_annotation (
678+ annotations : & HashMap < String , String > ,
679+ container_devices : Vec < ContainerDevice > ,
680+ spec : & mut oci:: Spec ,
681+ ) -> Result < ( Vec < ContainerDevice > , Vec < Storage > ) > {
682+ let raw = match annotations. get ( BLOCK_DEVICE_MOUNTS_ANNOTATION ) {
683+ Some ( v) if !v. is_empty ( ) => v. as_str ( ) ,
684+ _ => return Ok ( ( container_devices, vec ! [ ] ) ) ,
685+ } ;
686+
687+ let block_mounts: HashMap < String , BlockMountConfig > =
688+ serde_json:: from_str ( raw) . context ( "failed to parse block mount annotation" ) ?;
689+
690+ if block_mounts. is_empty ( ) {
691+ return Ok ( ( container_devices, vec ! [ ] ) ) ;
692+ }
693+
694+ let mut storages = Vec :: new ( ) ;
695+ let mut mounted_paths: HashMap < String , bool > = HashMap :: new ( ) ;
696+
697+ let ( annotated, remaining) : ( Vec < _ > , Vec < _ > ) = container_devices
698+ . into_iter ( )
699+ . partition ( |cd| block_mounts. contains_key ( & cd. device . container_path ) ) ;
700+
701+ for cd in annotated {
702+ let config = block_mounts
703+ . get ( & cd. device . container_path )
704+ . expect ( "partition guarantees key exists" ) ;
705+
706+ let source = cd. device . id . clone ( ) ;
707+ let driver = cd. device . field_type . clone ( ) ;
708+ let fstype = if config. fstype . is_empty ( ) {
709+ "ext4" . to_string ( )
710+ } else {
711+ config. fstype . clone ( )
712+ } ;
713+ let options = if config. options . is_empty ( ) {
714+ vec ! [ "rw" . to_string( ) ]
715+ } else {
716+ config. options . clone ( )
717+ } ;
718+
719+ // Build a unique, valid path component from the PCI source string.
720+ let sanitized: String = source
721+ . chars ( )
722+ . map ( |c| if c. is_alphanumeric ( ) || c == '-' { c } else { '_' } )
723+ . collect ( ) ;
724+ let guest_mount_point = format ! ( "/run/kata-containers/storage/{}" , sanitized) ;
725+
726+ storages. push ( Storage {
727+ driver,
728+ source,
729+ fs_type : fstype,
730+ options,
731+ mount_point : guest_mount_point. clone ( ) ,
732+ ..Default :: default ( )
733+ } ) ;
734+
735+ // Add bind mount: agent mounts the block device at guest_mount_point, then
736+ // the bind mount exposes it at the container destination path.
737+ let mut bind_mount = oci:: Mount :: default ( ) ;
738+ bind_mount. set_destination ( std:: path:: PathBuf :: from ( & config. mount ) ) ;
739+ bind_mount. set_typ ( Some ( "bind" . to_string ( ) ) ) ;
740+ bind_mount. set_source ( Some ( std:: path:: PathBuf :: from ( & guest_mount_point) ) ) ;
741+ bind_mount. set_options ( Some ( vec ! [ "bind" . to_string( ) ] ) ) ;
742+
743+ let mut mounts = spec. mounts ( ) . clone ( ) . unwrap_or_default ( ) ;
744+ mounts. push ( bind_mount) ;
745+ spec. set_mounts ( Some ( mounts) ) ;
746+
747+ mounted_paths. insert ( cd. device . container_path , true ) ;
748+ }
749+
750+ // Remove matched devices from spec.linux.devices so the agent doesn't
751+ // see them as character/block devices in the container namespace.
752+ if !mounted_paths. is_empty ( ) {
753+ if let Some ( linux) = spec. linux_mut ( ) {
754+ if let Some ( devices) = linux. devices_mut ( ) {
755+ devices. retain ( |d| {
756+ !mounted_paths. contains_key ( & d. path ( ) . display ( ) . to_string ( ) )
757+ } ) ;
758+ }
759+ }
760+ }
761+
762+ Ok ( ( remaining, storages) )
763+ }
764+
646765fn amend_spec (
647766 spec : & mut oci:: Spec ,
648767 disable_guest_seccomp : bool ,
0 commit comments