@@ -2,12 +2,8 @@ use crate::agent::{AGENT_AARCH64, AGENT_X86_64, is_placeholder};
22use anyhow:: { Context , Result , bail} ;
33use intar_core:: Scenario ;
44use intar_ui:: App ;
5- use intar_vm:: { IntarDirs , RunState } ;
6- use std:: io:: { BufRead , BufReader , Write } ;
7- use std:: os:: unix:: net:: UnixStream ;
5+ use intar_vm:: IntarDirs ;
86use std:: path:: { Path , PathBuf } ;
9- use std:: process:: Command ;
10- use std:: time:: { Duration , Instant } ;
117
128pub async fn start ( scenario_path : PathBuf ) -> Result < ( ) > {
139 if is_placeholder ( AGENT_X86_64 ) || is_placeholder ( AGENT_AARCH64 ) {
@@ -32,31 +28,6 @@ pub async fn start(scenario_path: PathBuf) -> Result<()> {
3228 Ok ( ( ) )
3329}
3430
35- pub fn status ( ) -> Result < ( ) > {
36- let dirs = IntarDirs :: new ( ) . context ( "Failed to initialize directories" ) ?;
37- let runs_dir = dirs. runs_dir ( ) ;
38-
39- if !runs_dir. exists ( ) {
40- println ! ( "No scenario runs found." ) ;
41- return Ok ( ( ) ) ;
42- }
43-
44- println ! ( "Scenario runs in {}:" , runs_dir. display( ) ) ;
45- for entry in std:: fs:: read_dir ( & runs_dir) ? {
46- let entry = entry?;
47- if entry. file_type ( ) ?. is_dir ( ) {
48- let name = entry. file_name ( ) ;
49- let has_qmp_sock = std:: fs:: read_dir ( entry. path ( ) ) ?
50- . filter_map ( Result :: ok)
51- . any ( |e| e. path ( ) . extension ( ) . is_some_and ( |ext| ext == "sock" ) ) ;
52- let status = if has_qmp_sock { "running" } else { "stopped" } ;
53- println ! ( " {} ({})" , name. to_string_lossy( ) , status) ;
54- }
55- }
56-
57- Ok ( ( ) )
58- }
59-
6031pub fn ssh ( vm_name : & str , run_name : Option < & str > ) -> Result < ( ) > {
6132 let dirs = IntarDirs :: new ( ) . context ( "Failed to initialize directories" ) ?;
6233 let runs_root = dirs. runs_dir ( ) ;
@@ -137,38 +108,6 @@ pub fn ssh(vm_name: &str, run_name: Option<&str>) -> Result<()> {
137108 Ok ( ( ) )
138109}
139110
140- pub fn reset ( ) {
141- println ! ( "Reset must be done from within the running UI (press 'r')" ) ;
142- }
143-
144- pub fn stop ( ) -> Result < ( ) > {
145- let dirs = IntarDirs :: new ( ) . context ( "Failed to initialize directories" ) ?;
146- let runs_root = dirs. runs_dir ( ) ;
147-
148- if !runs_root. exists ( ) {
149- println ! ( "No scenario runs found." ) ;
150- return Ok ( ( ) ) ;
151- }
152-
153- let run_dirs: Vec < PathBuf > = std:: fs:: read_dir ( & runs_root) ?
154- . filter_map ( Result :: ok)
155- . filter ( |e| e. file_type ( ) . map ( |t| t. is_dir ( ) ) . unwrap_or ( false ) )
156- . map ( |e| e. path ( ) )
157- . collect ( ) ;
158-
159- if run_dirs. is_empty ( ) {
160- println ! ( "No scenario runs found." ) ;
161- return Ok ( ( ) ) ;
162- }
163-
164- for run_dir in run_dirs {
165- stop_run_dir ( & run_dir) ?;
166- println ! ( "Stopped and cleaned run at {}" , run_dir. display( ) ) ;
167- }
168-
169- Ok ( ( ) )
170- }
171-
172111pub fn list ( dir : & Path ) -> Result < ( ) > {
173112 println ! ( "Searching for scenarios in: {}" , dir. display( ) ) ;
174113
@@ -198,155 +137,6 @@ pub fn list(dir: &Path) -> Result<()> {
198137 Ok ( ( ) )
199138}
200139
201- fn stop_run_dir ( run_dir : & Path ) -> Result < ( ) > {
202- let state = RunState :: load ( run_dir) ;
203- let mut vms_failed_to_stop = Vec :: new ( ) ;
204-
205- if let Ok ( state) = state {
206- for vm in state. vms {
207- let qmp_socket = run_dir. join ( format ! ( "{}-qmp.sock" , vm. name) ) ;
208- let pid_file = run_dir. join ( format ! ( "{}-qemu.pid" , vm. name) ) ;
209-
210- if qmp_socket. exists ( )
211- && let Err ( e) = send_qmp_quit ( & qmp_socket)
212- {
213- eprintln ! (
214- "Warning: failed to stop VM '{}' via {}: {}" ,
215- vm. name,
216- qmp_socket. display( ) ,
217- e
218- ) ;
219- }
220-
221- if let Some ( pid) = read_pid_file ( & pid_file) {
222- if let Err ( e) = wait_for_pid_exit ( pid, Duration :: from_secs ( 3 ) ) {
223- eprintln ! (
224- "Warning: failed waiting for VM '{}' (pid {pid}) to exit: {e}" ,
225- vm. name
226- ) ;
227- }
228-
229- if pid_is_running ( pid) {
230- if let Err ( e) = send_kill_signal ( pid, "-TERM" ) {
231- eprintln ! ( "Warning: failed to send SIGTERM to pid {pid}: {e}" ) ;
232- }
233- let _ = wait_for_pid_exit ( pid, Duration :: from_secs ( 2 ) ) ;
234- }
235-
236- if pid_is_running ( pid) {
237- if let Err ( e) = send_kill_signal ( pid, "-KILL" ) {
238- eprintln ! ( "Warning: failed to send SIGKILL to pid {pid}: {e}" ) ;
239- }
240- let _ = wait_for_pid_exit ( pid, Duration :: from_secs ( 2 ) ) ;
241- }
242-
243- if pid_is_running ( pid) {
244- vms_failed_to_stop. push ( vm. name ) ;
245- }
246- }
247- }
248- } else if let Err ( e) = state {
249- eprintln ! (
250- "Warning: failed to load state for {}: {}" ,
251- run_dir. display( ) ,
252- e
253- ) ;
254- }
255-
256- if !vms_failed_to_stop. is_empty ( ) {
257- bail ! (
258- "Failed to stop VMs in {}: {}" ,
259- run_dir. display( ) ,
260- vms_failed_to_stop. join( ", " )
261- ) ;
262- }
263-
264- remove_dir_all_with_retries ( run_dir, 5 , Duration :: from_millis ( 200 ) )
265- . with_context ( || format ! ( "Failed to delete {}" , run_dir. display( ) ) ) ?;
266-
267- Ok ( ( ) )
268- }
269-
270- fn send_qmp_quit ( socket : & Path ) -> std:: io:: Result < ( ) > {
271- let mut stream = UnixStream :: connect ( socket) ?;
272- stream. set_read_timeout ( Some ( Duration :: from_secs ( 1 ) ) ) ?;
273- stream. set_write_timeout ( Some ( Duration :: from_secs ( 1 ) ) ) ?;
274-
275- let mut reader = BufReader :: new ( stream. try_clone ( ) ?) ;
276- let mut line = String :: new ( ) ;
277-
278- // QMP greeting
279- let _ = reader. read_line ( & mut line) ;
280-
281- stream. write_all ( br#"{"execute": "qmp_capabilities"}\n"# ) ?;
282- line. clear ( ) ;
283- let _ = reader. read_line ( & mut line) ;
284-
285- stream. write_all ( br#"{"execute": "quit"}\n"# ) ?;
286-
287- Ok ( ( ) )
288- }
289-
290- fn read_pid_file ( path : & Path ) -> Option < u32 > {
291- let raw = std:: fs:: read_to_string ( path) . ok ( ) ?;
292- let trimmed = raw. trim ( ) ;
293- if trimmed. is_empty ( ) {
294- return None ;
295- }
296- trimmed. parse :: < u32 > ( ) . ok ( )
297- }
298-
299- fn pid_is_running ( pid : u32 ) -> bool {
300- Command :: new ( "kill" )
301- . args ( [ "-0" , & pid. to_string ( ) ] )
302- . status ( )
303- . is_ok_and ( |s| s. success ( ) )
304- }
305-
306- fn send_kill_signal ( pid : u32 , signal : & str ) -> std:: io:: Result < ( ) > {
307- let status = Command :: new ( "kill" )
308- . args ( [ signal, & pid. to_string ( ) ] )
309- . status ( ) ?;
310- if status. success ( ) {
311- return Ok ( ( ) ) ;
312- }
313- Err ( std:: io:: Error :: other ( format ! (
314- "kill {signal} {pid} failed with status {status}"
315- ) ) )
316- }
317-
318- fn wait_for_pid_exit ( pid : u32 , timeout : Duration ) -> std:: io:: Result < ( ) > {
319- let deadline = Instant :: now ( ) + timeout;
320- while Instant :: now ( ) < deadline {
321- if !pid_is_running ( pid) {
322- return Ok ( ( ) ) ;
323- }
324- std:: thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
325- }
326- Err ( std:: io:: Error :: other (
327- "timed out waiting for process to exit" ,
328- ) )
329- }
330-
331- fn remove_dir_all_with_retries (
332- path : & Path ,
333- attempts : usize ,
334- delay : Duration ,
335- ) -> std:: io:: Result < ( ) > {
336- for i in 0 ..attempts {
337- match std:: fs:: remove_dir_all ( path) {
338- Ok ( ( ) ) => return Ok ( ( ) ) ,
339- Err ( e) => {
340- if i + 1 == attempts {
341- return Err ( e) ;
342- }
343- std:: thread:: sleep ( delay) ;
344- }
345- }
346- }
347- Ok ( ( ) )
348- }
349-
350140pub fn logs ( run_name : Option < & str > , vm_name : Option < & str > , log_type : & str ) -> Result < ( ) > {
351141 let dirs = IntarDirs :: new ( ) . context ( "Failed to initialize directories" ) ?;
352142 let runs_root = dirs. runs_dir ( ) ;
0 commit comments