@@ -3,6 +3,7 @@ use std::{
33 io:: IsTerminal ,
44 path:: PathBuf ,
55 process:: { Command as ProcessCommand , Stdio } ,
6+ sync:: Arc ,
67 time:: { Duration , Instant } ,
78} ;
89
@@ -21,6 +22,7 @@ use fabric::{
2122 service:: { self , DEFAULT_MEMORY_MAX_MB , ServiceInstallOptions } ,
2223 shell:: { self , ServerFrame } ,
2324 sync:: config:: { SyncBook , SyncEntry , SyncPeers , SyncPolicy } ,
25+ terminal:: TerminalModeGuard ,
2426} ;
2527use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
2628
@@ -1102,48 +1104,83 @@ async fn wait_for_daemon_ready(
11021104
11031105async fn run_shell_client ( socket : & PathBuf ) -> Result < i32 > {
11041106 let stream = tokio:: net:: UnixStream :: connect ( socket) . await ?;
1105- let ( mut read, mut write) = stream. into_split ( ) ;
1106- let _raw_mode = RawModeGuard :: enable_if_terminal ( ) ?;
1107+ let ( mut read, write) = stream. into_split ( ) ;
1108+ let mut signals = ShellSignals :: new ( ) ?;
1109+ let terminal = TerminalModeGuard :: enable_if_terminal ( ) ?;
11071110 let ( cols, rows) = terminal_size ( ) ;
1108- shell:: write_client_resize ( & mut write, rows, cols) . await ?;
1111+ let write = Arc :: new ( tokio:: sync:: Mutex :: new ( write) ) ;
1112+ shell:: write_client_resize ( & mut * write. lock ( ) . await , rows, cols) . await ?;
11091113
1114+ let stdin_write = write. clone ( ) ;
11101115 let stdin_task = tokio:: spawn ( async move {
11111116 let mut stdin = tokio:: io:: stdin ( ) ;
11121117 let mut buf = [ 0u8 ; 8192 ] ;
11131118 loop {
11141119 let read = stdin. read ( & mut buf) . await ?;
11151120 if read == 0 {
1116- shell:: write_client_eof ( & mut write ) . await ?;
1121+ shell:: write_client_eof ( & mut * stdin_write . lock ( ) . await ) . await ?;
11171122 return Ok :: < ( ) , anyhow:: Error > ( ( ) ) ;
11181123 }
1119- shell:: write_client_stdin ( & mut write , & buf[ ..read] ) . await ?;
1124+ shell:: write_client_stdin ( & mut * stdin_write . lock ( ) . await , & buf[ ..read] ) . await ?;
11201125 }
11211126 } ) ;
11221127
11231128 let mut stdout = tokio:: io:: stdout ( ) ;
11241129 let mut stderr = tokio:: io:: stderr ( ) ;
11251130 let mut exit_code = 1 ;
11261131
1127- while let Some ( frame) = shell:: read_server_frame ( & mut read) . await ? {
1128- match frame {
1129- ServerFrame :: Output ( bytes) => {
1130- stdout. write_all ( & bytes) . await ?;
1131- stdout. flush ( ) . await ?;
1132- }
1133- ServerFrame :: Error ( message) => {
1134- stderr. write_all ( message. as_bytes ( ) ) . await ?;
1135- stderr. write_all ( b"\n " ) . await ?;
1136- stderr. flush ( ) . await ?;
1132+ loop {
1133+ tokio:: select! {
1134+ frame = shell:: read_server_frame( & mut read) => {
1135+ let Some ( frame) = frame? else {
1136+ break ;
1137+ } ;
1138+ match frame {
1139+ ServerFrame :: Output ( bytes) => {
1140+ stdout. write_all( & bytes) . await ?;
1141+ stdout. flush( ) . await ?;
1142+ }
1143+ ServerFrame :: Error ( message) => {
1144+ stderr. write_all( message. as_bytes( ) ) . await ?;
1145+ stderr. write_all( b"\n " ) . await ?;
1146+ stderr. flush( ) . await ?;
1147+ }
1148+ ServerFrame :: Status ( message) => {
1149+ stderr. write_all( message. as_bytes( ) ) . await ?;
1150+ stderr. write_all( b"\n " ) . await ?;
1151+ stderr. flush( ) . await ?;
1152+ }
1153+ ServerFrame :: Exit ( code) => {
1154+ exit_code = normalize_exit_code( code) ;
1155+ break ;
1156+ }
1157+ }
11371158 }
1138- ServerFrame :: Exit ( code) => {
1139- exit_code = normalize_exit_code ( code) ;
1140- break ;
1159+ signal = signals. recv( ) => {
1160+ match signal {
1161+ ShellSignal :: Resize => {
1162+ let ( cols, rows) = terminal_size( ) ;
1163+ shell:: write_client_resize( & mut * write. lock( ) . await , rows, cols) . await ?;
1164+ }
1165+ ShellSignal :: Suspend => {
1166+ terminal. restore( ) ?;
1167+ suspend_current_process( ) ;
1168+ terminal. reenter_raw( ) ?;
1169+ let ( cols, rows) = terminal_size( ) ;
1170+ shell:: write_client_resize( & mut * write. lock( ) . await , rows, cols) . await ?;
1171+ }
1172+ ShellSignal :: Terminate ( signal) => {
1173+ terminal. restore( ) ?;
1174+ terminate_with_signal( signal) ;
1175+ }
1176+ }
11411177 }
11421178 }
11431179 }
11441180
11451181 stdin_task. abort ( ) ;
11461182 let _ = stdin_task. await ;
1183+ terminal. restore ( ) ?;
11471184 stdout. flush ( ) . await ?;
11481185 stderr. flush ( ) . await ?;
11491186 Ok ( exit_code)
@@ -1276,10 +1313,6 @@ fn normalize_exit_code(code: i32) -> i32 {
12761313 code. clamp ( 0 , 255 )
12771314}
12781315
1279- struct RawModeGuard {
1280- enabled : bool ,
1281- }
1282-
12831316struct SocketFileGuard ( PathBuf ) ;
12841317
12851318impl Drop for SocketFileGuard {
@@ -1288,23 +1321,91 @@ impl Drop for SocketFileGuard {
12881321 }
12891322}
12901323
1291- impl RawModeGuard {
1292- fn enable_if_terminal ( ) -> Result < Self > {
1293- if std:: io:: stdin ( ) . is_terminal ( ) {
1294- crossterm:: terminal:: enable_raw_mode ( ) ?;
1295- Ok ( Self { enabled : true } )
1296- } else {
1297- Ok ( Self { enabled : false } )
1324+ enum ShellSignal {
1325+ Resize ,
1326+ Suspend ,
1327+ Terminate ( i32 ) ,
1328+ }
1329+
1330+ #[ cfg( unix) ]
1331+ struct ShellSignals {
1332+ hangup : tokio:: signal:: unix:: Signal ,
1333+ interrupt : tokio:: signal:: unix:: Signal ,
1334+ quit : tokio:: signal:: unix:: Signal ,
1335+ terminate : tokio:: signal:: unix:: Signal ,
1336+ suspend : tokio:: signal:: unix:: Signal ,
1337+ resize : tokio:: signal:: unix:: Signal ,
1338+ }
1339+
1340+ #[ cfg( not( unix) ) ]
1341+ struct ShellSignals ;
1342+
1343+ #[ cfg( unix) ]
1344+ impl ShellSignals {
1345+ fn new ( ) -> Result < Self > {
1346+ use tokio:: signal:: unix:: { SignalKind , signal} ;
1347+
1348+ Ok ( Self {
1349+ hangup : signal ( SignalKind :: hangup ( ) ) ?,
1350+ interrupt : signal ( SignalKind :: interrupt ( ) ) ?,
1351+ quit : signal ( SignalKind :: quit ( ) ) ?,
1352+ terminate : signal ( SignalKind :: terminate ( ) ) ?,
1353+ suspend : signal ( SignalKind :: from_raw ( libc:: SIGTSTP ) ) ?,
1354+ resize : signal ( SignalKind :: window_change ( ) ) ?,
1355+ } )
1356+ }
1357+
1358+ async fn recv ( & mut self ) -> ShellSignal {
1359+ tokio:: select! {
1360+ _ = self . hangup. recv( ) => ShellSignal :: Terminate ( libc:: SIGHUP ) ,
1361+ _ = self . interrupt. recv( ) => ShellSignal :: Terminate ( libc:: SIGINT ) ,
1362+ _ = self . quit. recv( ) => ShellSignal :: Terminate ( libc:: SIGQUIT ) ,
1363+ _ = self . terminate. recv( ) => ShellSignal :: Terminate ( libc:: SIGTERM ) ,
1364+ _ = self . suspend. recv( ) => ShellSignal :: Suspend ,
1365+ _ = self . resize. recv( ) => ShellSignal :: Resize ,
12981366 }
12991367 }
13001368}
13011369
1302- impl Drop for RawModeGuard {
1303- fn drop ( & mut self ) {
1304- if self . enabled {
1305- let _ = crossterm:: terminal:: disable_raw_mode ( ) ;
1306- }
1370+ #[ cfg( not( unix) ) ]
1371+ impl ShellSignals {
1372+ fn new ( ) -> Result < Self > {
1373+ Ok ( Self )
13071374 }
1375+
1376+ async fn recv ( & mut self ) -> ShellSignal {
1377+ std:: future:: pending ( ) . await
1378+ }
1379+ }
1380+
1381+ #[ cfg( unix) ]
1382+ fn suspend_current_process ( ) {
1383+ // SIGTSTP is intercepted above so we can restore the terminal first. SIGSTOP
1384+ // cannot be caught, which guarantees one real stop; execution resumes here
1385+ // after the process receives SIGCONT.
1386+ unsafe {
1387+ libc:: raise ( libc:: SIGSTOP ) ;
1388+ }
1389+ }
1390+
1391+ #[ cfg( not( unix) ) ]
1392+ fn suspend_current_process ( ) { }
1393+
1394+ #[ cfg( unix) ]
1395+ fn terminate_with_signal ( signal : i32 ) -> ! {
1396+ // Tokio installed the process signal handler. Restore the default action
1397+ // after restoring termios, then re-raise so parents observe a signal exit
1398+ // instead of a fabricated numeric status.
1399+ unsafe {
1400+ libc:: signal ( signal, libc:: SIG_DFL ) ;
1401+ libc:: raise ( signal) ;
1402+ libc:: _exit ( 128 + signal) ;
1403+ }
1404+ }
1405+
1406+ #[ cfg( not( unix) ) ]
1407+ fn terminate_with_signal ( _signal : i32 ) -> ! {
1408+ std:: process:: exit ( 1 )
13081409}
13091410
13101411async fn spawn_daemon ( home : & FabricHome , options : DaemonOptions ) -> Result < ( ) > {
0 commit comments