@@ -3,20 +3,21 @@ use std::{collections::HashSet, io::Read, sync::OnceLock};
33use anyhow:: { Context , Result } ;
44use bootc_kernel_cmdline:: utf8:: Cmdline ;
55use bootc_mount:: inspect_filesystem;
6+ use cfsctl:: composefs:: fsverity:: Sha512HashValue ;
7+ use cfsctl:: composefs_oci:: OciImage ;
68use fn_error_context:: context;
79use serde:: { Deserialize , Serialize } ;
810
911use crate :: {
1012 bootc_composefs:: {
1113 boot:: BootType ,
12- repo:: get_imgref,
1314 selinux:: are_selinux_policies_compatible,
1415 state:: { get_composefs_usr_overlay_status, read_origin} ,
1516 utils:: { compute_store_boot_digest_for_uki, get_uki_cmdline} ,
1617 } ,
1718 composefs_consts:: {
18- COMPOSEFS_CMDLINE , ORIGIN_KEY_BOOT_DIGEST , TYPE1_ENT_PATH , TYPE1_ENT_PATH_STAGED , USER_CFG ,
19- USER_CFG_STAGED ,
19+ COMPOSEFS_CMDLINE , ORIGIN_KEY_BOOT_DIGEST , ORIGIN_KEY_IMAGE , ORIGIN_KEY_MANIFEST_DIGEST ,
20+ TYPE1_ENT_PATH , TYPE1_ENT_PATH_STAGED , USER_CFG , USER_CFG_STAGED ,
2021 } ,
2122 install:: EFI_LOADER_INFO ,
2223 parsers:: {
@@ -358,57 +359,63 @@ pub(crate) fn get_bootloader() -> Result<Bootloader> {
358359 }
359360}
360361
361- /// Reads the .imginfo file for the provided deployment
362- #[ context( "Reading imginfo" ) ]
363- pub ( crate ) async fn get_imginfo (
364- storage : & Storage ,
365- deployment_id : & str ,
366- imgref : Option < & ImageReference > ,
367- ) -> Result < ImgConfigManifest > {
368- let imginfo_fname = format ! ( "{deployment_id}.imginfo" ) ;
362+ /// Retrieves the OCI manifest and config for a deployment from the composefs repository.
363+ ///
364+ /// The manifest digest is read from the deployment's `.origin` file,
365+ /// then `OciImage::open()` retrieves manifest+config from the composefs repo
366+ /// where composefs-rs stores them as splitstreams during pull.
367+ ///
368+ /// Falls back to reading legacy `.imginfo` files for backwards compatibility
369+ /// with deployments created before the manifest digest was stored in `.origin`.
370+ #[ context( "Reading image info for deployment {deployment_id}" ) ]
371+ pub ( crate ) fn get_imginfo ( storage : & Storage , deployment_id : & str ) -> Result < ImgConfigManifest > {
372+ let ini = read_origin ( & storage. physical_root , deployment_id) ?
373+ . ok_or_else ( || anyhow:: anyhow!( "No origin file for deployment {deployment_id}" ) ) ?;
374+
375+ // Try to read the manifest digest from the origin file (new path)
376+ if let Some ( manifest_digest) = ini. get :: < String > ( ORIGIN_KEY_IMAGE , ORIGIN_KEY_MANIFEST_DIGEST ) {
377+ let repo = storage. get_ensure_composefs ( ) ?;
378+ let oci_image = OciImage :: < Sha512HashValue > :: open ( & repo, & manifest_digest, None )
379+ . with_context ( || format ! ( "Opening OCI image for manifest {manifest_digest}" ) ) ?;
380+
381+ let manifest = oci_image. manifest ( ) . clone ( ) ;
382+ let config = oci_image
383+ . config ( )
384+ . cloned ( )
385+ . ok_or_else ( || anyhow:: anyhow!( "OCI image has no config (artifact?)" ) ) ?;
386+
387+ return Ok ( ImgConfigManifest { config, manifest } ) ;
388+ }
369389
390+ // Fallback: read legacy .imginfo file for deployments created before
391+ // the manifest digest was stored in .origin
370392 let depl_state_path = std:: path:: PathBuf :: from ( STATE_DIR_RELATIVE ) . join ( deployment_id) ;
371- let path = depl_state_path. join ( imginfo_fname) ;
393+ let imginfo_fname = format ! ( "{deployment_id}.imginfo" ) ;
394+ let path = depl_state_path. join ( & imginfo_fname) ;
372395
373396 let mut img_conf = storage
374397 . physical_root
375398 . open_optional ( & path)
376- . context ( "Failed to open file" ) ?;
399+ . with_context ( || format ! ( "Opening legacy {imginfo_fname}" ) ) ?;
377400
378401 let Some ( img_conf) = & mut img_conf else {
379- let imgref = imgref. ok_or_else ( || anyhow:: anyhow!( "No imgref or imginfo file found" ) ) ?;
380-
381- let container_details =
382- get_container_manifest_and_config ( & get_imgref ( & imgref. transport , & imgref. image ) )
383- . await ?;
384-
385- let state_dir = storage. physical_root . open_dir ( depl_state_path) ?;
386-
387- state_dir
388- . atomic_write (
389- format ! ( "{}.imginfo" , deployment_id) ,
390- serde_json:: to_vec ( & container_details) ?,
391- )
392- . context ( "Failed to write to .imginfo file" ) ?;
393-
394- let state_dir = state_dir. reopen_as_ownedfd ( ) ?;
395-
396- rustix:: fs:: fsync ( state_dir) . context ( "fsync" ) ?;
397-
398- return Ok ( container_details) ;
402+ anyhow:: bail!(
403+ "No manifest_digest in origin and no legacy .imginfo file \
404+ for deployment {deployment_id}"
405+ ) ;
399406 } ;
400407
401408 let mut buffer = String :: new ( ) ;
402409 img_conf. read_to_string ( & mut buffer) ?;
403410
404411 let img_conf = serde_json:: from_str :: < ImgConfigManifest > ( & buffer)
405- . context ( "Failed to parse file as JSON" ) ?;
412+ . context ( "Failed to parse .imginfo file as JSON" ) ?;
406413
407414 Ok ( img_conf)
408415}
409416
410417#[ context( "Getting composefs deployment metadata" ) ]
411- async fn boot_entry_from_composefs_deployment (
418+ fn boot_entry_from_composefs_deployment (
412419 storage : & Storage ,
413420 origin : tini:: Ini ,
414421 verity : & str ,
@@ -418,7 +425,7 @@ async fn boot_entry_from_composefs_deployment(
418425 let ostree_img_ref = OstreeImageReference :: from_str ( & img_name_from_config) ?;
419426 let img_ref = ImageReference :: from ( ostree_img_ref) ;
420427
421- let img_conf = get_imginfo ( storage, & verity, Some ( & img_ref ) ) . await ?;
428+ let img_conf = get_imginfo ( storage, & verity) ?;
422429
423430 let image_digest = img_conf. manifest . config ( ) . digest ( ) . to_string ( ) ;
424431 let architecture = img_conf. config . architecture ( ) . to_string ( ) ;
@@ -699,6 +706,11 @@ async fn composefs_deployment_status_from(
699706 // This is our source of truth
700707 let bootloader_entry_verity = list_bootloader_entries ( storage) ?;
701708
709+ let state_dir = storage
710+ . physical_root
711+ . open_dir ( STATE_DIR_RELATIVE )
712+ . with_context ( || format ! ( "Opening {STATE_DIR_RELATIVE}" ) ) ?;
713+
702714 let host_spec = HostSpec {
703715 image : None ,
704716 boot_order : BootOrder :: Default ,
@@ -727,11 +739,17 @@ async fn composefs_deployment_status_from(
727739 let mut extra_deployment_boot_entries: Vec < BootEntry > = Vec :: new ( ) ;
728740
729741 for verity_digest in bootloader_entry_verity {
730- let ini = read_origin ( & storage. physical_root , & verity_digest) ?
731- . ok_or_else ( || anyhow:: anyhow!( "No origin file for deployment {verity_digest}" ) ) ?;
742+ // read the origin file
743+ let config = state_dir
744+ . open_dir ( & verity_digest)
745+ . with_context ( || format ! ( "Failed to open {verity_digest}" ) ) ?
746+ . read_to_string ( format ! ( "{verity_digest}.origin" ) )
747+ . with_context ( || format ! ( "Reading file {verity_digest}.origin" ) ) ?;
748+
749+ let ini = tini:: Ini :: from_string ( & config)
750+ . with_context ( || format ! ( "Failed to parse file {verity_digest}.origin as ini" ) ) ?;
732751
733- let mut boot_entry =
734- boot_entry_from_composefs_deployment ( storage, ini, & verity_digest) . await ?;
752+ let mut boot_entry = boot_entry_from_composefs_deployment ( storage, ini, & verity_digest) ?;
735753
736754 // SAFETY: boot_entry.composefs will always be present
737755 let boot_type_from_origin = boot_entry. composefs . as_ref ( ) . unwrap ( ) . boot_type ;
0 commit comments