@@ -3,19 +3,25 @@ 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 cap_std_ext:: cap_std;
810use fn_error_context:: context;
911use ostree:: glib;
1012use ostree_container:: OstreeImageReference ;
1113use ostree_ext:: container as ostree_container;
14+ use ostree_ext:: container:: deploy:: ORIGIN_CONTAINER ;
1215use ostree_ext:: container_utils:: composefs_booted;
1316use ostree_ext:: container_utils:: ostree_booted;
17+ use ostree_ext:: containers_image_proxy;
1418use ostree_ext:: keyfileext:: KeyFileExt ;
1519use ostree_ext:: oci_spec;
1620use ostree_ext:: ostree;
21+ use tokio:: io:: AsyncReadExt ;
1722
1823use crate :: cli:: OutputFormat ;
24+ use crate :: spec:: ImageStatus ;
1925use crate :: spec:: { BootEntry , BootOrder , Host , HostSpec , HostStatus , HostType } ;
2026use crate :: spec:: { ImageReference , ImageSignature } ;
2127use crate :: store:: { CachedImageStatus , ContainerImageStore , Storage } ;
@@ -287,6 +293,140 @@ pub(crate) fn get_status(
287293 Ok ( ( deployments, host) )
288294}
289295
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+
290430/// Implementation of the `bootc status` CLI command.
291431#[ context( "Status" ) ]
292432pub ( crate ) async fn status ( opts : super :: cli:: StatusOpts ) -> Result < ( ) > {
@@ -301,45 +441,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
301441 let ( _deployments, host) = get_status ( & sysroot, booted_deployment. as_ref ( ) ) ?;
302442 host
303443 } else if composefs_booted ( ) ? {
304- let dir_contents = std:: fs:: read_dir ( "/sysroot/composefs/images" ) ?;
305-
306- let host_spec = HostSpec {
307- image : Some ( ImageReference {
308- image : "" . into ( ) ,
309- transport : "" . into ( ) ,
310- signature : None ,
311- } ) ,
312- boot_order : BootOrder :: Default ,
313- } ;
314-
315- let mut host = Host :: new ( host_spec) ;
316-
317- let cmdline = crate :: kernel:: parse_cmdline ( ) ?;
318- let booted = cmdline. iter ( ) . find_map ( |x| x. strip_prefix ( "composefs=" ) ) ;
319-
320- let Some ( booted) = booted else {
321- anyhow:: bail!( "Failed to find composefs parameter in kernel cmdline" ) ;
322- } ;
323-
324- host. status = HostStatus {
325- staged : None ,
326- booted : Some ( BootEntry {
327- image : None ,
328- cached_update : None ,
329- incompatible : false ,
330- pinned : false ,
331- store : None ,
332- ostree : None ,
333- composefs : Some ( crate :: spec:: BootEntryComposefs {
334- verity : booted. into ( ) ,
335- } ) ,
336- } ) ,
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