@@ -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
@@ -1106,48 +1108,83 @@ async fn wait_for_daemon_ready(
11061108
11071109async fn run_shell_client ( socket : & PathBuf ) -> Result < i32 > {
11081110 let stream = tokio:: net:: UnixStream :: connect ( socket) . await ?;
1109- let ( mut read, mut write) = stream. into_split ( ) ;
1110- let _raw_mode = RawModeGuard :: enable_if_terminal ( ) ?;
1111+ let ( mut read, write) = stream. into_split ( ) ;
1112+ let mut signals = ShellSignals :: new ( ) ?;
1113+ let terminal = TerminalModeGuard :: enable_if_terminal ( ) ?;
11111114 let ( cols, rows) = terminal_size ( ) ;
1112- shell:: write_client_resize ( & mut write, rows, cols) . await ?;
1115+ let write = Arc :: new ( tokio:: sync:: Mutex :: new ( write) ) ;
1116+ shell:: write_client_resize ( & mut * write. lock ( ) . await , rows, cols) . await ?;
11131117
1118+ let stdin_write = write. clone ( ) ;
11141119 let stdin_task = tokio:: spawn ( async move {
11151120 let mut stdin = tokio:: io:: stdin ( ) ;
11161121 let mut buf = [ 0u8 ; 8192 ] ;
11171122 loop {
11181123 let read = stdin. read ( & mut buf) . await ?;
11191124 if read == 0 {
1120- shell:: write_client_eof ( & mut write ) . await ?;
1125+ shell:: write_client_eof ( & mut * stdin_write . lock ( ) . await ) . await ?;
11211126 return Ok :: < ( ) , anyhow:: Error > ( ( ) ) ;
11221127 }
1123- shell:: write_client_stdin ( & mut write , & buf[ ..read] ) . await ?;
1128+ shell:: write_client_stdin ( & mut * stdin_write . lock ( ) . await , & buf[ ..read] ) . await ?;
11241129 }
11251130 } ) ;
11261131
11271132 let mut stdout = tokio:: io:: stdout ( ) ;
11281133 let mut stderr = tokio:: io:: stderr ( ) ;
11291134 let mut exit_code = 1 ;
11301135
1131- while let Some ( frame) = shell:: read_server_frame ( & mut read) . await ? {
1132- match frame {
1133- ServerFrame :: Output ( bytes) => {
1134- stdout. write_all ( & bytes) . await ?;
1135- stdout. flush ( ) . await ?;
1136- }
1137- ServerFrame :: Error ( message) => {
1138- stderr. write_all ( message. as_bytes ( ) ) . await ?;
1139- stderr. write_all ( b"\n " ) . await ?;
1140- stderr. flush ( ) . await ?;
1136+ loop {
1137+ tokio:: select! {
1138+ frame = shell:: read_server_frame( & mut read) => {
1139+ let Some ( frame) = frame? else {
1140+ break ;
1141+ } ;
1142+ match frame {
1143+ ServerFrame :: Output ( bytes) => {
1144+ stdout. write_all( & bytes) . await ?;
1145+ stdout. flush( ) . await ?;
1146+ }
1147+ ServerFrame :: Error ( message) => {
1148+ stderr. write_all( message. as_bytes( ) ) . await ?;
1149+ stderr. write_all( b"\n " ) . await ?;
1150+ stderr. flush( ) . await ?;
1151+ }
1152+ ServerFrame :: Status ( message) => {
1153+ stderr. write_all( message. as_bytes( ) ) . await ?;
1154+ stderr. write_all( b"\n " ) . await ?;
1155+ stderr. flush( ) . await ?;
1156+ }
1157+ ServerFrame :: Exit ( code) => {
1158+ exit_code = normalize_exit_code( code) ;
1159+ break ;
1160+ }
1161+ }
11411162 }
1142- ServerFrame :: Exit ( code) => {
1143- exit_code = normalize_exit_code ( code) ;
1144- break ;
1163+ signal = signals. recv( ) => {
1164+ match signal {
1165+ ShellSignal :: Resize => {
1166+ let ( cols, rows) = terminal_size( ) ;
1167+ shell:: write_client_resize( & mut * write. lock( ) . await , rows, cols) . await ?;
1168+ }
1169+ ShellSignal :: Suspend => {
1170+ terminal. restore( ) ?;
1171+ suspend_current_process( ) ;
1172+ terminal. reenter_raw( ) ?;
1173+ let ( cols, rows) = terminal_size( ) ;
1174+ shell:: write_client_resize( & mut * write. lock( ) . await , rows, cols) . await ?;
1175+ }
1176+ ShellSignal :: Terminate ( signal) => {
1177+ terminal. restore( ) ?;
1178+ terminate_with_signal( signal) ;
1179+ }
1180+ }
11451181 }
11461182 }
11471183 }
11481184
11491185 stdin_task. abort ( ) ;
11501186 let _ = stdin_task. await ;
1187+ terminal. restore ( ) ?;
11511188 stdout. flush ( ) . await ?;
11521189 stderr. flush ( ) . await ?;
11531190 Ok ( exit_code)
@@ -1280,10 +1317,6 @@ fn normalize_exit_code(code: i32) -> i32 {
12801317 code. clamp ( 0 , 255 )
12811318}
12821319
1283- struct RawModeGuard {
1284- enabled : bool ,
1285- }
1286-
12871320struct SocketFileGuard ( PathBuf ) ;
12881321
12891322impl Drop for SocketFileGuard {
@@ -1292,23 +1325,91 @@ impl Drop for SocketFileGuard {
12921325 }
12931326}
12941327
1295- impl RawModeGuard {
1296- fn enable_if_terminal ( ) -> Result < Self > {
1297- if std:: io:: stdin ( ) . is_terminal ( ) {
1298- crossterm:: terminal:: enable_raw_mode ( ) ?;
1299- Ok ( Self { enabled : true } )
1300- } else {
1301- Ok ( Self { enabled : false } )
1328+ enum ShellSignal {
1329+ Resize ,
1330+ Suspend ,
1331+ Terminate ( i32 ) ,
1332+ }
1333+
1334+ #[ cfg( unix) ]
1335+ struct ShellSignals {
1336+ hangup : tokio:: signal:: unix:: Signal ,
1337+ interrupt : tokio:: signal:: unix:: Signal ,
1338+ quit : tokio:: signal:: unix:: Signal ,
1339+ terminate : tokio:: signal:: unix:: Signal ,
1340+ suspend : tokio:: signal:: unix:: Signal ,
1341+ resize : tokio:: signal:: unix:: Signal ,
1342+ }
1343+
1344+ #[ cfg( not( unix) ) ]
1345+ struct ShellSignals ;
1346+
1347+ #[ cfg( unix) ]
1348+ impl ShellSignals {
1349+ fn new ( ) -> Result < Self > {
1350+ use tokio:: signal:: unix:: { SignalKind , signal} ;
1351+
1352+ Ok ( Self {
1353+ hangup : signal ( SignalKind :: hangup ( ) ) ?,
1354+ interrupt : signal ( SignalKind :: interrupt ( ) ) ?,
1355+ quit : signal ( SignalKind :: quit ( ) ) ?,
1356+ terminate : signal ( SignalKind :: terminate ( ) ) ?,
1357+ suspend : signal ( SignalKind :: from_raw ( libc:: SIGTSTP ) ) ?,
1358+ resize : signal ( SignalKind :: window_change ( ) ) ?,
1359+ } )
1360+ }
1361+
1362+ async fn recv ( & mut self ) -> ShellSignal {
1363+ tokio:: select! {
1364+ _ = self . hangup. recv( ) => ShellSignal :: Terminate ( libc:: SIGHUP ) ,
1365+ _ = self . interrupt. recv( ) => ShellSignal :: Terminate ( libc:: SIGINT ) ,
1366+ _ = self . quit. recv( ) => ShellSignal :: Terminate ( libc:: SIGQUIT ) ,
1367+ _ = self . terminate. recv( ) => ShellSignal :: Terminate ( libc:: SIGTERM ) ,
1368+ _ = self . suspend. recv( ) => ShellSignal :: Suspend ,
1369+ _ = self . resize. recv( ) => ShellSignal :: Resize ,
13021370 }
13031371 }
13041372}
13051373
1306- impl Drop for RawModeGuard {
1307- fn drop ( & mut self ) {
1308- if self . enabled {
1309- let _ = crossterm:: terminal:: disable_raw_mode ( ) ;
1310- }
1374+ #[ cfg( not( unix) ) ]
1375+ impl ShellSignals {
1376+ fn new ( ) -> Result < Self > {
1377+ Ok ( Self )
13111378 }
1379+
1380+ async fn recv ( & mut self ) -> ShellSignal {
1381+ std:: future:: pending ( ) . await
1382+ }
1383+ }
1384+
1385+ #[ cfg( unix) ]
1386+ fn suspend_current_process ( ) {
1387+ // SIGTSTP is intercepted above so we can restore the terminal first. SIGSTOP
1388+ // cannot be caught, which guarantees one real stop; execution resumes here
1389+ // after the process receives SIGCONT.
1390+ unsafe {
1391+ libc:: raise ( libc:: SIGSTOP ) ;
1392+ }
1393+ }
1394+
1395+ #[ cfg( not( unix) ) ]
1396+ fn suspend_current_process ( ) { }
1397+
1398+ #[ cfg( unix) ]
1399+ fn terminate_with_signal ( signal : i32 ) -> ! {
1400+ // Tokio installed the process signal handler. Restore the default action
1401+ // after restoring termios, then re-raise so parents observe a signal exit
1402+ // instead of a fabricated numeric status.
1403+ unsafe {
1404+ libc:: signal ( signal, libc:: SIG_DFL ) ;
1405+ libc:: raise ( signal) ;
1406+ libc:: _exit ( 128 + signal) ;
1407+ }
1408+ }
1409+
1410+ #[ cfg( not( unix) ) ]
1411+ fn terminate_with_signal ( _signal : i32 ) -> ! {
1412+ std:: process:: exit ( 1 )
13121413}
13131414
13141415async fn spawn_daemon ( home : & FabricHome , options : DaemonOptions ) -> Result < ( ) > {
0 commit comments