1- use std:: { fs:: File , io :: Read , os:: fd:: AsRawFd } ;
1+ use std:: { fs:: File , os:: fd:: AsRawFd } ;
22
33use anyhow:: { Context , Result } ;
44use cap_std_ext:: cap_std:: { ambient_authority, fs:: Dir } ;
5- use composefs:: {
6- fsverity:: FsVerityHashValue ,
7- splitstream:: { SplitStreamData , SplitStreamReader } ,
8- tree:: { LeafContent , RegularFile } ,
9- } ;
10- use composefs_oci:: tar:: TarItem ;
5+ use composefs:: splitstream:: SplitStreamData ;
116use ocidir:: { oci_spec:: image:: Platform , OciDir } ;
127use ostree_ext:: container:: skopeo;
138use ostree_ext:: { container:: Transport , oci_spec:: image:: ImageConfiguration } ;
14- use tar:: { EntryType , Header } ;
9+ use tar:: EntryType ;
1510
11+ use crate :: image:: get_imgrefs_for_copy;
1612use crate :: {
1713 bootc_composefs:: {
1814 status:: { get_composefs_status, get_imginfo} ,
1915 update:: str_to_sha256digest,
2016 } ,
21- image:: IMAGE_DEFAULT ,
2217 store:: { BootedComposefs , Storage } ,
2318} ;
2419
25- fn get_entry_with_header < R : Read , ObjectID : FsVerityHashValue > (
26- reader : & mut SplitStreamReader < R , ObjectID > ,
27- ) -> anyhow:: Result < Option < ( Header , TarItem < ObjectID > ) > > {
28- let mut buf = [ 0u8 ; 512 ] ;
29- if !reader. read_inline_exact ( & mut buf) ? || buf == [ 0u8 ; 512 ] {
30- return Ok ( None ) ;
31- }
32-
33- let header = tar:: Header :: from_byte_slice ( & buf) ;
34-
35- let size = header. entry_size ( ) ?;
36-
37- let item = match reader. read_exact ( size as usize , ( ( size + 511 ) & !511 ) as usize ) ? {
38- SplitStreamData :: External ( id) => match header. entry_type ( ) {
39- EntryType :: Regular | EntryType :: Continuous => {
40- TarItem :: Leaf ( LeafContent :: Regular ( RegularFile :: External ( id, size) ) )
41- }
42- _ => anyhow:: bail!( "Unsupported external-chunked entry {header:?} {id:?}" ) ,
43- } ,
44-
45- SplitStreamData :: Inline ( content) => match header. entry_type ( ) {
46- EntryType :: Directory => TarItem :: Directory ,
47- // We do not care what the content is as we're re-archiving it anyway
48- _ => TarItem :: Leaf ( LeafContent :: Regular ( RegularFile :: Inline ( content) ) ) ,
49- } ,
50- } ;
51-
52- return Ok ( Some ( ( header. clone ( ) , item) ) ) ;
53- }
54-
5520/// Exports a composefs repository to a container image in containers-storage:
5621pub async fn export_repo_to_image (
5722 storage : & Storage ,
@@ -61,37 +26,7 @@ pub async fn export_repo_to_image(
6126) -> Result < ( ) > {
6227 let host = get_composefs_status ( storage, booted_cfs) . await ?;
6328
64- let booted_image = host
65- . status
66- . booted
67- . as_ref ( )
68- . ok_or_else ( || anyhow:: anyhow!( "Booted deployment not found" ) ) ?
69- . image
70- . as_ref ( )
71- . unwrap ( ) ;
72-
73- // If the target isn't specified, push to containers-storage + our default image
74- let dest_imgref = match target {
75- Some ( target) => ostree_ext:: container:: ImageReference {
76- transport : Transport :: ContainerStorage ,
77- name : target. to_owned ( ) ,
78- } ,
79- None => ostree_ext:: container:: ImageReference {
80- transport : Transport :: ContainerStorage ,
81- name : IMAGE_DEFAULT . into ( ) ,
82- } ,
83- } ;
84-
85- // If the source isn't specified, we use the booted image
86- let source = match source {
87- Some ( source) => ostree_ext:: container:: ImageReference :: try_from ( source)
88- . context ( "Parsing source image" ) ?,
89-
90- None => ostree_ext:: container:: ImageReference {
91- transport : Transport :: try_from ( booted_image. image . transport . as_str ( ) ) . unwrap ( ) ,
92- name : booted_image. image . image . clone ( ) ,
93- } ,
94- } ;
29+ let ( source, dest_imgref) = get_imgrefs_for_copy ( source, target) . await ?;
9530
9631 let mut depl_verity = None ;
9732
@@ -144,6 +79,8 @@ pub async fn export_repo_to_image(
14479 let mut new_manifest = imginfo. manifest . clone ( ) ;
14580 new_manifest. layers_mut ( ) . clear ( ) ;
14681
82+ let total_layers = config. rootfs ( ) . diff_ids ( ) . len ( ) ;
83+
14784 for ( idx, old_diff_id) in config. rootfs ( ) . diff_ids ( ) . iter ( ) . enumerate ( ) {
14885 let layer_sha256 = str_to_sha256digest ( old_diff_id) ?;
14986 let layer_verity = config_stream. lookup ( & layer_sha256) ?;
@@ -155,37 +92,60 @@ pub async fn export_repo_to_image(
15592 let mut layer_writer = oci_dir. create_layer ( None ) ?;
15693 layer_writer. follow_symlinks ( false ) ;
15794
158- while let Some ( ( header, entry) ) = get_entry_with_header ( & mut layer_stream) ? {
159- let hsize = header. size ( ) ? as usize ;
160- let mut v = vec ! [ 0 ; hsize] ;
161-
162- match & entry {
163- TarItem :: Leaf ( leaf_content) => {
164- match & leaf_content {
165- LeafContent :: Regular ( reg) => match reg {
166- RegularFile :: Inline ( items) => {
167- v[ ..hsize] . copy_from_slice ( items) ;
168- }
169-
170- RegularFile :: External ( obj_id, ..) => {
171- let mut file = File :: from ( booted_cfs. repo . open_object ( obj_id) ?) ;
172- file. read_exact ( & mut v) ?;
173- }
174- } ,
175-
176- // we don't need to write the data for symlinks.
177- // Same goes for devices, fifos and sockets
178- _ => { }
95+ let mut got_zero_block = false ;
96+
97+ loop {
98+ let mut buf = [ 0u8 ; 512 ] ;
99+
100+ if !layer_stream
101+ . read_inline_exact ( & mut buf)
102+ . context ( "Reading into buffer" ) ?
103+ {
104+ break ;
105+ }
106+
107+ let all_zeroes = buf. iter ( ) . all ( |x| * x == 0 ) ;
108+
109+ // EOF for tar
110+ if all_zeroes && got_zero_block {
111+ break ;
112+ } else if all_zeroes {
113+ got_zero_block = true ;
114+ continue ;
115+ }
116+
117+ got_zero_block = false ;
118+
119+ let header = tar:: Header :: from_byte_slice ( & buf) ;
120+
121+ let size = header. entry_size ( ) ?;
122+
123+ match layer_stream. read_exact ( size as usize , ( ( size + 511 ) & !511 ) as usize ) ? {
124+ SplitStreamData :: External ( obj_id) => match header. entry_type ( ) {
125+ EntryType :: Regular | EntryType :: Continuous => {
126+ let file = File :: from ( booted_cfs. repo . open_object ( & obj_id) ?) ;
127+
128+ layer_writer
129+ . append ( & header, file)
130+ . context ( "Failed to write external entry" ) ?;
179131 }
180- }
181132
182- // we don't need to write the data for hardlinks/dirs
183- TarItem :: Directory | TarItem :: Hardlink ( ..) => { }
184- } ;
133+ _ => anyhow:: bail!( "Unsupported external-chunked entry {header:?} {obj_id:?}" ) ,
134+ } ,
135+
136+ SplitStreamData :: Inline ( content) => match header. entry_type ( ) {
137+ EntryType :: Directory => {
138+ layer_writer. append ( & header, std:: io:: empty ( ) ) ?;
139+ }
185140
186- layer_writer
187- . append ( & header, v. as_slice ( ) )
188- . context ( "Failed to write entry" ) ?;
141+ // We do not care what the content is as we're re-archiving it anyway
142+ _ => {
143+ layer_writer
144+ . append ( & header, & * content)
145+ . context ( "Failed to write inline entry" ) ?;
146+ }
147+ } ,
148+ } ;
189149 }
190150
191151 layer_writer. finish ( ) ?;
@@ -196,7 +156,11 @@ pub async fn export_repo_to_image(
196156 . complete ( )
197157 . context ( "Writing layer to disk" ) ?;
198158
199- tracing:: debug!( "Wrote layer: {}" , layer. uncompressed_sha256_as_digest( ) ) ;
159+ tracing:: debug!(
160+ "Wrote layer: {layer_sha} #{layer_num}/{total_layers}" ,
161+ layer_sha = layer. uncompressed_sha256_as_digest( ) ,
162+ layer_num = idx + 1 ,
163+ ) ;
200164
201165 let previous_annotations = imginfo
202166 . manifest
0 commit comments