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