@@ -940,6 +940,135 @@ impl ImageImporter {
940940 } )
941941 }
942942
943+ /// Generate a single ostree commit that combines all layers, and also
944+ /// includes container image metadata such as the manifest and config.
945+ fn write_merge_commit_impl (
946+ repo : & ostree:: Repo ,
947+ base_commit : Option < & str > ,
948+ layer_commits : & [ String ] ,
949+ have_derived_layers : bool ,
950+ metadata : glib:: Variant ,
951+ timestamp : u64 ,
952+ ostree_ref : & str ,
953+ no_imgref : bool ,
954+ disable_gc : bool ,
955+ cancellable : Option < & gio:: Cancellable > ,
956+ ) -> Result < Box < LayeredImageState > > {
957+ use rustix:: fd:: AsRawFd ;
958+
959+ let txn = repo. auto_transaction ( cancellable) ?;
960+
961+ let devino = ostree:: RepoDevInoCache :: new ( ) ;
962+ let repodir = Dir :: reopen_dir ( & repo. dfd_borrow ( ) ) ?;
963+ let repo_tmp = repodir. open_dir ( "tmp" ) ?;
964+ let td = cap_std_ext:: cap_tempfile:: TempDir :: new_in ( & repo_tmp) ?;
965+
966+ let rootpath = "root" ;
967+ let checkout_mode = if repo. mode ( ) == ostree:: RepoMode :: Bare {
968+ ostree:: RepoCheckoutMode :: None
969+ } else {
970+ ostree:: RepoCheckoutMode :: User
971+ } ;
972+ let mut checkout_opts = ostree:: RepoCheckoutAtOptions {
973+ mode : checkout_mode,
974+ overwrite_mode : ostree:: RepoCheckoutOverwriteMode :: UnionFiles ,
975+ devino_to_csum_cache : Some ( devino. clone ( ) ) ,
976+ no_copy_fallback : true ,
977+ force_copy_zerosized : true ,
978+ process_whiteouts : false ,
979+ ..Default :: default ( )
980+ } ;
981+ if let Some ( base) = base_commit. as_ref ( ) {
982+ repo. checkout_at (
983+ Some ( & checkout_opts) ,
984+ ( * td) . as_raw_fd ( ) ,
985+ rootpath,
986+ & base,
987+ cancellable,
988+ )
989+ . context ( "Checking out base commit" ) ?;
990+ }
991+
992+ // Layer all subsequent commits
993+ checkout_opts. process_whiteouts = true ;
994+ for commit in layer_commits {
995+ tracing:: debug!( "Unpacking {commit}" ) ;
996+ repo. checkout_at (
997+ Some ( & checkout_opts) ,
998+ ( * td) . as_raw_fd ( ) ,
999+ rootpath,
1000+ & commit,
1001+ cancellable,
1002+ )
1003+ . with_context ( || format ! ( "Checking out layer {commit}" ) ) ?;
1004+ }
1005+
1006+ let root_dir = td. open_dir ( rootpath) ?;
1007+
1008+ let modifier =
1009+ ostree:: RepoCommitModifier :: new ( ostree:: RepoCommitModifierFlags :: empty ( ) , None ) ;
1010+ modifier. set_devino_cache ( & devino) ;
1011+ // If we have derived layers, then we need to handle the case where
1012+ // the derived layers include custom policy. Just relabel everything
1013+ // in this case.
1014+ if have_derived_layers {
1015+ let sepolicy = ostree:: SePolicy :: new_at ( root_dir. as_raw_fd ( ) , cancellable) ?;
1016+ tracing:: debug!( "labeling from merged tree" ) ;
1017+ modifier. set_sepolicy ( Some ( & sepolicy) ) ;
1018+ } else if let Some ( base) = base_commit. as_ref ( ) {
1019+ tracing:: debug!( "labeling from base tree" ) ;
1020+ // TODO: We can likely drop this; we know all labels should be pre-computed.
1021+ modifier. set_sepolicy_from_commit ( repo, & base, cancellable) ?;
1022+ } else {
1023+ panic ! ( "Unexpected state: no derived layers and no base" )
1024+ }
1025+
1026+ cleanup_root ( & root_dir) ?;
1027+
1028+ let mt = ostree:: MutableTree :: new ( ) ;
1029+ repo. write_dfd_to_mtree (
1030+ ( * td) . as_raw_fd ( ) ,
1031+ rootpath,
1032+ & mt,
1033+ Some ( & modifier) ,
1034+ cancellable,
1035+ )
1036+ . context ( "Writing merged filesystem to mtree" ) ?;
1037+
1038+ let merged_root = repo
1039+ . write_mtree ( & mt, cancellable)
1040+ . context ( "Writing mtree" ) ?;
1041+ let merged_root = merged_root. downcast :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
1042+ // The merge has the base commit as a parent, if it exists. See
1043+ // https://github.com/ostreedev/ostree/pull/3523
1044+ let parent = base_commit. as_deref ( ) ;
1045+ let merged_commit = repo
1046+ . write_commit_with_time (
1047+ parent,
1048+ None ,
1049+ None ,
1050+ Some ( & metadata) ,
1051+ & merged_root,
1052+ timestamp,
1053+ cancellable,
1054+ )
1055+ . context ( "Writing commit" ) ?;
1056+ if !no_imgref {
1057+ repo. transaction_set_ref ( None , ostree_ref, Some ( merged_commit. as_str ( ) ) ) ;
1058+ }
1059+ txn. commit ( cancellable) ?;
1060+
1061+ if !disable_gc {
1062+ let n: u32 = gc_image_layers_impl ( repo, cancellable) ?;
1063+ tracing:: debug!( "pruned {n} layers" ) ;
1064+ }
1065+
1066+ // Here we re-query state just to run through the same code path,
1067+ // though it'd be cheaper to synthesize it from the data we already have.
1068+ let state = query_image_commit ( repo, & merged_commit) ?;
1069+ Ok ( state)
1070+ }
1071+
9431072 /// Import a layered container image.
9441073 ///
9451074 /// If enabled, this will also prune unused container image layers.
@@ -1080,121 +1209,18 @@ impl ImageImporter {
10801209 let repo = self . repo ;
10811210 let mut state = crate :: tokio_util:: spawn_blocking_cancellable_flatten (
10821211 move |cancellable| -> Result < Box < LayeredImageState > > {
1083- use rustix:: fd:: AsRawFd ;
1084-
1085- let cancellable = Some ( cancellable) ;
1086- let repo = & repo;
1087- let txn = repo. auto_transaction ( cancellable) ?;
1088-
1089- let devino = ostree:: RepoDevInoCache :: new ( ) ;
1090- let repodir = Dir :: reopen_dir ( & repo. dfd_borrow ( ) ) ?;
1091- let repo_tmp = repodir. open_dir ( "tmp" ) ?;
1092- let td = cap_std_ext:: cap_tempfile:: TempDir :: new_in ( & repo_tmp) ?;
1093-
1094- let rootpath = "root" ;
1095- let checkout_mode = if repo. mode ( ) == ostree:: RepoMode :: Bare {
1096- ostree:: RepoCheckoutMode :: None
1097- } else {
1098- ostree:: RepoCheckoutMode :: User
1099- } ;
1100- let mut checkout_opts = ostree:: RepoCheckoutAtOptions {
1101- mode : checkout_mode,
1102- overwrite_mode : ostree:: RepoCheckoutOverwriteMode :: UnionFiles ,
1103- devino_to_csum_cache : Some ( devino. clone ( ) ) ,
1104- no_copy_fallback : true ,
1105- force_copy_zerosized : true ,
1106- process_whiteouts : false ,
1107- ..Default :: default ( )
1108- } ;
1109- if let Some ( base) = base_commit. as_ref ( ) {
1110- repo. checkout_at (
1111- Some ( & checkout_opts) ,
1112- ( * td) . as_raw_fd ( ) ,
1113- rootpath,
1114- & base,
1115- cancellable,
1116- )
1117- . context ( "Checking out base commit" ) ?;
1118- }
1119-
1120- // Layer all subsequent commits
1121- checkout_opts. process_whiteouts = true ;
1122- for commit in layer_commits {
1123- tracing:: debug!( "Unpacking {commit}" ) ;
1124- repo. checkout_at (
1125- Some ( & checkout_opts) ,
1126- ( * td) . as_raw_fd ( ) ,
1127- rootpath,
1128- & commit,
1129- cancellable,
1130- )
1131- . with_context ( || format ! ( "Checking out layer {commit}" ) ) ?;
1132- }
1133-
1134- let root_dir = td. open_dir ( rootpath) ?;
1135-
1136- let modifier =
1137- ostree:: RepoCommitModifier :: new ( ostree:: RepoCommitModifierFlags :: empty ( ) , None ) ;
1138- modifier. set_devino_cache ( & devino) ;
1139- // If we have derived layers, then we need to handle the case where
1140- // the derived layers include custom policy. Just relabel everything
1141- // in this case.
1142- if have_derived_layers {
1143- let sepolicy = ostree:: SePolicy :: new_at ( root_dir. as_raw_fd ( ) , cancellable) ?;
1144- tracing:: debug!( "labeling from merged tree" ) ;
1145- modifier. set_sepolicy ( Some ( & sepolicy) ) ;
1146- } else if let Some ( base) = base_commit. as_ref ( ) {
1147- tracing:: debug!( "labeling from base tree" ) ;
1148- // TODO: We can likely drop this; we know all labels should be pre-computed.
1149- modifier. set_sepolicy_from_commit ( repo, & base, cancellable) ?;
1150- } else {
1151- unreachable ! ( )
1152- }
1153-
1154- cleanup_root ( & root_dir) ?;
1155-
1156- let mt = ostree:: MutableTree :: new ( ) ;
1157- repo. write_dfd_to_mtree (
1158- ( * td) . as_raw_fd ( ) ,
1159- rootpath,
1160- & mt,
1161- Some ( & modifier) ,
1162- cancellable,
1212+ Self :: write_merge_commit_impl (
1213+ & repo,
1214+ base_commit. as_deref ( ) ,
1215+ & layer_commits,
1216+ have_derived_layers,
1217+ metadata,
1218+ timestamp,
1219+ & ostree_ref,
1220+ self . no_imgref ,
1221+ self . disable_gc ,
1222+ Some ( cancellable) ,
11631223 )
1164- . context ( "Writing merged filesystem to mtree" ) ?;
1165-
1166- let merged_root = repo
1167- . write_mtree ( & mt, cancellable)
1168- . context ( "Writing mtree" ) ?;
1169- let merged_root = merged_root. downcast :: < ostree:: RepoFile > ( ) . unwrap ( ) ;
1170- // The merge has the base commit as a parent, if it exists. See
1171- // https://github.com/ostreedev/ostree/pull/3523
1172- let parent = base_commit. as_deref ( ) ;
1173- let merged_commit = repo
1174- . write_commit_with_time (
1175- parent,
1176- None ,
1177- None ,
1178- Some ( & metadata) ,
1179- & merged_root,
1180- timestamp,
1181- cancellable,
1182- )
1183- . context ( "Writing commit" ) ?;
1184- if !self . no_imgref {
1185- repo. transaction_set_ref ( None , & ostree_ref, Some ( merged_commit. as_str ( ) ) ) ;
1186- }
1187- txn. commit ( cancellable) ?;
1188-
1189- if !self . disable_gc {
1190- let n: u32 = gc_image_layers_impl ( repo, cancellable) ?;
1191- tracing:: debug!( "pruned {n} layers" ) ;
1192- }
1193-
1194- // Here we re-query state just to run through the same code path,
1195- // though it'd be cheaper to synthesize it from the data we already have.
1196- let state = query_image_commit ( repo, & merged_commit) ?;
1197- Ok ( state)
11981224 } ,
11991225 )
12001226 . await ?;
0 commit comments