@@ -115,6 +115,45 @@ impl InputHandler {
115115 }
116116 }
117117 }
118+
119+ /// Suspends the input handler temporarily, restoring the original terminal
120+ /// state.
121+ ///
122+ /// Used by the stop signal handler.
123+ #[ cfg( unix) ]
124+ pub ( crate ) fn suspend ( & mut self ) {
125+ let Some ( ( handler, _) ) = self . imp . as_mut ( ) else {
126+ return ;
127+ } ;
128+
129+ if let Err ( error) = handler. restore ( ) {
130+ warn ! ( "failed to suspend terminal non-canonical mode: {}" , error) ;
131+ // Don't set imp to None -- we want to try to reinit() on resume.
132+ }
133+ }
134+
135+ /// Resumes the input handler after a suspension.
136+ ///
137+ /// Used by the continue signal handler.
138+ #[ cfg( unix) ]
139+ pub ( crate ) fn resume ( & mut self ) {
140+ let Some ( ( handler, _) ) = self . imp . as_mut ( ) else {
141+ // None means that the input handler is disabled, so there is
142+ // nothing to resume.
143+ return ;
144+ } ;
145+
146+ if let Err ( error) = handler. reinit ( ) {
147+ warn ! (
148+ "failed to resume terminal non-canonical mode, \
149+ cannot read input events: {}",
150+ error
151+ ) ;
152+ // Do set self.imp to None in this case -- we want to indicate to
153+ // callers (e.g. via status()) that the input handler is disabled.
154+ self . imp = None ;
155+ }
156+ }
118157}
119158
120159/// The status of the input handler, returned by
@@ -151,7 +190,7 @@ impl InputHandlerImpl {
151190 let panic_hook = std:: panic:: take_hook ( ) ;
152191 std:: panic:: set_hook ( Box :: new ( move |info| {
153192 // Ignore errors to avoid double-panicking.
154- if let Err ( error) = ret2. finish ( ) {
193+ if let Err ( error) = ret2. restore ( ) {
155194 eprintln ! (
156195 "failed to restore terminal state: {}" ,
157196 DisplayErrorChain :: new( error)
@@ -163,22 +202,44 @@ impl InputHandlerImpl {
163202 Ok ( ret)
164203 }
165204
166- fn finish ( & self ) -> Result < ( ) , InputHandlerFinishError > {
205+ #[ cfg( unix) ]
206+ fn reinit ( & self ) -> Result < ( ) , InputHandlerCreateError > {
207+ // Make a new input guard and replace the old one. Don't set a new panic
208+ // hook.
209+ //
210+ // The mutex is shared by the panic hook and self/the drop handler, so
211+ // the change below will also be visible to the panic hook. But we
212+ // acquire the mutex first to avoid a potential race where multiple
213+ // calls to reinit() can happen concurrently.
214+ //
215+ // Also note that if this fails, the old InputGuard will be visible to
216+ // the panic hook, which is fine -- since we called restore() first, the
217+ // terminal state is already restored and guard is None.
218+ let mut locked = self
219+ . guard
220+ . lock ( )
221+ . map_err ( |_| InputHandlerCreateError :: Poisoned ) ?;
222+ let guard = imp:: InputGuard :: new ( ) . map_err ( InputHandlerCreateError :: EnableNonCanonical ) ?;
223+ * locked = guard;
224+ Ok ( ( ) )
225+ }
226+
227+ fn restore ( & self ) -> Result < ( ) , InputHandlerFinishError > {
167228 // Do not panic here, in case a panic happened while the thread was
168229 // locked. Instead, ignore the error.
169230 let mut locked = self
170231 . guard
171232 . lock ( )
172233 . map_err ( |_| InputHandlerFinishError :: Poisoned ) ?;
173- locked. finish ( ) . map_err ( InputHandlerFinishError :: Restore )
234+ locked. restore ( ) . map_err ( InputHandlerFinishError :: Restore )
174235 }
175236}
176237
177238// Defense in depth -- use both the Drop impl (for regular drops and
178239// panic=unwind) and a panic hook (for panic=abort).
179240impl Drop for InputHandlerImpl {
180241 fn drop ( & mut self ) {
181- if let Err ( error) = self . finish ( ) {
242+ if let Err ( error) = self . restore ( ) {
182243 eprintln ! (
183244 "failed to restore terminal state: {}" ,
184245 DisplayErrorChain :: new( error)
@@ -191,6 +252,10 @@ impl Drop for InputHandlerImpl {
191252enum InputHandlerCreateError {
192253 #[ error( "failed to enable terminal non-canonical mode" ) ]
193254 EnableNonCanonical ( #[ source] imp:: Error ) ,
255+
256+ #[ cfg( unix) ]
257+ #[ error( "mutex was poisoned while reinitializing terminal state" ) ]
258+ Poisoned ,
194259}
195260
196261#[ derive( Debug , Error ) ]
@@ -228,35 +293,15 @@ mod imp {
228293
229294 impl InputGuard {
230295 pub ( super ) fn new ( ) -> io:: Result < Self > {
231- let mut termios = mem:: MaybeUninit :: uninit ( ) ;
232- let res = unsafe { tcgetattr ( std:: io:: stdin ( ) . as_raw_fd ( ) , termios. as_mut_ptr ( ) ) } ;
233- if res == -1 {
234- return Err ( io:: Error :: last_os_error ( ) ) ;
235- }
236-
237- // SAFETY: if res is 0, then termios has been initialized.
238- let original = unsafe { termios. assume_init ( ) } ;
239-
240- let mut updated = original;
241-
242- // Disable echoing inputs and canonical mode. We don't disable things like ISIG -- we
243- // handle that via the signal handler.
244- updated. c_lflag &= !( ECHO | ICANON ) ;
245- // VMIN is 1 and VTIME is 0: this enables blocking reads of 1 byte
246- // at a time with no timeout. See
247- // https://linux.die.net/man/3/tcgetattr's "Canonical and
248- // noncanonical mode" section.
249- updated. c_cc [ VMIN ] = 1 ;
250- updated. c_cc [ VTIME ] = 0 ;
251-
296+ let TermiosPair { original, updated } = compute_termios ( ) ?;
252297 stdin_tcsetattr ( TCSAFLUSH , & updated) ?;
253298
254299 Ok ( Self {
255300 original : Some ( original) ,
256301 } )
257302 }
258303
259- pub ( super ) fn finish ( & mut self ) -> io:: Result < ( ) > {
304+ pub ( super ) fn restore ( & mut self ) -> io:: Result < ( ) > {
260305 if let Some ( original) = self . original . take ( ) {
261306 stdin_tcsetattr ( TCSANOW , & original)
262307 } else {
@@ -265,6 +310,37 @@ mod imp {
265310 }
266311 }
267312
313+ fn compute_termios ( ) -> io:: Result < TermiosPair > {
314+ let mut termios = mem:: MaybeUninit :: uninit ( ) ;
315+ let res = unsafe { tcgetattr ( std:: io:: stdin ( ) . as_raw_fd ( ) , termios. as_mut_ptr ( ) ) } ;
316+ if res == -1 {
317+ return Err ( io:: Error :: last_os_error ( ) ) ;
318+ }
319+
320+ // SAFETY: if res is 0, then termios has been initialized.
321+ let original = unsafe { termios. assume_init ( ) } ;
322+
323+ let mut updated = original;
324+
325+ // Disable echoing inputs and canonical mode. We don't disable things like ISIG -- we
326+ // handle that via the signal handler.
327+ updated. c_lflag &= !( ECHO | ICANON ) ;
328+ // VMIN is 1 and VTIME is 0: this enables blocking reads of 1 byte
329+ // at a time with no timeout. See
330+ // https://linux.die.net/man/3/tcgetattr's "Canonical and
331+ // noncanonical mode" section.
332+ updated. c_cc [ VMIN ] = 1 ;
333+ updated. c_cc [ VTIME ] = 0 ;
334+
335+ Ok ( TermiosPair { original, updated } )
336+ }
337+
338+ #[ derive( Clone , Debug ) ]
339+ struct TermiosPair {
340+ original : libc:: termios ,
341+ updated : libc:: termios ,
342+ }
343+
268344 fn stdin_tcsetattr ( optional_actions : c_int , updated : & libc:: termios ) -> io:: Result < ( ) > {
269345 let res = unsafe { tcsetattr ( std:: io:: stdin ( ) . as_raw_fd ( ) , optional_actions, updated) } ;
270346 if res == -1 {
@@ -321,7 +397,7 @@ mod imp {
321397 } )
322398 }
323399
324- pub ( super ) fn finish ( & mut self ) -> io:: Result < ( ) > {
400+ pub ( super ) fn restore ( & mut self ) -> io:: Result < ( ) > {
325401 if let Some ( original) = self . original . take ( ) {
326402 let handle = std:: io:: stdin ( ) . as_raw_handle ( ) ;
327403 let res = unsafe { SetConsoleMode ( handle, original) } ;
0 commit comments