Skip to content

Commit f0be34d

Browse files
committed
kernel: Restore prim_tty signals option to pass control bytes through in raw mode
1 parent 6146f0d commit f0be34d

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
@@ -1073,7 +1073,7 @@ static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM
10731073

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

1076-
ERL_NIF_TERM input;
1076+
ERL_NIF_TERM input, signals;
10771077
TTYResource *tty;
10781078

10791079
debug("tty_init_nif(%T,%T)\r\n", argv[0], argv[1]);
@@ -1088,6 +1088,14 @@ static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
10881088
if (!enif_get_map_value(env, argv[1], enif_make_atom(env, "input"), &input))
10891089
return enif_make_badarg(env);
10901090

1091+
/* Whether signal and flow-control handling should stay enabled in
1092+
raw mode. Defaults to true to preserve the shell's ctrl+c break
1093+
behavior. When false, ISIG, IEXTEN and IXON are disabled so that
1094+
control bytes such as ctrl+o, ctrl+c and ctrl+s/ctrl+q reach the
1095+
application. */
1096+
if (!enif_get_map_value(env, argv[1], enif_make_atom(env, "signals"), &signals))
1097+
signals = atom_true;
1098+
10911099
if (tty->tty == unavailable) {
10921100
if (enif_is_identical(input, atom_raw))
10931101
return make_enotsup(env);
@@ -1126,6 +1134,15 @@ static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
11261134
/* erts_fprintf(stderr,"echo %T\r\n", echo); */
11271135
tty->tty_smode.c_lflag &= ~ECHO;
11281136

1137+
if (enif_is_identical(signals, atom_false)) {
1138+
/* Also disable signal and flow-control handling so that
1139+
control bytes are passed through to the application.
1140+
This restores the sig => false behavior that the
1141+
prim_tty API had before the OTP 27 cleanup. */
1142+
tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|IXON|IXANY);
1143+
tty->tty_smode.c_lflag &= ~(ISIG|IEXTEN);
1144+
}
1145+
11291146
}
11301147

11311148
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
@@ -166,7 +166,8 @@
166166

167167
-type options() :: #{ input := cooked | raw | disabled,
168168
output := raw | cooked,
169-
ofd => stdout | stderr }.
169+
ofd => stdout | stderr,
170+
signals => boolean() }.
170171
-type request() ::
171172
{putc_raw, binary()} |
172173
{putc, unicode:unicode_binary()} |
@@ -326,7 +327,8 @@ reinit(State = #state{ options = OldOptions }, UserOptions) ->
326327
end.
327328

328329
options(UserOptions) ->
329-
maps:merge(#{ input => raw, output => cooked, ofd => stdout }, UserOptions).
330+
maps:merge(#{ input => raw, output => cooked, ofd => stdout,
331+
signals => true }, UserOptions).
330332

331333
init(State, ssh) ->
332334
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},
@@ -2136,6 +2137,69 @@ noshell_raw(Config) ->
21362137
end,
21372138
ok.
21382139

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

0 commit comments

Comments
 (0)