@@ -3,20 +3,27 @@ use std::collections::VecDeque;
33use std:: io:: IsTerminal ;
44use std:: io:: Read ;
55use std:: io:: Write ;
6+ use std:: str:: FromStr ;
67
78use anyhow:: { Context , Result } ;
9+ use bootc_utils:: try_deserialize_timestamp;
810use canon_json:: CanonJsonSerialize ;
11+ use cap_std_ext:: cap_std;
912use fn_error_context:: context;
1013use ostree:: glib;
1114use ostree_container:: OstreeImageReference ;
1215use ostree_ext:: container as ostree_container;
16+ use ostree_ext:: container:: deploy:: ORIGIN_CONTAINER ;
1317use ostree_ext:: container_utils:: composefs_booted;
1418use ostree_ext:: container_utils:: ostree_booted;
19+ use ostree_ext:: containers_image_proxy;
1520use ostree_ext:: keyfileext:: KeyFileExt ;
1621use ostree_ext:: oci_spec;
1722use ostree_ext:: ostree;
23+ use tokio:: io:: AsyncReadExt ;
1824
1925use crate :: cli:: OutputFormat ;
26+ use crate :: spec:: ImageStatus ;
2027use crate :: spec:: { BootEntry , BootOrder , Host , HostSpec , HostStatus , HostType } ;
2128use crate :: spec:: { ImageReference , ImageSignature } ;
2229use crate :: store:: { CachedImageStatus , ContainerImageStore , Storage } ;
@@ -286,6 +293,140 @@ pub(crate) fn get_status(
286293 Ok ( ( deployments, host) )
287294}
288295
296+ #[ context( "Getting composefs deployment metadata" ) ]
297+ async fn boot_entry_from_composefs_deployment (
298+ origin : tini:: Ini ,
299+ booted : & String ,
300+ ) -> Result < BootEntry > {
301+ let image = match origin. get :: < String > ( "origin" , ORIGIN_CONTAINER ) {
302+ Some ( img_name_from_config) => {
303+ let ostree_img_ref = OstreeImageReference :: from_str ( & img_name_from_config) ?;
304+ let imgref = ostree_img_ref. imgref . to_string ( ) ;
305+
306+ let img_ref = ImageReference :: from ( ostree_img_ref) ;
307+
308+ let config = containers_image_proxy:: ImageProxyConfig :: default ( ) ;
309+ let proxy = containers_image_proxy:: ImageProxy :: new_with_config ( config) . await ?;
310+
311+ // The image might've been removed, so don't error if we can't get the image manifest
312+ let ( image_digest, version, architecture, created_at) =
313+ match proxy. open_image ( & imgref) . await . context ( "Opening image" ) {
314+ Ok ( img) => {
315+ let ( _, manifest) = proxy. fetch_manifest ( & img) . await ?;
316+ let ( mut reader, driver) =
317+ proxy. get_descriptor ( & img, manifest. config ( ) ) . await ?;
318+
319+ let mut buf = Vec :: with_capacity ( manifest. config ( ) . size ( ) as usize ) ;
320+ buf. resize ( manifest. config ( ) . size ( ) as usize , 0 ) ;
321+ reader. read_exact ( & mut buf) . await ?;
322+ driver. await ?;
323+
324+ let config: oci_spec:: image:: ImageConfiguration =
325+ serde_json:: from_slice ( & buf) ?;
326+
327+ let digest = manifest. config ( ) . digest ( ) . to_string ( ) ;
328+ let arch = config. architecture ( ) . to_string ( ) ;
329+ let created = config. created ( ) . clone ( ) ;
330+ let version = manifest
331+ . annotations ( )
332+ . as_ref ( )
333+ . and_then ( |a| a. get ( oci_spec:: image:: ANNOTATION_VERSION ) . cloned ( ) ) ;
334+
335+ ( digest, version, arch, created)
336+ }
337+
338+ Err ( e) => {
339+ tracing:: debug!( "Failed to open image {img_ref}, because {e:?}" ) ;
340+ ( "" . into ( ) , None , "" . into ( ) , None )
341+ }
342+ } ;
343+
344+ let timestamp = if let Some ( created_at) = created_at {
345+ try_deserialize_timestamp ( & created_at)
346+ } else {
347+ None
348+ } ;
349+
350+ let image_status = ImageStatus {
351+ image : img_ref,
352+ version,
353+ timestamp,
354+ image_digest,
355+ architecture,
356+ } ;
357+
358+ Some ( image_status)
359+ }
360+
361+ // Wasn't booted using a container image. Do nothing
362+ None => None ,
363+ } ;
364+
365+ let e = BootEntry {
366+ image,
367+ cached_update : None ,
368+ incompatible : false ,
369+ pinned : false ,
370+ store : None ,
371+ ostree : None ,
372+ composefs : Some ( crate :: spec:: BootEntryComposefs {
373+ verity : booted. into ( ) ,
374+ } ) ,
375+ } ;
376+
377+ return Ok ( e) ;
378+ }
379+
380+ #[ context( "Getting composefs deployment status" ) ]
381+ pub ( crate ) async fn composefs_deployment_status ( ) -> Result < Host > {
382+ let cmdline = crate :: kernel:: parse_cmdline ( ) ?;
383+ let booted = cmdline. iter ( ) . find_map ( |x| x. strip_prefix ( "composefs=" ) ) ;
384+
385+ let Some ( booted) = booted else {
386+ anyhow:: bail!( "Failed to find composefs parameter in kernel cmdline" ) ;
387+ } ;
388+
389+ let sysroot = cap_std:: fs:: Dir :: open_ambient_dir ( "/sysroot" , cap_std:: ambient_authority ( ) )
390+ . context ( "Opening sysroot" ) ?;
391+ let deployments = sysroot
392+ . read_dir ( "state/deploy" )
393+ . context ( "Reading sysroot state/deploy" ) ?;
394+
395+ let host_spec = HostSpec {
396+ image : None ,
397+ boot_order : BootOrder :: Default ,
398+ } ;
399+
400+ let mut host = Host :: new ( host_spec) ;
401+
402+ for depl in deployments {
403+ let depl = depl?;
404+
405+ let depl_file_name = depl. file_name ( ) ;
406+ let depl_file_name = depl_file_name. to_string_lossy ( ) ;
407+
408+ // read the origin file
409+ let config = depl
410+ . open_dir ( )
411+ . with_context ( || format ! ( "Failed to open {depl_file_name}" ) ) ?
412+ . read_to_string ( format ! ( "{depl_file_name}.origin" ) )
413+ . with_context ( || format ! ( "Reading file {depl_file_name}.origin" ) ) ?;
414+
415+ let ini = tini:: Ini :: from_string ( & config)
416+ . with_context ( || format ! ( "Failed to parse file {depl_file_name}.origin as ini" ) ) ?;
417+
418+ let boot_entry = boot_entry_from_composefs_deployment ( ini, & booted. into ( ) ) . await ?;
419+
420+ if depl. file_name ( ) == booted {
421+ host. spec . image = boot_entry. image . as_ref ( ) . map ( |x| x. image . clone ( ) ) ;
422+ host. status . booted = Some ( boot_entry) ;
423+ continue ;
424+ }
425+ }
426+
427+ Ok ( host)
428+ }
429+
289430/// Implementation of the `bootc status` CLI command.
290431#[ context( "Status" ) ]
291432pub ( crate ) async fn status ( opts : super :: cli:: StatusOpts ) -> Result < ( ) > {
@@ -300,46 +441,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
300441 let ( _deployments, host) = get_status ( & sysroot, booted_deployment. as_ref ( ) ) ?;
301442 host
302443 } else if composefs_booted ( ) ? {
303- let dir_contents = std:: fs:: read_dir ( "/sysroot/composefs/images" ) ?;
304-
305- let host_spec = HostSpec {
306- image : Some ( ImageReference {
307- image : "" . into ( ) ,
308- transport : "" . into ( ) ,
309- signature : None ,
310- } ) ,
311- boot_order : BootOrder :: Default ,
312- } ;
313-
314- let mut host = Host :: new ( host_spec) ;
315-
316- let cmdline = crate :: kernel:: parse_cmdline ( ) ?;
317- let booted = cmdline. iter ( ) . find_map ( |x| x. strip_prefix ( "composefs=" ) ) ;
318-
319- let Some ( booted) = booted else {
320- anyhow:: bail!( "Failed to find composefs parameter in kernel cmdline" ) ;
321- } ;
322-
323- host. status = HostStatus {
324- staged : None ,
325- booted : Some ( BootEntry {
326- image : None ,
327- cached_update : None ,
328- incompatible : false ,
329- pinned : false ,
330- store : None ,
331- ostree : None ,
332- composefs : Some ( crate :: spec:: BootEntryComposefs {
333- verity : booted. into ( ) ,
334- } ) ,
335- } ) ,
336- other_deployments : vec ! [ ] ,
337- rollback : None ,
338- rollback_queued : false ,
339- ty : None ,
340- } ;
341-
342- host
444+ composefs_deployment_status ( ) . await ?
343445 } else {
344446 Default :: default ( )
345447 } ;
0 commit comments