1+ use std:: io:: Write ;
2+
13use anyhow:: { Context , Result } ;
24use camino:: Utf8PathBuf ;
3- use cap_std_ext:: cap_std:: fs:: Dir ;
5+ use canon_json:: CanonJsonSerialize ;
6+ use cap_std_ext:: { cap_std:: fs:: Dir , dirext:: CapStdExtDirExt } ;
47use composefs:: fsverity:: { FsVerityHashValue , Sha512HashValue } ;
58use composefs_boot:: BootOps ;
69use composefs_oci:: image:: create_filesystem;
710use fn_error_context:: context;
11+ use ocidir:: cap_std:: ambient_authority;
812use ostree_ext:: container:: ManifestDiff ;
913
1014use crate :: {
@@ -15,12 +19,15 @@ use crate::{
1519 soft_reboot:: prepare_soft_reboot_composefs,
1620 state:: write_composefs_state,
1721 status:: {
18- ImgConfigManifest , get_bootloader, get_composefs_status,
22+ ImgConfigManifest , StagedDeployment , get_bootloader, get_composefs_status,
1923 get_container_manifest_and_config, get_imginfo,
2024 } ,
2125 } ,
2226 cli:: { SoftRebootMode , UpgradeOpts } ,
23- composefs_consts:: { STATE_DIR_RELATIVE , TYPE1_ENT_PATH_STAGED , USER_CFG_STAGED } ,
27+ composefs_consts:: {
28+ COMPOSEFS_STAGED_DEPLOYMENT_FNAME , COMPOSEFS_TRANSIENT_STATE_DIR , STATE_DIR_RELATIVE ,
29+ TYPE1_ENT_PATH_STAGED , USER_CFG_STAGED ,
30+ } ,
2431 spec:: { Bootloader , Host , ImageReference } ,
2532 store:: { BootedComposefs , ComposefsRepository , Storage } ,
2633} ;
@@ -206,6 +213,31 @@ pub(crate) fn validate_update(
206213pub ( crate ) struct DoUpgradeOpts {
207214 pub ( crate ) apply : bool ,
208215 pub ( crate ) soft_reboot : Option < SoftRebootMode > ,
216+ pub ( crate ) download_only : bool ,
217+ }
218+
219+ async fn apply_upgrade (
220+ storage : & Storage ,
221+ booted_cfs : & BootedComposefs ,
222+ depl_id : & String ,
223+ opts : & DoUpgradeOpts ,
224+ ) -> Result < ( ) > {
225+ if let Some ( soft_reboot_mode) = opts. soft_reboot {
226+ return prepare_soft_reboot_composefs (
227+ storage,
228+ booted_cfs,
229+ Some ( depl_id) ,
230+ soft_reboot_mode,
231+ opts. apply ,
232+ )
233+ . await ;
234+ } ;
235+
236+ if opts. apply {
237+ return crate :: reboot:: reboot ( ) ;
238+ }
239+
240+ Ok ( ( ) )
209241}
210242
211243/// Performs the Update or Switch operation
@@ -255,29 +287,17 @@ pub(crate) async fn do_upgrade(
255287 & Utf8PathBuf :: from ( "/sysroot" ) ,
256288 & id,
257289 imgref,
258- true ,
290+ Some ( StagedDeployment {
291+ depl_id : id. to_hex ( ) ,
292+ finalization_locked : opts. download_only ,
293+ } ) ,
259294 boot_type,
260295 boot_digest,
261296 img_manifest_config,
262297 )
263298 . await ?;
264299
265- if let Some ( soft_reboot_mode) = opts. soft_reboot {
266- return prepare_soft_reboot_composefs (
267- storage,
268- booted_cfs,
269- Some ( & id. to_hex ( ) ) ,
270- soft_reboot_mode,
271- opts. apply ,
272- )
273- . await ;
274- } ;
275-
276- if opts. apply {
277- return crate :: reboot:: reboot ( ) ;
278- }
279-
280- Ok ( ( ) )
300+ apply_upgrade ( storage, booted_cfs, & id. to_hex ( ) , opts) . await
281301}
282302
283303#[ context( "Upgrading composefs" ) ]
@@ -286,18 +306,60 @@ pub(crate) async fn upgrade_composefs(
286306 storage : & Storage ,
287307 composefs : & BootedComposefs ,
288308) -> Result < ( ) > {
289- // Download-only mode is not yet supported for composefs backend
290- if opts. download_only {
291- anyhow:: bail!( "--download-only is not yet supported for composefs backend" ) ;
292- }
293- if opts. from_downloaded {
294- anyhow:: bail!( "--from-downloaded is not yet supported for composefs backend" ) ;
295- }
296-
297309 let host = get_composefs_status ( storage, composefs)
298310 . await
299311 . context ( "Getting composefs deployment status" ) ?;
300312
313+ let do_upgrade_opts = DoUpgradeOpts {
314+ soft_reboot : opts. soft_reboot ,
315+ apply : opts. apply ,
316+ download_only : opts. download_only ,
317+ } ;
318+
319+ if opts. from_downloaded {
320+ let staged = host
321+ . status
322+ . staged
323+ . as_ref ( )
324+ . ok_or_else ( || anyhow:: anyhow!( "No staged deployment found" ) ) ?;
325+
326+ // Staged deployment exists, but it will be finalized
327+ if !staged. download_only {
328+ println ! ( "Staged deployment is present and not in download only mode." ) ;
329+ println ! ( "Use `bootc update --apply` to apply the update." ) ;
330+ return Ok ( ( ) ) ;
331+ }
332+
333+ start_finalize_stated_svc ( ) ?;
334+
335+ // Make the staged deployment not download_only
336+ let new_staged = StagedDeployment {
337+ depl_id : staged. require_composefs ( ) ?. verity . clone ( ) ,
338+ finalization_locked : false ,
339+ } ;
340+
341+ let staged_depl_dir =
342+ Dir :: open_ambient_dir ( COMPOSEFS_TRANSIENT_STATE_DIR , ambient_authority ( ) )
343+ . context ( "Opening transient state directory" ) ?;
344+
345+ staged_depl_dir
346+ . atomic_replace_with (
347+ COMPOSEFS_STAGED_DEPLOYMENT_FNAME ,
348+ |f| -> std:: io:: Result < ( ) > {
349+ f. write_all ( new_staged. to_canon_json_string ( ) ?. as_bytes ( ) )
350+ } ,
351+ )
352+ . context ( "Writing staged file" ) ?;
353+
354+ return apply_upgrade (
355+ storage,
356+ composefs,
357+ & staged. require_composefs ( ) ?. verity ,
358+ & do_upgrade_opts,
359+ )
360+ . await ;
361+ }
362+
301363 let mut booted_imgref = host
302364 . spec
303365 . image
@@ -313,11 +375,6 @@ pub(crate) async fn upgrade_composefs(
313375 // Or if we have another staged deployment with a different image
314376 let staged_image = host. status . staged . as_ref ( ) . and_then ( |i| i. image . as_ref ( ) ) ;
315377
316- let do_upgrade_opts = DoUpgradeOpts {
317- soft_reboot : opts. soft_reboot ,
318- apply : opts. apply ,
319- } ;
320-
321378 if let Some ( staged_image) = staged_image {
322379 // We have a staged image and it has the same digest as the currently booted image's latest
323380 // digest
0 commit comments