@@ -1007,6 +1007,80 @@ impl MergeState {
10071007 }
10081008}
10091009
1010+ /// Mount an ostree commit as a composefs filesystem, returning a read-only
1011+ /// directory handle.
1012+ ///
1013+ /// This uses `checkout_composefs` to generate an erofs metadata image from
1014+ /// the commit, then mounts it via composefs with the ostree repo objects
1015+ /// as the backing data store. The returned `Dir` is a detached mount —
1016+ /// it stays alive as long as the fd is open.
1017+ ///
1018+ /// The erofs image is written into the ostree repo's own `tmp/` directory
1019+ /// (which lives on the real root filesystem) because erofs file-backed
1020+ /// mounts require a non-stacked backing filesystem.
1021+ #[ context( "Mounting ostree commit {commit}" ) ]
1022+ pub ( crate ) fn mount_ostree_commit ( repo : & ostree:: Repo , commit : & str ) -> Result < Dir > {
1023+ use composefs_ctl:: composefs:: mount:: { MountOptions , composefs_fsmount} ;
1024+ use std:: os:: fd:: AsRawFd ;
1025+
1026+ // Write the erofs image into the ostree repo's tmp/ directory so it
1027+ // lives on the real (non-stacked) filesystem.
1028+ let repo_dir = Dir :: reopen_dir ( & repo. dfd_borrow ( ) ) ?;
1029+ let repo_tmp = repo_dir. open_dir ( "tmp" ) . context ( "Opening ostree repo tmp/" ) ?;
1030+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new_in ( & repo_tmp) ?;
1031+ let image_name = "image.cfs" ;
1032+
1033+ // Call checkout_composefs via FFI directly; the high-level ostree
1034+ // crate (0.20.x) has a broken feature ladder that omits v2024_7.
1035+ #[ allow( unsafe_code) ]
1036+ {
1037+ use glib:: translate:: * ;
1038+ use std:: ffi:: CString ;
1039+ let c_path = CString :: new ( image_name) . unwrap ( ) ;
1040+ let c_commit = CString :: new ( commit) . context ( "Invalid commit string" ) ?;
1041+ let mut error = std:: ptr:: null_mut ( ) ;
1042+ // SAFETY: all pointers are valid; the repo, path, and commit are
1043+ // borrowed for the duration of the call.
1044+ let ok = unsafe {
1045+ ostree:: ffi:: ostree_repo_checkout_composefs (
1046+ repo. to_glib_none ( ) . 0 ,
1047+ std:: ptr:: null_mut ( ) ,
1048+ td. as_raw_fd ( ) ,
1049+ c_path. as_ptr ( ) ,
1050+ c_commit. as_ptr ( ) ,
1051+ std:: ptr:: null_mut ( ) ,
1052+ & mut error,
1053+ )
1054+ } ;
1055+ if ok == glib:: ffi:: GFALSE {
1056+ // SAFETY: on failure, error is set by the C function.
1057+ return Err ( unsafe { glib:: Error :: from_glib_full ( error) } )
1058+ . context ( "checkout_composefs" ) ;
1059+ }
1060+ }
1061+
1062+ // Open the erofs image and the ostree objects directory.
1063+ let image_fd = td
1064+ . open ( image_name)
1065+ . context ( "Opening composefs image" ) ?
1066+ . into ( ) ;
1067+ let objects_fd = repo_dir
1068+ . open_dir ( "objects" )
1069+ . context ( "Opening ostree objects dir" ) ?;
1070+
1071+ // Mount via composefs: erofs metadata + ostree objects as data store.
1072+ let mount_fd = composefs_fsmount (
1073+ image_fd,
1074+ commit,
1075+ objects_fd,
1076+ false , // no fsverity requirement for read-only inspection
1077+ & MountOptions :: default ( ) ,
1078+ )
1079+ . context ( "composefs_fsmount" ) ?;
1080+
1081+ Dir :: reopen_dir ( & mount_fd) . context ( "Reopening composefs mount as Dir" )
1082+ }
1083+
10101084/// Stage (queue deployment of) a fetched container image.
10111085#[ context( "Staging" ) ]
10121086pub ( crate ) async fn stage (
@@ -1072,32 +1146,19 @@ pub(crate) async fn stage(
10721146 . collect ( ) ,
10731147 } )
10741148 . await ;
1149+ // Pull bound images *before* staging by mounting the ostree commit
1150+ // as a composefs filesystem to read the image specs. This way a pull
1151+ // failure never results in a staged deployment at all.
1152+ let repo = sysroot. get_ostree ( ) ?. repo ( ) ;
1153+ let commit_root = mount_ostree_commit ( & repo, & image. ostree_commit ) ?;
1154+ let bound_images = crate :: boundimage:: query_bound_images ( & commit_root) ?;
1155+ drop ( commit_root) ;
1156+ crate :: boundimage:: pull_images ( sysroot, bound_images) . await ?;
1157+
10751158 let origin = origin_from_imageref ( spec. image ) ?;
1076- let deployment =
1159+ let _deployment =
10771160 crate :: deploy:: deploy ( sysroot, from, image, & origin, lock_finalization) . await ?;
10781161
1079- subtask. completed = true ;
1080- subtasks. push ( subtask. clone ( ) ) ;
1081- subtask. subtask = "bound_images" . into ( ) ;
1082- subtask. id = "bound_images" . into ( ) ;
1083- subtask. description = "Pulling Bound Images" . into ( ) ;
1084- subtask. completed = false ;
1085- prog. send ( Event :: ProgressSteps {
1086- task : "staging" . into ( ) ,
1087- description : "Deploying Image" . into ( ) ,
1088- id : image. manifest_digest . clone ( ) . as_ref ( ) . into ( ) ,
1089- steps_cached : 0 ,
1090- steps : 1 ,
1091- steps_total : 3 ,
1092- subtasks : subtasks
1093- . clone ( )
1094- . into_iter ( )
1095- . chain ( [ subtask. clone ( ) ] )
1096- . collect ( ) ,
1097- } )
1098- . await ;
1099- crate :: boundimage:: pull_bound_images ( sysroot, & deployment) . await ?;
1100-
11011162 subtask. completed = true ;
11021163 subtasks. push ( subtask. clone ( ) ) ;
11031164 subtask. subtask = "cleanup" . into ( ) ;
0 commit comments