diff --git a/src/frontend/mosh-server.cc b/src/frontend/mosh-server.cc index 5a1dbeb..aab2ead 100644 --- a/src/frontend/mosh-server.cc +++ b/src/frontend/mosh-server.cc @@ -638,6 +638,7 @@ static void serve( int host_fd, Terminal::Complete &terminal, ServerConnection & now = Network::timestamp(); uint64_t time_since_remote_state = now - network.get_latest_remote_state().timestamp; + string terminal_to_host; if ( sel.read( network_fd ) ) { /* packet received from the network */ @@ -647,7 +648,6 @@ static void serve( int host_fd, Terminal::Complete &terminal, ServerConnection & if ( network.get_remote_state_num() != last_remote_num ) { last_remote_num = network.get_remote_state_num(); - string terminal_to_host; Network::UserStream us; us.apply_string( network.get_remote_diff() ); @@ -687,11 +687,6 @@ static void serve( int host_fd, Terminal::Complete &terminal, ServerConnection & network.set_current_state( terminal ); } - /* write any writeback octets back to the host */ - if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) { - break; - } - #ifdef HAVE_UTEMPTER /* update utmp entry if we have become "connected" */ if ( (!connected_utmp) @@ -734,18 +729,18 @@ static void serve( int host_fd, Terminal::Complete &terminal, ServerConnection & if ( bytes_read <= 0 ) { network.start_shutdown(); } else { - string terminal_to_host = terminal.act( string( buf, bytes_read ) ); + terminal_to_host += terminal.act( string( buf, bytes_read ) ); /* update client with new state of terminal */ network.set_current_state( terminal ); - - /* write any writeback octets back to the host */ - if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) { - break; - } } } + /* write user input and terminal writeback to the host */ + if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) { + break; + } + bool idle_shutdown = false; if ( network_timeout_ms && network_timeout_ms <= time_since_remote_state ) { diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index e75c599..4d3e2e2 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -21,6 +21,7 @@ displaytests = \ emulation-cursor-motion.test \ emulation-multiline-scroll.test \ emulation-wrap-across-frames.test \ + pty-deadlock.test \ server-network-timeout.test \ server-signal-timeout.test \ window-resize.test \ diff --git a/src/tests/pty-deadlock.test b/src/tests/pty-deadlock.test new file mode 100755 index 0000000..3d46697 --- /dev/null +++ b/src/tests/pty-deadlock.test @@ -0,0 +1,93 @@ +#!/bin/sh + +# +# This is a regression test for a BSD pty bug in Mosh. On +# FreeBSD/OpenBSD/OS X, a pty master can block on read() after +# select() has informed us that there is data available, if a ^S is +# written to the pty master between the select() and the read(). +# +# Unfortunately, everything attached to the pty gets stuck when this +# happens. If this tests fails, you will need to do some manual +# cleanup with kill -9. +# + + +fail() +{ + printf "$@" 2>&1 + exit 99 +} + + + +PATH=$PATH:.:$srcdir +# Top-level wrapper. +if [ $# -eq 0 ]; then + e2e-test $0 tmux baseline post + exit +fi + +tmux_commands() +{ + # An interactive shell is waiting for us in the mosh session. + # Start test... + printf "send-keys 0x0d\n" + sleep 1 + # Stop output... + printf "send-keys 0x13\n" + sleep 2 + # Restart output... + printf "send-keys 0x11\n" + sleep 2 + # And stop the test script, so it produces its exit messge. + printf "send-keys 0x0d\n" + # need to sleep extra long here, to let child commands complete, + # and not have tmux exit prematurely + sleep 5 +} + +tmux_stdin() +{ + tmux_commands | "$@" + exit +} + +baseline() +{ + # Make a lot of noise on stdout to keep mosh busy, and exit + # with a distinctive message when we get a CR. Exit after 10s in any case. + trap "exit 1" TERM + (sleep 10; kill $$) & + killpid=$! + read x + # very, very old school way to get non-blocking reads in shell + tty=$(stty -g) + trap "stty $tty" EXIT + stty -icanon min 0 time 0 + while ! read x; do + printf 'a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n' + done + printf "=== normal exit ===\n" + # Kill the killer and exit normally. + kill $killpid +} + +post() +{ + if grep -q '=== normal exit ===' $(basename $0).d/baseline.capture; then + exit 0 + fi + exit 1 +} + +case $1 in + tmux) + shift; + tmux_stdin "$@";; + baseline) + baseline;; + post) + post;; + *) + fail "unknown test argument %s\n" $1;; +esac