Skip to content

Commit 25a4cfc

Browse files
committed
kernel: Restore prim_tty signals option to pass control bytes through in raw mode
1 parent ee10b4f commit 25a4cfc

3 files changed

Lines changed: 88 additions & 5 deletions

File tree

erts/emulator/nifs/common/prim_tty_nif.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@ static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM
961961

962962
static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
963963

964-
ERL_NIF_TERM input;
964+
ERL_NIF_TERM input, signals;
965965
TTYResource *tty;
966966

967967
debug("tty_init_nif(%T,%T)\r\n", argv[0], argv[1]);
@@ -976,6 +976,14 @@ static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
976976
if (!enif_get_map_value(env, argv[1], enif_make_atom(env, "input"), &input))
977977
return enif_make_badarg(env);
978978

979+
/* Whether signal and flow-control handling should stay enabled in
980+
raw mode. Defaults to true to preserve the shell's ctrl+c break
981+
behavior. When false, ISIG, IEXTEN and IXON are disabled so that
982+
control bytes such as ctrl+o, ctrl+c and ctrl+s/ctrl+q reach the
983+
application. */
984+
if (!enif_get_map_value(env, argv[1], enif_make_atom(env, "signals"), &signals))
985+
signals = atom_true;
986+
979987
if (tty->tty == unavailable) {
980988
if (enif_is_identical(input, atom_raw))
981989
return make_enotsup(env);
@@ -1014,6 +1022,15 @@ static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
10141022
/* erts_fprintf(stderr,"echo %T\r\n", echo); */
10151023
tty->tty_smode.c_lflag &= ~ECHO;
10161024

1025+
if (enif_is_identical(signals, atom_false)) {
1026+
/* Also disable signal and flow-control handling so that
1027+
control bytes are passed through to the application.
1028+
This restores the sig => false behavior that the
1029+
prim_tty API had before the OTP 27 cleanup. */
1030+
tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|IXON|IXANY);
1031+
tty->tty_smode.c_lflag &= ~(ISIG|IEXTEN);
1032+
}
1033+
10171034
}
10181035

10191036
if (tcsetattr(tty->ofd, TCSANOW, &tty->tty_smode) < 0) {

lib/kernel/src/prim_tty.erl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@
171171

172172
-type options() :: #{ input := cooked | raw | disabled,
173173
output := raw | cooked,
174-
ofd => stdout | stderr }.
174+
ofd => stdout | stderr,
175+
signals => boolean() }.
175176
-type request() ::
176177
{putc_raw, binary()} |
177178
{putc, unicode:unicode_binary()} |
@@ -352,7 +353,8 @@ reinit(State = #state{ options = OldOptions }, UserOptions) ->
352353
end.
353354

354355
options(UserOptions) ->
355-
maps:merge(#{ input => raw, output => cooked, ofd => stdout }, UserOptions).
356+
maps:merge(#{ input => raw, output => cooked, ofd => stdout,
357+
signals => true }, UserOptions).
356358

357359
init(State, ssh) ->
358360
State#state{ xn = true };

lib/kernel/test/interactive_shell_SUITE.erl

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
init_per_testcase/2, end_per_testcase/2,
4444
get_columns_and_rows/1, exit_initial/1, job_control_local/1,
4545
job_control_remote/1,stop_during_init/1,wrap/1,
46-
noshell_raw/1,
46+
noshell_raw/1, raw_signals/1,
4747
shell_history/1, shell_history_resize/1, shell_history_eaccess/1,
4848
shell_history_repair/1, shell_history_repair_corrupt/1,
4949
shell_history_corrupt/1,
@@ -154,7 +154,8 @@ groups() ->
154154
external_editor,
155155
external_editor_visual,
156156
shell_ignore_pager_commands,
157-
shell_help
157+
shell_help,
158+
raw_signals
158159
]},
159160
{tty_unicode,[parallel],
160161
[{group,tty_tests},
@@ -2125,6 +2126,69 @@ noshell_raw(Config) ->
21252126
end,
21262127
ok.
21272128

2129+
%% Verify that control bytes reach the application in raw mode when
2130+
%% signal and flow-control handling is disabled. ctrl+o is VDISCARD on
2131+
%% POSIX: while IEXTEN is set the terminal driver swallows the byte, so
2132+
%% the reader would time out. Uses a noshell peer on a tmux pty so that
2133+
%% prim_tty is the only reader of the terminal.
2134+
raw_signals(Config) ->
2135+
Term = shell_test_lib:setup_tty(
2136+
[{args, ["-noshell", "-noinput"]} | Config]),
2137+
2138+
try
2139+
%% Init the terminal in raw mode with signals disabled and start
2140+
%% a reader that reports the first byte it receives.
2141+
ok = shell_test_lib:rpc(Term, fun() ->
2142+
Self = self(),
2143+
spawn(fun() ->
2144+
%% prim_tty names its reader and writer processes after
2145+
%% the parent, so the parent must be registered.
2146+
register(raw_signals_probe, self()),
2147+
ok = prim_tty:load(),
2148+
TTY = prim_tty:init(#{input => raw, output => cooked,
2149+
signals => false}),
2150+
#{read := Ref} = prim_tty:handles(TTY),
2151+
Self ! ready,
2152+
ok = prim_tty:read(TTY, 1),
2153+
Res = receive
2154+
{Ref, {data, Data}} -> {ok, Data};
2155+
{Ref, eof} -> eof
2156+
after 5000 -> timeout
2157+
end,
2158+
persistent_term:put({?MODULE, raw_signals}, Res)
2159+
end),
2160+
receive ready -> ok after 10000 -> timeout end
2161+
end),
2162+
2163+
%% Send ctrl+o (0x0f) through the pty. With signals enabled this
2164+
%% byte is consumed by the terminal driver's discard function
2165+
%% and never reaches the application. tmux send-keys writes to
2166+
%% the master side, so the byte passes through the line
2167+
%% discipline of the pane.
2168+
shell_test_lib:send_tty(Term, "C-o"),
2169+
2170+
%% The byte must be delivered to the reader.
2171+
{ok, <<$\^o>>} = await_raw_signals(Term)
2172+
after
2173+
shell_test_lib:stop_tty(Term)
2174+
end.
2175+
2176+
await_raw_signals(Term) ->
2177+
await_raw_signals(Term, 50).
2178+
2179+
await_raw_signals(_Term, 0) ->
2180+
not_set;
2181+
await_raw_signals(Term, N) ->
2182+
case shell_test_lib:rpc(Term, fun() ->
2183+
persistent_term:get({?MODULE, raw_signals}, not_set)
2184+
end) of
2185+
not_set ->
2186+
timer:sleep(100),
2187+
await_raw_signals(Term, N - 1);
2188+
Res ->
2189+
Res
2190+
end.
2191+
21282192
get_until(start, NewChars) ->
21292193
get_until([], NewChars);
21302194
get_until(State, NewChars) ->

0 commit comments

Comments
 (0)