@@ -21,16 +21,16 @@ use std::io::{Read, Write};
2121use std:: net:: { TcpStream , Shutdown } ;
2222use std:: path:: Path ;
2323use std:: process:: exit;
24- use std:: str;
24+ use std:: { fs , str} ;
2525use json:: object;
2626use log:: * ;
27- use config:: Config ;
2827
29- use crate :: constants:: MESSAGE_ACK ;
30- use crate :: pipeline:: streamer:: streamer;
31- use std:: { collections:: HashMap , path:: PathBuf } ;
3228use crate :: cli:: { DEFAULT_ADDRESS , DEFAULT_PORT } ;
3329use crate :: constants:: * ;
30+ use crate :: pipeline:: streamer:: streamer;
31+ use std:: { collections:: HashMap , env, path:: PathBuf } ;
32+
33+ use config:: Config ;
3434
3535const BUFFER_SIZE : usize = 32768 * 4 ;
3636
@@ -44,6 +44,16 @@ pub struct ClientConfig {
4444}
4545
4646impl ClientConfig {
47+ fn new ( log_file : String , address : String , port : String , id : String , dependencies : String ) -> Self {
48+ ClientConfig {
49+ log_file,
50+ address,
51+ port,
52+ id,
53+ dependencies,
54+ }
55+ }
56+
4757 pub fn get_log_file ( & self ) -> & str {
4858 & self . log_file
4959 }
@@ -71,55 +81,183 @@ const CONFIG_KEY_ADDR: &str = "address";
7181const CONFIG_KEY_PORT : & str = "port" ;
7282const CONFIG_KEY_LOG : & str = "log-file" ;
7383
74- pub fn load_config_file < P : AsRef < Path > > ( images_dir : P ) -> ClientConfig {
84+ pub fn load_config_file < P : AsRef < Path > > ( images_dir : P , action : & str ) -> ClientConfig {
7585 let images_dir = images_dir. as_ref ( ) ;
76- let mut config_file = images_dir. join ( Path :: new ( CONFIG_FILE ) ) ;
77- if !config_file. is_file ( ) {
78- // The following allows us to load global config files from /etc/criu.
79- // This is useful for example when we want to use the same config file
80- // for multiple containers.
81- let config_dir = PathBuf :: from ( "/etc/criu" ) ;
82- config_file = config_dir. join ( Path :: new ( CONFIG_FILE ) ) ;
83- if !config_file. is_file ( ) {
84- panic ! ( "config file does not exist" )
85- }
86- }
86+ let local_config_file = images_dir. join ( Path :: new ( CONFIG_FILE ) ) ;
8787
88- let settings = Config :: builder ( ) . add_source ( config:: File :: from ( config_file) ) . build ( ) . unwrap ( ) ;
89- let settings_map = settings. try_deserialize :: < HashMap < String , String > > ( ) . unwrap ( ) ;
88+ // Handle per-process configuration workflow
89+ if local_config_file. is_file ( ) {
90+ // Example of per-process config file:
91+ // {
92+ // "id": "A",
93+ // "dependencies": "B:C",
94+ // "address": "127.0.0.1",
95+ // "port": "8080",
96+ // "log-file": "/var/log/criu-coordinator.log"
97+ // }
98+ let settings = Config :: builder ( ) . add_source ( config:: File :: from ( local_config_file) ) . build ( ) . unwrap ( ) ;
99+ let settings_map = settings. try_deserialize :: < HashMap < String , String > > ( ) . unwrap ( ) ;
90100
91- if !settings_map. contains_key ( CONFIG_KEY_ID ) {
92- panic ! ( "id missing in config file" )
101+ return ClientConfig :: new (
102+ settings_map. get ( CONFIG_KEY_LOG ) . cloned ( ) . unwrap_or_else ( || "-" . to_string ( ) ) ,
103+ settings_map. get ( CONFIG_KEY_ADDR ) . cloned ( ) . unwrap_or_else ( || DEFAULT_ADDRESS . to_string ( ) ) ,
104+ settings_map. get ( CONFIG_KEY_PORT ) . cloned ( ) . unwrap_or_else ( || DEFAULT_PORT . to_string ( ) ) ,
105+ settings_map. get ( CONFIG_KEY_ID ) . unwrap ( ) . clone ( ) ,
106+ settings_map. get ( CONFIG_KEY_DEPS ) . cloned ( ) . unwrap_or_default ( ) ,
107+ ) ;
93108 }
94- let id = settings_map. get ( CONFIG_KEY_ID ) . unwrap ( ) ;
95109
96- let mut dependencies = String :: new ( ) ;
97- if settings_map. contains_key ( CONFIG_KEY_DEPS ) {
98- dependencies = settings_map. get ( CONFIG_KEY_DEPS ) . unwrap ( ) . to_string ( ) ;
99- }
110+ // The following allows us to load global config files from /etc/criu.
111+ // This is useful for example when we want to use the same config file
112+ // for multiple containers.
113+ // Example of global config file:
114+ // {
115+ // "address": "127.0.0.1",
116+ // "port": "8080",
117+ // "log-file": "/var/log/criu-coordinator.log",
118+ // "dependencies": {
119+ // "A": ["B", "C"],
120+ // "B": ["C", "A"],
121+ // "C": ["A"]
122+ // }
123+ // }
124+ // Where dependencies is a map of IDs (e.g: container IDs) to a list of dependencies.
125+ let global_config_file = PathBuf :: from ( "/etc/criu" ) . join ( Path :: new ( CONFIG_FILE ) ) ;
100126
101- let mut address = DEFAULT_ADDRESS ;
102- if settings_map. contains_key ( CONFIG_KEY_ADDR ) {
103- address = settings_map. get ( CONFIG_KEY_ADDR ) . unwrap ( ) ;
127+ if !global_config_file. is_file ( ) {
128+ panic ! ( "Global config file {:?} is not found" , global_config_file) ;
104129 }
105130
106- let mut port = DEFAULT_PORT ;
107- if settings_map. contains_key ( CONFIG_KEY_PORT ) {
108- port = settings_map. get ( CONFIG_KEY_PORT ) . unwrap ( ) ;
109- }
131+ let global_settings = Config :: builder ( ) . add_source ( config:: File :: from ( global_config_file) ) . build ( ) . unwrap ( ) ;
132+ let global_map = global_settings. try_deserialize :: < HashMap < String , config:: Value > > ( ) . unwrap ( ) ;
133+
134+ let address = global_map. get ( CONFIG_KEY_ADDR ) . map ( |v| v. clone ( ) . into_string ( ) . unwrap ( ) ) . unwrap_or_else ( || DEFAULT_ADDRESS . to_string ( ) ) ;
135+ let port = global_map. get ( CONFIG_KEY_PORT ) . map ( |v| v. clone ( ) . into_string ( ) . unwrap ( ) ) . unwrap_or_else ( || DEFAULT_PORT . to_string ( ) ) ;
136+ let log_file = global_map. get ( CONFIG_KEY_LOG ) . map ( |v| v. clone ( ) . into_string ( ) . unwrap ( ) ) . unwrap_or_else ( || "-" . to_string ( ) ) ;
137+
138+ if is_dump_action ( action) {
139+ let pid_str = env:: var ( ENV_INIT_PID )
140+ . unwrap_or_else ( |_| panic ! ( "{} not set" , ENV_INIT_PID ) ) ;
141+ let pid: u32 = pid_str. parse ( ) . expect ( "Invalid PID" ) ;
142+
143+
144+ let deps_map: HashMap < String , Vec < String > > = global_map
145+ . get ( CONFIG_KEY_DEPS )
146+ . unwrap_or_else ( || panic ! ( "'{}' map is missing in global config" , CONFIG_KEY_DEPS ) )
147+ . clone ( ) . into_table ( ) . unwrap ( )
148+ . into_iter ( ) . map ( |( k, v) | {
149+ let deps = v. into_array ( ) . unwrap ( ) . into_iter ( ) . map ( |val| val. into_string ( ) . unwrap ( ) ) . collect ( ) ;
150+ ( k, deps)
151+ } ) . collect ( ) ;
152+
153+ // We first try to find a container ID.
154+ let id = match find_container_id_from_pid ( pid) {
155+ Ok ( container_id) => container_id,
156+ Err ( _) => {
157+ // If the PID is not in a container cgroup, we consider it a regular process.
158+ // We identify it by its process name from /proc/<pid>/comm.
159+ let process_name_path = format ! ( "/proc/{pid}/comm" ) ;
160+ if let Ok ( name) = fs:: read_to_string ( process_name_path) {
161+ name. trim ( ) . to_string ( )
162+ } else {
163+ // Fallback to using the PID as the ID if comm is unreadable
164+ pid. to_string ( )
165+ }
166+ }
167+ } ;
168+
169+ let dependencies = find_dependencies_in_global_config ( & deps_map, & id) . unwrap ( ) ;
170+
171+ // Write the local config for each container during dump
172+ if action == ACTION_PRE_DUMP || action == ACTION_PRE_STREAM {
173+ write_checkpoint_config ( images_dir, & id, & dependencies) ;
174+ }
110175
111- let mut log_file = "-" ;
112- if settings_map. contains_key ( CONFIG_KEY_LOG ) {
113- log_file = settings_map. get ( CONFIG_KEY_LOG ) . unwrap ( ) ;
176+ ClientConfig :: new (
177+ log_file,
178+ address,
179+ port,
180+ id,
181+ dependencies,
182+ )
183+ } else { // Restore action
184+ if !local_config_file. is_file ( ) {
185+ panic ! ( "Restore action initiated, but no {CONFIG_FILE} found in the image directory {:?}" , images_dir) ;
186+ }
187+
188+ let local_settings = Config :: builder ( ) . add_source ( config:: File :: from ( local_config_file) ) . build ( ) . unwrap ( ) ;
189+ let local_map = local_settings. try_deserialize :: < HashMap < String , String > > ( ) . unwrap ( ) ;
190+
191+ ClientConfig :: new (
192+ log_file,
193+ address,
194+ port,
195+ local_map. get ( CONFIG_KEY_ID ) . unwrap ( ) . clone ( ) ,
196+ local_map. get ( CONFIG_KEY_DEPS ) . cloned ( ) . unwrap_or_default ( ) ,
197+ )
114198 }
199+ }
200+
201+ /// Find containers dependencies by matching the discovered ID as a prefix of a key in the map
202+ fn find_dependencies_in_global_config (
203+ deps_map : & HashMap < String , Vec < String > > ,
204+ id : & str ,
205+ ) -> Result < String , String > {
206+ let deps = deps_map
207+ . iter ( )
208+ . find ( |( key, _) | id. starts_with ( * key) )
209+ . map ( |( _, deps) | deps. join ( ":" ) )
210+ . ok_or_else ( || {
211+ format ! ( "No dependency entry found for container ID matching '{id}'" )
212+ } ) ?;
213+ Ok ( deps)
214+ }
215+
216+ /// Find a container ID from the host PID by inspecting the process's cgroup.
217+ fn find_container_id_from_pid ( pid : u32 ) -> Result < String , String > {
218+ let cgroup_path = format ! ( "/proc/{pid}/cgroup" ) ;
219+ let cgroup_content = fs:: read_to_string ( & cgroup_path)
220+ . map_err ( |e| format ! ( "Failed to read {cgroup_path}: {e}" ) ) ?;
115221
116- ClientConfig {
117- log_file : log_file. to_string ( ) ,
118- address : address. to_string ( ) ,
119- port : port. to_string ( ) ,
120- id : id. to_string ( ) ,
121- dependencies,
222+ let mut container_id: Option < String > = None ;
223+ for line in cgroup_content. lines ( ) {
224+ if line. len ( ) < 64 {
225+ continue ;
226+ }
227+ for i in 0 ..=( line. len ( ) - 64 ) {
228+ let potential_id = & line[ i..i + 64 ] ;
229+ if potential_id. chars ( ) . all ( |c| c. is_ascii_hexdigit ( ) ) {
230+ let is_start = i == 0 || !line. chars ( ) . nth ( i - 1 ) . unwrap ( ) . is_ascii_hexdigit ( ) ;
231+ let is_end = ( i + 64 == line. len ( ) )
232+ || !line. chars ( ) . nth ( i + 64 ) . unwrap ( ) . is_ascii_hexdigit ( ) ;
233+ if is_start && is_end {
234+ container_id = Some ( potential_id. to_string ( ) ) ;
235+ }
236+ }
237+ }
122238 }
239+
240+ container_id. ok_or_else ( || {
241+ format ! ( "Could not find container ID from cgroup file for PID {pid}" )
242+ } )
243+ }
244+
245+ /// Write per-checkpoint configuration file into the checkpoint images directory.
246+ fn write_checkpoint_config ( img_dir : & Path , id : & str , dependencies : & str ) {
247+ let config_path = img_dir. join ( CONFIG_FILE ) ;
248+ let content = format ! ( "{{\n \" id\" : \" {id}\" ,\n \" dependencies\" : \" {dependencies}\" \n }}" , ) ;
249+
250+ fs:: write ( & config_path, content)
251+ . unwrap_or_else ( |_| panic ! ( "Failed to write checkpoint config file to {:?}" , config_path) )
252+ }
253+
254+
255+ pub fn is_dump_action ( action : & str ) -> bool {
256+ matches ! ( action, ACTION_PRE_DUMP | ACTION_NETWORK_LOCK | ACTION_POST_DUMP | ACTION_PRE_STREAM )
257+ }
258+
259+ pub fn is_restore_action ( action : & str ) -> bool {
260+ matches ! ( action, ACTION_PRE_RESTORE | ACTION_POST_RESTORE | ACTION_NETWORK_UNLOCK | ACTION_POST_RESUME )
123261}
124262
125263pub fn run_client ( address : & str , port : u16 , id : & str , deps : & str , action : & str , images_dir : & Path , enable_streaming : bool ) {
0 commit comments