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