@@ -56,14 +56,35 @@ mode=bare-split-xattrs
5656/// System calls are expensive.
5757const BUF_CAPACITY : usize = 131072 ;
5858
59- /// Convert /usr/etc back to /etc
60- fn map_path ( p : & Utf8Path ) -> std:: borrow:: Cow < Utf8Path > {
61- match p. strip_prefix ( "./usr/etc" ) {
62- Ok ( r) => Cow :: Owned ( Utf8Path :: new ( "./etc" ) . join ( r) ) ,
59+ /// Convert `from` to `to`
60+ fn map_path_inner < ' p > (
61+ p : & ' p Utf8Path ,
62+ from : & ' _ str ,
63+ to : & ' _ str ,
64+ ) -> std:: borrow:: Cow < ' p , Utf8Path > {
65+ match p. strip_prefix ( from) {
66+ Ok ( r) => {
67+ if r. components ( ) . count ( ) > 0 {
68+ Cow :: Owned ( Utf8Path :: new ( to) . join ( r) )
69+ } else {
70+ Cow :: Owned ( Utf8PathBuf :: from ( to) )
71+ }
72+ }
6373 _ => Cow :: Borrowed ( p) ,
6474 }
6575}
6676
77+ /// Convert /usr/etc back to /etc
78+ fn map_path ( p : & Utf8Path ) -> std:: borrow:: Cow < Utf8Path > {
79+ map_path_inner ( p, "./usr/etc" , "./etc" )
80+ }
81+
82+ /// Convert etc to usr/etc
83+ /// Note: no leading '/' or './'
84+ fn unmap_path ( p : & Utf8Path ) -> std:: borrow:: Cow < Utf8Path > {
85+ map_path_inner ( p, "etc" , "usr/etc" )
86+ }
87+
6788/// Convert usr/etc back to etc for the tar stream.
6889fn map_path_v1 ( p : & Utf8Path ) -> & Utf8Path {
6990 debug_assert ! ( !p. starts_with( "/" ) && !p. starts_with( "." ) ) ;
@@ -615,6 +636,52 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
615636 . append_data ( & mut header, "var/tmp" , std:: io:: empty ( ) ) ?;
616637 Ok ( ( ) )
617638 }
639+
640+ fn write_parents_of (
641+ & mut self ,
642+ path : & Utf8Path ,
643+ cache : & mut HashSet < Utf8PathBuf > ,
644+ ) -> Result < ( ) > {
645+ let Some ( parent) = path. parent ( ) else {
646+ return Ok ( ( ) ) ;
647+ } ;
648+
649+ if parent. components ( ) . count ( ) == 0 {
650+ return Ok ( ( ) ) ;
651+ }
652+
653+ if cache. contains ( parent) {
654+ return Ok ( ( ) ) ;
655+ }
656+
657+ self . write_parents_of ( parent, cache) ?;
658+
659+ let inserted = cache. insert ( parent. to_owned ( ) ) ;
660+ debug_assert ! ( inserted) ;
661+
662+ let root = self
663+ . repo
664+ . read_commit ( & self . commit_checksum , gio:: Cancellable :: NONE ) ?
665+ . 0 ;
666+ let parent_file = root. resolve_relative_path ( unmap_path ( parent) . as_ref ( ) ) ;
667+ let queryattrs = "unix::*" ;
668+ let queryflags = gio:: FileQueryInfoFlags :: NOFOLLOW_SYMLINKS ;
669+ let stat = parent_file. query_info ( & queryattrs, queryflags, gio:: Cancellable :: NONE ) ?;
670+ let uid = stat. attribute_uint32 ( gio:: FILE_ATTRIBUTE_UNIX_UID ) ;
671+ let gid = stat. attribute_uint32 ( gio:: FILE_ATTRIBUTE_UNIX_GID ) ;
672+ let orig_mode = stat. attribute_uint32 ( gio:: FILE_ATTRIBUTE_UNIX_MODE ) ;
673+ let mode = self . filter_mode ( orig_mode) ;
674+
675+ let mut header = tar:: Header :: new_gnu ( ) ;
676+ header. set_entry_type ( tar:: EntryType :: Directory ) ;
677+ header. set_size ( 0 ) ;
678+ header. set_uid ( uid as u64 ) ;
679+ header. set_gid ( gid as u64 ) ;
680+ header. set_mode ( mode) ;
681+ self . out
682+ . append_data ( & mut header, parent, std:: io:: empty ( ) ) ?;
683+ Ok ( ( ) )
684+ }
618685}
619686
620687/// Recursively walk an OSTree commit and generate data into a `[tar::Builder]`
@@ -663,12 +730,17 @@ fn path_for_tar_v1(p: &Utf8Path) -> &Utf8Path {
663730fn write_chunk < W : std:: io:: Write > (
664731 writer : & mut OstreeTarWriter < W > ,
665732 chunk : chunking:: ChunkMapping ,
733+ create_parent_dirs : bool ,
666734) -> Result < ( ) > {
735+ let mut cache = std:: collections:: HashSet :: new ( ) ;
667736 for ( checksum, ( _size, paths) ) in chunk. into_iter ( ) {
668737 let ( objpath, h) = writer. append_content ( checksum. borrow ( ) ) ?;
669738 for path in paths. iter ( ) {
670739 let path = path_for_tar_v1 ( path) ;
671740 let h = h. clone ( ) ;
741+ if create_parent_dirs {
742+ writer. write_parents_of ( & path, & mut cache) ?;
743+ }
672744 writer. append_content_hardlink ( & objpath, h, path) ?;
673745 }
674746 }
@@ -681,13 +753,14 @@ pub(crate) fn export_chunk<W: std::io::Write>(
681753 commit : & str ,
682754 chunk : chunking:: ChunkMapping ,
683755 out : & mut tar:: Builder < W > ,
756+ create_parent_dirs : bool ,
684757) -> Result < ( ) > {
685758 // For chunking, we default to format version 1
686759 #[ allow( clippy:: needless_update) ]
687760 let opts = ExportOptions ;
688761 let writer = & mut OstreeTarWriter :: new ( repo, commit, out, opts) ?;
689762 writer. write_repo_structure ( ) ?;
690- write_chunk ( writer, chunk)
763+ write_chunk ( writer, chunk, create_parent_dirs )
691764}
692765
693766/// Output the last chunk in a chunking.
@@ -697,6 +770,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
697770 commit_checksum : & str ,
698771 remainder : chunking:: Chunk ,
699772 out : & mut tar:: Builder < W > ,
773+ create_parent_dirs : bool ,
700774) -> Result < ( ) > {
701775 let options = ExportOptions ;
702776 let writer = & mut OstreeTarWriter :: new ( repo, commit_checksum, out, options) ?;
@@ -705,7 +779,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
705779 writer. structure_only = true ;
706780 writer. write_commit ( ) ?;
707781 writer. structure_only = false ;
708- write_chunk ( writer, remainder. content )
782+ write_chunk ( writer, remainder. content , create_parent_dirs )
709783}
710784
711785/// Process an exported tar stream, and update the detached metadata.
@@ -799,19 +873,53 @@ mod tests {
799873
800874 #[ test]
801875 fn test_map_path ( ) {
802- assert_eq ! ( map_path( "/" . into( ) ) , Utf8Path :: new( "/" ) ) ;
803876 assert_eq ! (
804- map_path( "./usr/etc/blah" . into( ) ) ,
805- Utf8Path :: new( "./etc/blah" )
877+ map_path( "/" . into( ) ) . as_os_str( ) ,
878+ Utf8Path :: new( "/" ) . as_os_str( )
879+ ) ;
880+ assert_eq ! (
881+ map_path( "./usr/etc/blah" . into( ) ) . as_os_str( ) ,
882+ Utf8Path :: new( "./etc/blah" ) . as_os_str( )
806883 ) ;
807884 for unchanged in [ "boot" , "usr/bin" , "usr/lib/foo" ] . iter ( ) . map ( Utf8Path :: new) {
808- assert_eq ! ( unchanged, map_path_v1( unchanged) ) ;
885+ assert_eq ! ( unchanged. as_os_str ( ) , map_path_v1( unchanged) . as_os_str ( ) ) ;
809886 }
810887
811- assert_eq ! ( Utf8Path :: new( "etc" ) , map_path_v1( Utf8Path :: new( "usr/etc" ) ) ) ;
812888 assert_eq ! (
813- Utf8Path :: new( "etc/foo" ) ,
814- map_path_v1( Utf8Path :: new( "usr/etc/foo" ) )
889+ Utf8Path :: new( "etc" ) . as_os_str( ) ,
890+ map_path_v1( Utf8Path :: new( "usr/etc" ) ) . as_os_str( )
891+ ) ;
892+ assert_eq ! (
893+ Utf8Path :: new( "etc/foo" ) . as_os_str( ) ,
894+ map_path_v1( Utf8Path :: new( "usr/etc/foo" ) ) . as_os_str( )
895+ ) ;
896+ }
897+
898+ #[ test]
899+ fn test_unmap_path ( ) {
900+ assert_eq ! (
901+ unmap_path( "/" . into( ) ) . as_os_str( ) ,
902+ Utf8Path :: new( "/" ) . as_os_str( )
903+ ) ;
904+ assert_eq ! (
905+ unmap_path( "/etc" . into( ) ) . as_os_str( ) ,
906+ Utf8Path :: new( "/etc" ) . as_os_str( )
907+ ) ;
908+ assert_eq ! (
909+ unmap_path( "/usr/etc" . into( ) ) . as_os_str( ) ,
910+ Utf8Path :: new( "/usr/etc" ) . as_os_str( )
911+ ) ;
912+ assert_eq ! (
913+ unmap_path( "usr/etc" . into( ) ) . as_os_str( ) ,
914+ Utf8Path :: new( "usr/etc" ) . as_os_str( )
915+ ) ;
916+ assert_eq ! (
917+ unmap_path( "etc" . into( ) ) . as_os_str( ) ,
918+ Utf8Path :: new( "usr/etc" ) . as_os_str( )
919+ ) ;
920+ assert_eq ! (
921+ unmap_path( "etc/blah" . into( ) ) . as_os_str( ) ,
922+ Utf8Path :: new( "usr/etc/blah" ) . as_os_str( )
815923 ) ;
816924 }
817925
0 commit comments