@@ -1007,6 +1007,82 @@ 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
1030+ . open_dir ( "tmp" )
1031+ . context ( "Opening ostree repo tmp/" ) ?;
1032+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new_in ( & repo_tmp) ?;
1033+ let image_name = "image.cfs" ;
1034+
1035+ // Call checkout_composefs via FFI directly; the high-level ostree
1036+ // crate (0.20.x) has a broken feature ladder that omits v2024_7.
1037+ #[ allow( unsafe_code) ]
1038+ {
1039+ use glib:: translate:: * ;
1040+ use std:: ffi:: CString ;
1041+ let c_path = CString :: new ( image_name) . unwrap ( ) ;
1042+ let c_commit = CString :: new ( commit) . context ( "Invalid commit string" ) ?;
1043+ let mut error = std:: ptr:: null_mut ( ) ;
1044+ // SAFETY: all pointers are valid; the repo, path, and commit are
1045+ // borrowed for the duration of the call.
1046+ let ok = unsafe {
1047+ ostree:: ffi:: ostree_repo_checkout_composefs (
1048+ repo. to_glib_none ( ) . 0 ,
1049+ std:: ptr:: null_mut ( ) ,
1050+ td. as_raw_fd ( ) ,
1051+ c_path. as_ptr ( ) ,
1052+ c_commit. as_ptr ( ) ,
1053+ std:: ptr:: null_mut ( ) ,
1054+ & mut error,
1055+ )
1056+ } ;
1057+ if ok == glib:: ffi:: GFALSE {
1058+ // SAFETY: on failure, error is set by the C function.
1059+ return Err ( unsafe { glib:: Error :: from_glib_full ( error) } )
1060+ . context ( "checkout_composefs" ) ;
1061+ }
1062+ }
1063+
1064+ // Open the erofs image and the ostree objects directory.
1065+ let image_fd = td
1066+ . open ( image_name)
1067+ . context ( "Opening composefs image" ) ?
1068+ . into ( ) ;
1069+ let objects_fd = repo_dir
1070+ . open_dir ( "objects" )
1071+ . context ( "Opening ostree objects dir" ) ?;
1072+
1073+ // Mount via composefs: erofs metadata + ostree objects as data store.
1074+ let mount_fd = composefs_fsmount (
1075+ image_fd,
1076+ commit,
1077+ objects_fd,
1078+ false , // no fsverity requirement for read-only inspection
1079+ & MountOptions :: default ( ) ,
1080+ )
1081+ . context ( "composefs_fsmount" ) ?;
1082+
1083+ Dir :: reopen_dir ( & mount_fd) . context ( "Reopening composefs mount as Dir" )
1084+ }
1085+
10101086/// Stage (queue deployment of) a fetched container image.
10111087#[ context( "Staging" ) ]
10121088pub ( crate ) async fn stage (
@@ -1054,9 +1130,9 @@ pub(crate) async fn stage(
10541130
10551131 subtask. completed = true ;
10561132 subtasks. push ( subtask. clone ( ) ) ;
1057- subtask. subtask = "deploying " . into ( ) ;
1058- subtask. id = "deploying " . into ( ) ;
1059- subtask. description = "Deploying Image " . into ( ) ;
1133+ subtask. subtask = "bound_images " . into ( ) ;
1134+ subtask. id = "bound_images " . into ( ) ;
1135+ subtask. description = "Pulling Bound Images " . into ( ) ;
10601136 subtask. completed = false ;
10611137 prog. send ( Event :: ProgressSteps {
10621138 task : "staging" . into ( ) ,
@@ -1072,15 +1148,20 @@ pub(crate) async fn stage(
10721148 . collect ( ) ,
10731149 } )
10741150 . await ;
1075- let origin = origin_from_imageref ( spec. image ) ?;
1076- let deployment =
1077- crate :: deploy:: deploy ( sysroot, from, image, & origin, lock_finalization) . await ?;
1151+ // Pull bound images *before* staging by mounting the ostree commit
1152+ // as a composefs filesystem to read the image specs. This way a pull
1153+ // failure never results in a staged deployment at all.
1154+ let repo = sysroot. get_ostree ( ) ?. repo ( ) ;
1155+ let commit_root = mount_ostree_commit ( & repo, & image. ostree_commit ) ?;
1156+ let bound_images = crate :: boundimage:: query_bound_images ( & commit_root) ?;
1157+ drop ( commit_root) ;
1158+ crate :: boundimage:: pull_images ( sysroot, bound_images) . await ?;
10781159
10791160 subtask. completed = true ;
10801161 subtasks. push ( subtask. clone ( ) ) ;
1081- subtask. subtask = "bound_images " . into ( ) ;
1082- subtask. id = "bound_images " . into ( ) ;
1083- subtask. description = "Pulling Bound Images " . into ( ) ;
1162+ subtask. subtask = "deploying " . into ( ) ;
1163+ subtask. id = "deploying " . into ( ) ;
1164+ subtask. description = "Deploying Image " . into ( ) ;
10841165 subtask. completed = false ;
10851166 prog. send ( Event :: ProgressSteps {
10861167 task : "staging" . into ( ) ,
@@ -1096,7 +1177,9 @@ pub(crate) async fn stage(
10961177 . collect ( ) ,
10971178 } )
10981179 . await ;
1099- crate :: boundimage:: pull_bound_images ( sysroot, & deployment) . await ?;
1180+ let origin = origin_from_imageref ( spec. image ) ?;
1181+ let _deployment =
1182+ crate :: deploy:: deploy ( sysroot, from, image, & origin, lock_finalization) . await ?;
11001183
11011184 subtask. completed = true ;
11021185 subtasks. push ( subtask. clone ( ) ) ;
0 commit comments