@@ -116,12 +116,12 @@ impl RequirementAccumulator {
116116 }
117117 }
118118
119- fn add_gid ( & mut self , gid : u32 ) {
119+ fn add_gid ( & mut self , gid : u32 ) -> Result < ( ) , CdiError > {
120120 if gid == 0 {
121- tracing:: debug!( "Skipping CDI additionalGids entry for root GID 0" ) ;
122- return ;
121+ return Err ( CdiError :: RootAdditionalGid ) ;
123122 }
124123 self . additional_gids . insert ( gid) ;
124+ Ok ( ( ) )
125125 }
126126
127127 fn validate_writable_mounts < F > (
@@ -235,6 +235,9 @@ fn resolve_container_edits(
235235 selected_devices : & [ String ] ,
236236) -> Result < CdiContainerEdits , CdiError > {
237237 let ( mut cache, refresh_error) = build_cache ( & context. spec_dirs ) ;
238+ if let Some ( device) = conflicting_selected_device ( refresh_error. as_deref ( ) , selected_devices) {
239+ return Err ( CdiError :: DuplicateDevice ( device) ) ;
240+ }
238241 let mut merged = UpstreamContainerEdits :: new ( ) ;
239242 let mut applied_specs = BTreeSet :: new ( ) ;
240243
@@ -268,6 +271,21 @@ fn resolve_container_edits(
268271 serde_json:: from_value ( value) . map_err ( |source| CdiError :: EditDecode { source } )
269272}
270273
274+ // Temporary compatibility check for the Rust CDI cache revision pinned by
275+ // OpenShell. That revision records same-priority device conflicts but leaves
276+ // the conflicted device resolvable. Remove this once OpenShell uses the
277+ // upstream fix that excludes conflicts from the cache's device map.
278+ fn conflicting_selected_device (
279+ refresh_error : Option < & str > ,
280+ selected_devices : & [ String ] ,
281+ ) -> Option < String > {
282+ let refresh_error = refresh_error?;
283+ selected_devices
284+ . iter ( )
285+ . find ( |device| refresh_error. contains ( & format ! ( "conflicting device {device} (specs " ) ) )
286+ . cloned ( )
287+ }
288+
271289fn missing_device_error ( device : & str , refresh_error : Option < & str > ) -> CdiError {
272290 refresh_error. map_or_else (
273291 || CdiError :: MissingDevice ( device. to_string ( ) ) ,
@@ -319,7 +337,7 @@ where
319337 }
320338
321339 for gid in & edits. additional_gids {
322- accumulator. add_gid ( * gid) ;
340+ accumulator. add_gid ( * gid) ? ;
323341 }
324342
325343 accumulator. validate_writable_mounts ( normalized_writable_file_allowlist, path_kind) ?;
@@ -684,6 +702,37 @@ devices:
684702 ) ;
685703 }
686704
705+ #[ test]
706+ fn rejects_duplicate_cdi_device_names ( ) {
707+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
708+ for ( name, value) in [ ( "first.yaml" , "FIRST" ) , ( "second.yaml" , "SECOND" ) ] {
709+ write_spec (
710+ dir. path ( ) ,
711+ name,
712+ & format ! (
713+ r#"
714+ cdiVersion: 1.1.0
715+ kind: nvidia.com/gpu
716+ devices:
717+ - name: "0"
718+ containerEdits:
719+ env:
720+ - OPEN_SHELL_TEST={value}
721+ "#
722+ ) ,
723+ ) ;
724+ }
725+
726+ let err = resolve_with_kind (
727+ & context ( dir. path ( ) , & [ "nvidia.com/gpu=0" ] ) ,
728+ & [ ] ,
729+ always_missing,
730+ )
731+ . unwrap_err ( ) ;
732+
733+ assert ! ( matches!( err, CdiError :: DuplicateDevice ( device) if device == "nvidia.com/gpu=0" ) ) ;
734+ }
735+
687736 #[ test]
688737 fn empty_selection_is_noop ( ) {
689738 let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -794,7 +843,7 @@ devices:
794843 }
795844
796845 #[ test]
797- fn skips_root_additional_gid ( ) {
846+ fn rejects_root_additional_gid ( ) {
798847 let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
799848 write_spec (
800849 dir. path ( ) ,
@@ -809,14 +858,14 @@ devices:
809858"# ,
810859 ) ;
811860
812- let requirements = resolve_with_kind (
861+ let err = resolve_with_kind (
813862 & context ( dir. path ( ) , & [ "nvidia.com/gpu=0" ] ) ,
814863 & [ ] ,
815864 always_missing,
816865 )
817- . unwrap ( ) ;
866+ . unwrap_err ( ) ;
818867
819- assert_eq ! ( requirements . additional_gids , vec! [ 44 ] ) ;
868+ assert ! ( matches! ( err , CdiError :: RootAdditionalGid ) ) ;
820869 }
821870
822871 #[ test]
0 commit comments