3
3
//! Create a merged filesystem tree with the image and mounted configmaps.
4
4
5
5
use std:: collections:: HashSet ;
6
+ use std:: fs:: create_dir_all;
6
7
use std:: io:: { BufRead , Write } ;
8
+ use std:: path:: PathBuf ;
7
9
8
10
use anyhow:: Ok ;
9
11
use anyhow:: { anyhow, Context , Result } ;
@@ -21,13 +23,17 @@ use ostree_ext::ostree::{self, Sysroot};
21
23
use ostree_ext:: sysroot:: SysrootLock ;
22
24
use ostree_ext:: tokio_util:: spawn_blocking_cancellable_flatten;
23
25
26
+ use crate :: bls_config:: { parse_bls_config, BLSConfig } ;
27
+ use crate :: install:: { get_efi_uuid_source, get_user_config, BootType } ;
24
28
use crate :: progress_jsonl:: { Event , ProgressWriter , SubTaskBytes , SubTaskStep } ;
25
29
use crate :: spec:: ImageReference ;
26
- use crate :: spec:: { BootOrder , HostSpec } ;
27
- use crate :: status:: labels_of_config;
30
+ use crate :: spec:: { BootOrder , HostSpec , BootEntry } ;
31
+ use crate :: status:: { composefs_deployment_status , labels_of_config} ;
28
32
use crate :: store:: Storage ;
29
33
use crate :: utils:: async_task_with_spinner;
30
34
35
+ use openat_ext:: OpenatDirExt ;
36
+
31
37
// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
32
38
const BASE_IMAGE_PREFIX : & str = "ostree/container/baseimage/bootc" ;
33
39
@@ -737,6 +743,165 @@ pub(crate) async fn stage(
737
743
Ok ( ( ) )
738
744
}
739
745
746
+
747
+ #[ context( "Rolling back UKI" ) ]
748
+ pub ( crate ) fn rollback_composefs_uki ( current : & BootEntry , rollback : & BootEntry ) -> Result < ( ) > {
749
+ let user_cfg_name = "grub2/user.cfg.staged" ;
750
+ let user_cfg_path = PathBuf :: from ( "/sysroot/boot" ) . join ( user_cfg_name) ;
751
+
752
+ let efi_uuid_source = get_efi_uuid_source ( ) ;
753
+
754
+ // TODO: Need to check if user.cfg.staged exists
755
+ let mut usr_cfg = std:: fs:: OpenOptions :: new ( )
756
+ . write ( true )
757
+ . create ( true )
758
+ . truncate ( true )
759
+ . open ( user_cfg_path)
760
+ . with_context ( || format ! ( "Opening {user_cfg_name}" ) ) ?;
761
+
762
+ usr_cfg. write ( efi_uuid_source. as_bytes ( ) ) ?;
763
+
764
+ let verity = if let Some ( composefs) = & rollback. composefs {
765
+ composefs. verity . clone ( )
766
+ } else {
767
+ // Shouldn't really happen
768
+ anyhow:: bail!( "Verity not found for rollback deployment" )
769
+ } ;
770
+ usr_cfg. write ( get_user_config ( & verity) . as_bytes ( ) ) ?;
771
+
772
+ let verity = if let Some ( composefs) = & current. composefs {
773
+ composefs. verity . clone ( )
774
+ } else {
775
+ // Shouldn't really happen
776
+ anyhow:: bail!( "Verity not found for booted deployment" )
777
+ } ;
778
+ usr_cfg. write ( get_user_config ( & verity) . as_bytes ( ) ) ?;
779
+
780
+ Ok ( ( ) )
781
+ }
782
+
783
+ /// Filename for `loader/entries`
784
+ const CURRENT_ENTRIES : & str = "entries" ;
785
+ const ROLLBACK_ENTRIES : & str = "entries.staged" ;
786
+
787
+ #[ context( "Getting boot entries" ) ]
788
+ pub ( crate ) fn get_sorted_boot_entries ( ascending : bool ) -> Result < Vec < BLSConfig > > {
789
+ let mut all_configs = vec ! [ ] ;
790
+
791
+ for entry in std:: fs:: read_dir ( format ! ( "/sysroot/boot/loader/{CURRENT_ENTRIES}" ) ) ? {
792
+ let entry = entry?;
793
+
794
+ let file_name = entry. file_name ( ) ;
795
+
796
+ let file_name = file_name
797
+ . to_str ( )
798
+ . ok_or ( anyhow:: anyhow!( "Found non UTF-8 characters in filename" ) ) ?;
799
+
800
+ if !file_name. ends_with ( ".conf" ) {
801
+ continue ;
802
+ }
803
+
804
+ let contents = std:: fs:: read_to_string ( & entry. path ( ) )
805
+ . with_context ( || format ! ( "Failed to read {:?}" , entry. path( ) ) ) ?;
806
+
807
+ let config = parse_bls_config ( & contents) . context ( "Parsing bls config" ) ?;
808
+
809
+ all_configs. push ( config) ;
810
+ }
811
+
812
+ all_configs. sort_by ( |a, b| if ascending { a. cmp ( b) } else { b. cmp ( a) } ) ;
813
+
814
+ return Ok ( all_configs) ;
815
+ }
816
+
817
+ #[ context( "Rolling back BLS" ) ]
818
+ pub ( crate ) fn rollback_composefs_bls ( ) -> Result < ( ) > {
819
+ // Sort in descending order as that's the order they're shown on the boot screen
820
+ // After this:
821
+ // all_configs[0] -> booted depl
822
+ // all_configs[1] -> rollback depl
823
+ let mut all_configs = get_sorted_boot_entries ( false ) ?;
824
+
825
+ // Update the indicies so that they're swapped
826
+ for ( idx, cfg) in all_configs. iter_mut ( ) . enumerate ( ) {
827
+ cfg. version = idx as u32 ;
828
+ }
829
+
830
+ assert ! ( all_configs. len( ) == 2 ) ;
831
+
832
+ // Write these
833
+ let dir_path = PathBuf :: from ( format ! ( "/sysroot/boot/loader/{ROLLBACK_ENTRIES}" ) ) ;
834
+ create_dir_all ( & dir_path) . with_context ( || format ! ( "Failed to create dir: {dir_path:?}" ) ) ?;
835
+
836
+ // Write the BLS configs in there
837
+ for cfg in all_configs {
838
+ let file_name = format ! ( "bootc-composefs-{}.conf" , cfg. version) ;
839
+
840
+ let mut file = std:: fs:: OpenOptions :: new ( )
841
+ . create ( true )
842
+ . write ( true )
843
+ . open ( dir_path. join ( & file_name) )
844
+ . with_context ( || format ! ( "Opening {file_name}" ) ) ?;
845
+
846
+ file. write_all ( cfg. to_string ( ) . as_bytes ( ) )
847
+ . with_context ( || format ! ( "Writing to {file_name}" ) ) ?;
848
+ }
849
+
850
+ // Atomically exchange "entries" <-> "entries.rollback"
851
+ let dir = openat:: Dir :: open ( "/sysroot/boot/loader" ) . context ( "Opening loader dir" ) ?;
852
+
853
+ tracing:: debug!( "Atomically exchanging for {ROLLBACK_ENTRIES} and {CURRENT_ENTRIES}" ) ;
854
+ dir. local_exchange ( ROLLBACK_ENTRIES , CURRENT_ENTRIES )
855
+ . context ( "local exchange" ) ?;
856
+
857
+ tracing:: debug!( "Removing {ROLLBACK_ENTRIES}" ) ;
858
+ dir. remove_all ( ROLLBACK_ENTRIES )
859
+ . context ( "Removing entries.rollback" ) ?;
860
+
861
+ tracing:: debug!( "Syncing to disk" ) ;
862
+ dir. syncfs ( ) . context ( "syncfs" ) ?;
863
+
864
+ Ok ( ( ) )
865
+ }
866
+
867
+ #[ context( "Rolling back composefs" ) ]
868
+ pub ( crate ) async fn composefs_rollback ( ) -> Result < ( ) > {
869
+ let host = composefs_deployment_status ( ) . await ?;
870
+
871
+ let new_spec = {
872
+ let mut new_spec = host. spec . clone ( ) ;
873
+ new_spec. boot_order = new_spec. boot_order . swap ( ) ;
874
+ new_spec
875
+ } ;
876
+
877
+ // Just to be sure
878
+ host. spec . verify_transition ( & new_spec) ?;
879
+
880
+ let reverting = new_spec. boot_order == BootOrder :: Default ;
881
+ if reverting {
882
+ println ! ( "notice: Reverting queued rollback state" ) ;
883
+ }
884
+
885
+ let rollback_status = host
886
+ . status
887
+ . rollback
888
+ . ok_or_else ( || anyhow ! ( "No rollback available" ) ) ?;
889
+
890
+ // TODO: Handle staged deployment
891
+ // Ostree will drop any staged deployment on rollback but will keep it if it is the first item
892
+ // in the new deployment list
893
+ let Some ( rollback_composefs_entry) = & rollback_status. composefs else {
894
+ anyhow:: bail!( "Rollback deployment not a composefs deployment" )
895
+ } ;
896
+
897
+ match rollback_composefs_entry. boot_type {
898
+ BootType :: Bls => rollback_composefs_bls ( ) ,
899
+ BootType :: Uki => rollback_composefs_uki ( & host. status . booted . unwrap ( ) , & rollback_status) ,
900
+ } ?;
901
+
902
+ Ok ( ( ) )
903
+ }
904
+
740
905
/// Implementation of rollback functionality
741
906
pub ( crate ) async fn rollback ( sysroot : & Storage ) -> Result < ( ) > {
742
907
const ROLLBACK_JOURNAL_ID : & str = "26f3b1eb24464d12aa5e7b544a6b5468" ;
0 commit comments