@@ -126,6 +126,8 @@ struct OstreeTarWriter<'a, W: std::io::Write> {
126126 wrote_dirmeta : HashSet < String > ,
127127 wrote_content : HashSet < String > ,
128128 wrote_xattrs : HashSet < String > ,
129+ /// Tracks the number of links to each xattrs object (checksum -> (link count, suffix))
130+ xattrs_link_counts : std:: collections:: HashMap < String , ( u32 , u32 ) > ,
129131}
130132
131133pub ( crate ) fn object_path ( objtype : ostree:: ObjectType , checksum : & str ) -> Utf8PathBuf {
@@ -141,9 +143,13 @@ pub(crate) fn object_path(objtype: ostree::ObjectType, checksum: &str) -> Utf8Pa
141143 format ! ( "{OSTREEDIR}/repo/objects/{first}/{rest}.{suffix}" ) . into ( )
142144}
143145
144- fn v1_xattrs_object_path ( checksum : & str ) -> Utf8PathBuf {
146+ fn v1_xattrs_object_path_with_suffix ( checksum : & str , suffix : u32 ) -> Utf8PathBuf {
145147 let ( first, rest) = checksum. split_at ( 2 ) ;
146- format ! ( "{OSTREEDIR}/repo/objects/{first}/{rest}.file-xattrs" ) . into ( )
148+ if suffix == 0 {
149+ format ! ( "{OSTREEDIR}/repo/objects/{first}/{rest}.file-xattrs" ) . into ( )
150+ } else {
151+ format ! ( "{OSTREEDIR}/repo/objects/{first}/{rest}.file-xattrs.{suffix}" ) . into ( )
152+ }
147153}
148154
149155fn v1_xattrs_link_object_path ( checksum : & str ) -> Utf8PathBuf {
@@ -197,6 +203,7 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
197203 wrote_dirtree : HashSet :: new ( ) ,
198204 wrote_content : HashSet :: new ( ) ,
199205 wrote_xattrs : HashSet :: new ( ) ,
206+ xattrs_link_counts : std:: collections:: HashMap :: new ( ) ,
200207 } ;
201208 Ok ( r)
202209 }
@@ -383,6 +390,8 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
383390 /// Return whether content was written.
384391 #[ context( "Writing xattrs" ) ]
385392 fn append_ostree_xattrs ( & mut self , checksum : & str , xattrs : & glib:: Variant ) -> Result < bool > {
393+ const XATTRS_LINK_LIMIT : u32 = 256 ;
394+
386395 let xattrs_data = xattrs. data_as_bytes ( ) ;
387396 let xattrs_data = xattrs_data. as_ref ( ) ;
388397
@@ -391,13 +400,40 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
391400 hex:: encode ( digest)
392401 } ;
393402
394- let path = v1_xattrs_object_path ( & xattrs_checksum) ;
395- // Write xattrs content into a separate `.file-xattrs` object.
396- if !self . wrote_xattrs . contains ( & xattrs_checksum) {
397- let inserted = self . wrote_xattrs . insert ( xattrs_checksum) ;
398- debug_assert ! ( inserted) ;
399- self . append_default_data ( & path, xattrs_data) ?;
403+ // Check if we've already written this xattrs object or need to create a new one
404+ let ( link_count, suffix) = self
405+ . xattrs_link_counts
406+ . get ( & xattrs_checksum)
407+ . copied ( )
408+ . unwrap_or ( ( 0 , 0 ) ) ;
409+
410+ // Determine which suffix to use based on link count
411+ let ( current_suffix, should_create_new) = if link_count >= XATTRS_LINK_LIMIT {
412+ ( suffix + 1 , true )
413+ } else {
414+ ( suffix, link_count == 0 )
415+ } ;
416+
417+ let path = v1_xattrs_object_path_with_suffix ( & xattrs_checksum, current_suffix) ;
418+
419+ // Write xattrs content into a separate `.file-xattrs` object if needed
420+ if should_create_new {
421+ // For the first object (suffix 0) or when we need a new suffixed object
422+ let key = format ! ( "{}.{}" , xattrs_checksum, current_suffix) ;
423+ if !self . wrote_xattrs . contains ( & key) {
424+ let inserted = self . wrote_xattrs . insert ( key) ;
425+ debug_assert ! ( inserted) ;
426+ self . append_default_data ( & path, xattrs_data) ?;
427+ // Reset link count for new suffix
428+ self . xattrs_link_counts
429+ . insert ( xattrs_checksum. clone ( ) , ( 1 , current_suffix) ) ;
430+ }
431+ } else {
432+ // Increment link count for existing xattrs object
433+ self . xattrs_link_counts
434+ . insert ( xattrs_checksum. clone ( ) , ( link_count + 1 , current_suffix) ) ;
400435 }
436+
401437 // Write a `.file-xattrs-link` which links the file object to
402438 // the corresponding detached xattrs.
403439 {
@@ -936,8 +972,13 @@ mod tests {
936972 fn test_v1_xattrs_object_path ( ) {
937973 let checksum = "b8627e3ef0f255a322d2bd9610cfaaacc8f122b7f8d17c0e7e3caafa160f9fc7" ;
938974 let expected = "sysroot/ostree/repo/objects/b8/627e3ef0f255a322d2bd9610cfaaacc8f122b7f8d17c0e7e3caafa160f9fc7.file-xattrs" ;
939- let output = v1_xattrs_object_path ( checksum) ;
975+ let output = v1_xattrs_object_path_with_suffix ( checksum, 0 ) ;
940976 assert_eq ! ( & output, expected) ;
977+
978+ // Test with suffix
979+ let expected_with_suffix = "sysroot/ostree/repo/objects/b8/627e3ef0f255a322d2bd9610cfaaacc8f122b7f8d17c0e7e3caafa160f9fc7.file-xattrs.1" ;
980+ let output_with_suffix = v1_xattrs_object_path_with_suffix ( checksum, 1 ) ;
981+ assert_eq ! ( & output_with_suffix, expected_with_suffix) ;
941982 }
942983
943984 #[ test]
@@ -947,4 +988,86 @@ mod tests {
947988 let output = v1_xattrs_link_object_path ( checksum) ;
948989 assert_eq ! ( & output, expected) ;
949990 }
991+
992+ #[ test]
993+ fn test_append_ostree_xattrs ( ) {
994+ // Create a temporary in-memory tar builder
995+ let mut tar_data = Vec :: new ( ) ;
996+ let mut tar_builder = tar:: Builder :: new ( & mut tar_data) ;
997+
998+ // Create a minimal OstreeTarWriter for testing
999+ // We bypass the complex commit creation and just create the struct directly
1000+ let tmp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
1001+ let repo_path = tmp_dir. path ( ) . join ( "repo" ) ;
1002+ let repo = ostree:: Repo :: new_for_path ( & repo_path) ;
1003+ repo. create ( ostree:: RepoMode :: Archive , gio:: Cancellable :: NONE )
1004+ . unwrap ( ) ;
1005+
1006+ // Create a dummy commit - we just need something valid
1007+ let empty_variant = glib:: Variant :: from ( ( ) ) ;
1008+
1009+ // Manually create an OstreeTarWriter instance
1010+ let mut writer = OstreeTarWriter {
1011+ repo : & repo,
1012+ commit_checksum : "dummy" ,
1013+ commit_object : empty_variant,
1014+ out : & mut tar_builder,
1015+ options : ExportOptions :: default ( ) ,
1016+ wrote_initdirs : false ,
1017+ structure_only : false ,
1018+ wrote_vartmp : false ,
1019+ wrote_dirtree : HashSet :: new ( ) ,
1020+ wrote_dirmeta : HashSet :: new ( ) ,
1021+ wrote_content : HashSet :: new ( ) ,
1022+ wrote_xattrs : HashSet :: new ( ) ,
1023+ xattrs_link_counts : std:: collections:: HashMap :: new ( ) ,
1024+ } ;
1025+
1026+ // Create xattrs in the expected format for ostree (a(ayay))
1027+ // Format: array of (name bytes with null terminator, value bytes)
1028+ let xattrs_data: Vec < ( Vec < u8 > , Vec < u8 > ) > =
1029+ vec ! [ ( b"user.test\0 " . to_vec( ) , b"test_value" . to_vec( ) ) ] ;
1030+ let xattrs_variant = glib:: Variant :: from ( xattrs_data) ;
1031+
1032+ // Test append_ostree_xattrs with a test checksum
1033+ let test_checksum = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" ;
1034+ let result = writer
1035+ . append_ostree_xattrs ( test_checksum, & xattrs_variant)
1036+ . unwrap ( ) ;
1037+ assert ! ( result) ;
1038+
1039+ // Verify that xattrs were written
1040+ let xattrs_checksum = {
1041+ let xattrs_data = xattrs_variant. data_as_bytes ( ) ;
1042+ let digest =
1043+ openssl:: hash:: hash ( openssl:: hash:: MessageDigest :: sha256 ( ) , xattrs_data. as_ref ( ) )
1044+ . unwrap ( ) ;
1045+ hex:: encode ( digest)
1046+ } ;
1047+ assert ! ( writer
1048+ . wrote_xattrs
1049+ . contains( & format!( "{}.0" , xattrs_checksum) ) ) ;
1050+
1051+ // Test with multiple calls to check link count behavior
1052+ for i in 1 ..300 {
1053+ let checksum = format ! ( "test{:064}" , i) ;
1054+ writer
1055+ . append_ostree_xattrs ( & checksum, & xattrs_variant)
1056+ . unwrap ( ) ;
1057+ }
1058+
1059+ // Verify that multiple xattrs objects were created when limit was exceeded
1060+ // Should have created suffix 0 and suffix 1 objects
1061+ assert ! ( writer
1062+ . wrote_xattrs
1063+ . contains( & format!( "{}.0" , xattrs_checksum) ) ) ;
1064+ assert ! ( writer
1065+ . wrote_xattrs
1066+ . contains( & format!( "{}.1" , xattrs_checksum) ) ) ;
1067+
1068+ // Verify the link counts
1069+ let ( link_count, suffix) = writer. xattrs_link_counts . get ( & xattrs_checksum) . unwrap ( ) ;
1070+ assert_eq ! ( * suffix, 1 ) ;
1071+ assert ! ( * link_count == 44 ) ;
1072+ }
9501073}
0 commit comments