@@ -4,15 +4,34 @@ mod tmp_files;
44pub ( crate ) mod users;
55
66use anyhow:: Result ;
7+ use lazy_errors:: prelude:: * ;
78use serde:: { Deserialize , Serialize } ;
89use std:: fs:: DirBuilder ;
910use std:: path:: { Path , PathBuf } ;
10- use std:: { fs, io, process} ;
11+ use std:: { fmt , fs, io, process} ;
1112use thiserror:: Error ;
1213
1314use crate :: activate:: etc_files:: FileTree ;
1415use crate :: { StorePath , STATE_FILE_NAME , SYSTEM_MANAGER_STATE_DIR } ;
1516
17+ pub ( crate ) fn collect_activation_result_err < R , F , M > (
18+ res : ActivationResult < R > ,
19+ err_stash : & mut ErrorStash < F , M > ,
20+ ) -> ActivationResult < R >
21+ where
22+ M : fmt:: Display ,
23+ F : FnOnce ( ) -> M ,
24+ {
25+ res. map_err ( |e| {
26+ let ActivationError :: WithPartialResult {
27+ result : _,
28+ ref source,
29+ } = e;
30+ err_stash. push ( source. to_string ( ) ) ;
31+ e
32+ } )
33+ }
34+
1635#[ derive( Error , Debug ) ]
1736pub enum ActivationError < R > {
1837 #[ error( "" ) ]
@@ -77,60 +96,68 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
7796
7897 let state_file = & get_state_file ( ) ?;
7998 let old_state = State :: from_file ( state_file) ?;
99+ let mut errs = ErrorStash :: new ( || "Activation completed with errors" ) ;
80100
81101 log:: info!( "Activating etc files..." ) ;
82102
83- match etc_files:: activate ( store_path, old_state. file_tree , ephemeral) {
103+ let etc_result = collect_activation_result_err (
104+ etc_files:: activate ( store_path, old_state. file_tree , ephemeral) ,
105+ & mut errs,
106+ ) ;
107+ if let Err ( ref e) = etc_result {
108+ log:: error!( "Error during activation: {e:?}" ) ;
109+ }
110+
111+ // Only run daemon reload, userborn, tmpfiles, and services when etc files
112+ // were fully applied. Partial etc results mean services may reference
113+ // missing config files.
114+ let ( etc_tree, services) = match etc_result {
84115 Ok ( etc_tree) => {
85116 log:: info!( "Restarting sysinit-reactivation.target..." ) ;
86- services:: restart_sysinit_reactivation_target ( ) ?;
117+ let sysinit_result = services:: restart_sysinit_reactivation_target ( ) ;
118+ if let Err ( ref e) = sysinit_result {
119+ log:: error!( "Error restarting sysinit-reactivation.target: {e}" ) ;
120+ }
121+ sysinit_result. or_stash ( & mut errs) ;
87122
88123 // Restart userborn before tmpfiles so users exist when tmpfiles runs
89- if let Err ( e) = services:: restart_userborn_if_exists ( ) {
124+ let userborn_result = services:: restart_userborn_if_exists ( ) ;
125+ if let Err ( ref e) = userborn_result {
90126 log:: error!( "Error restarting userborn.service: {e}" ) ;
91127 }
128+ userborn_result. or_stash ( & mut errs) ;
92129
93130 log:: info!( "Activating tmp files..." ) ;
94- let tmp_result = tmp_files:: activate ( & etc_tree) ;
95- if let Err ( e) = & tmp_result {
96- log:: error!( "Error during activation of tmp files" ) ;
97- log:: error!( "{e}" ) ;
98- } else {
99- log:: debug!( "Successfully created tmp files" ) ;
131+ let tmp_result =
132+ collect_activation_result_err ( tmp_files:: activate ( & etc_tree) , & mut errs) ;
133+ if let Err ( ref e) = tmp_result {
134+ log:: error!( "Error during activation of tmp files: {e}" ) ;
100135 }
101136
102137 log:: info!( "Activating systemd services..." ) ;
103- let final_state = match services:: activate ( store_path, old_state. services , ephemeral) {
104- Ok ( services) => State {
105- file_tree : etc_tree,
106- services,
107- } ,
108- Err ( ActivationError :: WithPartialResult { result, source } ) => {
109- log:: error!( "Error during activation: {source:?}" ) ;
110- State {
111- file_tree : etc_tree,
112- services : result,
113- }
114- }
115- } ;
116- final_state. write_to_file ( state_file) ?;
117-
118- if let Err ( e) = tmp_result {
119- return Err ( e. into ( ) ) ;
138+ let svc_result = collect_activation_result_err (
139+ services:: activate ( store_path, old_state. services , ephemeral) ,
140+ & mut errs,
141+ ) ;
142+ if let Err ( ref e) = svc_result {
143+ log:: error!( "Error during activation: {e:?}" ) ;
120144 }
121-
122- Ok ( ( ) )
123- }
124- Err ( ActivationError :: WithPartialResult { result, source } ) => {
125- log:: error!( "Error during activation: {source:?}" ) ;
126- let final_state = State {
127- file_tree : result,
128- ..old_state
145+ let services = match svc_result {
146+ Ok ( s) => s,
147+ Err ( ActivationError :: WithPartialResult { result, .. } ) => result,
129148 } ;
130- final_state. write_to_file ( state_file) ?;
131- Ok ( ( ) )
149+ ( etc_tree, services)
132150 }
133- }
151+ Err ( ActivationError :: WithPartialResult { result, .. } ) => ( result, old_state. services ) ,
152+ } ;
153+
154+ let final_state = State {
155+ file_tree : etc_tree,
156+ services,
157+ } ;
158+ final_state. write_to_file ( state_file) . or_stash ( & mut errs) ;
159+
160+ Ok ( Result :: < ( ) , _ > :: from ( errs) ?)
134161}
135162
136163pub fn prepopulate ( store_path : & StorePath , ephemeral : bool ) -> Result < ( ) > {
@@ -146,36 +173,47 @@ pub fn prepopulate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
146173
147174 let state_file = & get_state_file ( ) ?;
148175 let old_state = State :: from_file ( state_file) ?;
176+ let mut errs = ErrorStash :: new ( || "Pre-population completed with errors" ) ;
149177
150178 log:: info!( "Activating etc files..." ) ;
151179
152- match etc_files:: activate ( store_path, old_state. file_tree , ephemeral) {
180+ let etc_result = collect_activation_result_err (
181+ etc_files:: activate ( store_path, old_state. file_tree , ephemeral) ,
182+ & mut errs,
183+ ) ;
184+ if let Err ( ref e) = etc_result {
185+ log:: error!( "Error during activation: {e:?}" ) ;
186+ }
187+
188+ // Only register services when etc files were fully applied, preserving
189+ // old service state on etc failure to avoid persisting state from a
190+ // partial run.
191+ let ( etc_tree, services) = match etc_result {
153192 Ok ( etc_tree) => {
154193 log:: info!( "Registering systemd services..." ) ;
155- match services:: get_active_services ( store_path, old_state. services ) {
156- Ok ( services) => State {
157- file_tree : etc_tree,
158- services,
159- } ,
160- Err ( ActivationError :: WithPartialResult { result, source } ) => {
161- log:: error!( "Error during activation: {source:?}" ) ;
162- State {
163- file_tree : etc_tree,
164- services : result,
165- }
166- }
167- }
168- }
169- Err ( ActivationError :: WithPartialResult { result, source } ) => {
170- log:: error!( "Error during activation: {source:?}" ) ;
171- State {
172- file_tree : result,
173- ..old_state
194+ let svc_result = collect_activation_result_err (
195+ services:: get_active_services ( store_path, old_state. services ) ,
196+ & mut errs,
197+ ) ;
198+ if let Err ( ref e) = svc_result {
199+ log:: error!( "Error during activation: {e:?}" ) ;
174200 }
201+ let services = match svc_result {
202+ Ok ( s) => s,
203+ Err ( ActivationError :: WithPartialResult { result, .. } ) => result,
204+ } ;
205+ ( etc_tree, services)
175206 }
176- }
177- . write_to_file ( state_file) ?;
178- Ok ( ( ) )
207+ Err ( ActivationError :: WithPartialResult { result, .. } ) => ( result, old_state. services ) ,
208+ } ;
209+
210+ let final_state = State {
211+ file_tree : etc_tree,
212+ services,
213+ } ;
214+ final_state. write_to_file ( state_file) . or_stash ( & mut errs) ;
215+
216+ Ok ( Result :: < ( ) , _ > :: from ( errs) ?)
179217}
180218
181219fn run_preactivation_assertions ( store_path : & StorePath ) -> Result < process:: ExitStatus > {
@@ -198,3 +236,38 @@ pub(crate) fn get_state_file() -> Result<PathBuf> {
198236 . create ( SYSTEM_MANAGER_STATE_DIR ) ?;
199237 Ok ( state_file)
200238}
239+
240+ #[ cfg( test) ]
241+ mod tests {
242+ use super :: * ;
243+
244+ #[ test]
245+ fn empty_stash_returns_ok ( ) {
246+ let errs = ErrorStash :: new ( || "Activation completed with errors" ) ;
247+ let result: std:: result:: Result < ( ) , lazy_errors:: prelude:: Error > = errs. into ( ) ;
248+ assert ! ( result. is_ok( ) ) ;
249+ }
250+
251+ #[ test]
252+ fn single_stashed_error_returns_err ( ) {
253+ let mut errs = ErrorStash :: new ( || "Activation completed with errors" ) ;
254+ Err :: < ( ) , _ > ( anyhow:: anyhow!( "userborn failed" ) ) . or_stash ( & mut errs) ;
255+ let result: std:: result:: Result < ( ) , lazy_errors:: prelude:: Error > = errs. into ( ) ;
256+ let err = result. unwrap_err ( ) ;
257+ let msg = format ! ( "{err:#}" ) ;
258+ assert ! ( msg. contains( "userborn failed" ) , "message was: {msg}" ) ;
259+ }
260+
261+ #[ test]
262+ fn multiple_stashed_errors_returns_combined_err ( ) {
263+ let mut errs = ErrorStash :: new ( || "Deactivation completed with errors" ) ;
264+ Err :: < ( ) , _ > ( anyhow:: anyhow!( "userborn failed" ) ) . or_stash ( & mut errs) ;
265+ Err :: < ( ) , _ > ( anyhow:: anyhow!( "tmpfiles failed" ) ) . or_stash ( & mut errs) ;
266+ let result: std:: result:: Result < ( ) , lazy_errors:: prelude:: Error > = errs. into ( ) ;
267+ let err = result. unwrap_err ( ) ;
268+ let msg = format ! ( "{err:#}" ) ;
269+ assert ! ( msg. contains( "Deactivation" ) , "message was: {msg}" ) ;
270+ assert ! ( msg. contains( "userborn failed" ) , "message was: {msg}" ) ;
271+ assert ! ( msg. contains( "tmpfiles failed" ) , "message was: {msg}" ) ;
272+ }
273+ }
0 commit comments