Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 83850ac

Browse files
sortieCommit Bot
authored and
Commit Bot
committedMay 31, 2022
[io] Don't restore terminal state on exit.
This is breaking change #45630. The Dart VM has until now restored the terminal settings upon exit to their initial values for stdin, stdout, and stderr. This change removes that automatic behavior in favor of having the program do the restoration. Previously the intention was that dart programs can enable/disable echoing and line buffering and not worry about restoring the original settings. However, the VM doing so unconditionally leads to undesirable behavior e.g. when the program does not care about terminal settings and is sharing a process group with a program that does care. E.g. if dart's output is piped into less(1), then there is a race condition where dart might see the raw terminal settings set by less(1), and if the dart VM exits after less(1) has exited, then it will restore the raw terminal settings, leaving the user with a seemingly defective shell with echo disabled. This race condition can be reproduced using: cat > yes.dart << EOF main() { for (int i = 0; i < 1000000; i++) { print("yes"); } } EOF stty; (sleep 1 && dart yes.dart) | less; stty; stty sane The user will end up with a shell with echo behavior disabled. The stty command shows the current terminal settings, where the difference can be seen, and 'stty sane' fixes the settings before returning to the shell prompt. The 'stty sane' call can be omitted to see the defective shell prompt. This change removes the terminal restoring behavior (added in Dart 2.0.0) and instead asks applications to do the restoration themselves. The new design matches how programs in other programming languages implement interactive input that changes terminal settings. Furthermore the `echoMode` setting now only controls the `echo` local mode and no longer sets the `echonl` local mode on POSIX systems (which controls whether newline are echoed even if the regular echo mode is disabled). The `echonl` local mode is usually turned off in common shell environments. Programs that wish to control the `echonl` local mode can use the new `echoNewlineMode` setting. This change is required to prevent the reoccurence of #30318 when programs manually restore `echoMode`. Windows has further considerations: It also saves the console code pages and restore them if they were not UTF-8. This behavior is retained as it is useful and needed for Dart's output to function properly. ANSI output sequences are also turned on via ENABLE_VIRTUAL_TERMINAL_PROCESSING, which is slightly changed in this change to only rsetore that setting if it wasn't already on for consistency. Closes #36453 Closes #45630 TEST=Reproduced with less as above Change-Id: I2991f9c7f47b97fe475c1ad6edeb769024f8d0db Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/190484 Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Jonas Termansen <sortie@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
1 parent 69b9af2 commit 83850ac

18 files changed

+270
-69
lines changed
 

‎CHANGELOG.md

+46
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,52 @@ them, you must set the lower bound on the SDK constraint for your package to
5454
- Add `connectionState` attribute and `connectionstatechange` listener to
5555
`RtcPeerConnection`.
5656

57+
#### `dart:io`
58+
59+
- **Breaking Change** [#45630][]: The Dart VM no longer automatically restores
60+
the initial terminal settings upon exit. Programs that change the `Stdin`
61+
settings `lineMode` and `echoMode` are now responsible for restoring the
62+
settings upon program exit. E.g. a program disabling `echoMode` will now
63+
need to restore the setting itself and handle exiting by the appropriate
64+
signals if desired:
65+
66+
```dart
67+
import 'dart:io';
68+
import 'dart:async';
69+
70+
main() {
71+
bool echoWasEnabled = stdin.echoMode;
72+
try {
73+
late StreamSubscription subscription;
74+
subscription = ProcessSignal.sigint.watch().listen((ProcessSignal signal) {
75+
stdin.echoMode = echoWasEnabled;
76+
subscription.cancel();
77+
Process.killPid(pid, signal); /* Die by the signal. */
78+
});
79+
stdin.echoMode = false;
80+
} finally {
81+
stdin.echoMode = echoWasEnabled;
82+
}
83+
}
84+
```
85+
86+
This change is needed to fix [#36453][] where the dart programs not caring
87+
about the terminal settings can inadverently corrupt the terminal settings
88+
when e.g. piping into less.
89+
90+
Furthermore the `echoMode` setting now only controls the `echo` local mode
91+
and no longer sets the `echonl` local mode on POSIX systems (which controls
92+
whether newline are echoed even if the regular echo mode is disabled). The
93+
`echonl` local mode is usually turned off in common shell environments.
94+
Programs that wish to control the `echonl` local mode can use the new
95+
`echoNewlineMode` setting.
96+
97+
The Windows console code pages (if not UTF-8) and ANSI escape code support
98+
(if disabled) remain restored when the VM exits.
99+
100+
[#45630]: https://github.com/dart-lang/sdk/issues/45630
101+
[#36453]: https://github.com/dart-lang/sdk/issues/36453
102+
57103
#### `dart:js_util`
58104
59105
- Added `dartify` and a number of minor helper functions.

‎runtime/bin/console_posix.cc

-61
Original file line numberDiff line numberDiff line change
@@ -18,71 +18,10 @@
1818
namespace dart {
1919
namespace bin {
2020

21-
class PosixConsole {
22-
public:
23-
static const tcflag_t kInvalidFlag = -1;
24-
25-
static void Initialize() {
26-
SaveMode(STDOUT_FILENO, &stdout_initial_c_lflag_);
27-
SaveMode(STDERR_FILENO, &stderr_initial_c_lflag_);
28-
SaveMode(STDIN_FILENO, &stdin_initial_c_lflag_);
29-
}
30-
31-
static void Cleanup() {
32-
RestoreMode(STDOUT_FILENO, stdout_initial_c_lflag_);
33-
RestoreMode(STDERR_FILENO, stderr_initial_c_lflag_);
34-
RestoreMode(STDIN_FILENO, stdin_initial_c_lflag_);
35-
ClearLFlags();
36-
}
37-
38-
private:
39-
static tcflag_t stdout_initial_c_lflag_;
40-
static tcflag_t stderr_initial_c_lflag_;
41-
static tcflag_t stdin_initial_c_lflag_;
42-
43-
static void ClearLFlags() {
44-
stdout_initial_c_lflag_ = kInvalidFlag;
45-
stderr_initial_c_lflag_ = kInvalidFlag;
46-
stdin_initial_c_lflag_ = kInvalidFlag;
47-
}
48-
49-
static void SaveMode(intptr_t fd, tcflag_t* flag) {
50-
ASSERT(flag != NULL);
51-
struct termios term;
52-
int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
53-
if (status != 0) {
54-
return;
55-
}
56-
*flag = term.c_lflag;
57-
}
58-
59-
static void RestoreMode(intptr_t fd, tcflag_t flag) {
60-
if (flag == kInvalidFlag) {
61-
return;
62-
}
63-
struct termios term;
64-
int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
65-
if (status != 0) {
66-
return;
67-
}
68-
term.c_lflag = flag;
69-
VOID_TEMP_FAILURE_RETRY(tcsetattr(fd, TCSANOW, &term));
70-
}
71-
72-
DISALLOW_ALLOCATION();
73-
DISALLOW_IMPLICIT_CONSTRUCTORS(PosixConsole);
74-
};
75-
76-
tcflag_t PosixConsole::stdout_initial_c_lflag_ = PosixConsole::kInvalidFlag;
77-
tcflag_t PosixConsole::stderr_initial_c_lflag_ = PosixConsole::kInvalidFlag;
78-
tcflag_t PosixConsole::stdin_initial_c_lflag_ = PosixConsole::kInvalidFlag;
79-
8021
void Console::SaveConfig() {
81-
PosixConsole::Initialize();
8222
}
8323

8424
void Console::RestoreConfig() {
85-
PosixConsole::Cleanup();
8625
}
8726

8827
} // namespace bin

‎runtime/bin/console_win.cc

+4
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ class ConsoleWin {
105105
/// to reset the state when we cleanup.
106106
if ((h != INVALID_HANDLE_VALUE) && GetConsoleMode(h, &mode)) {
107107
old_mode = mode;
108+
// No reason to restore the mode on exit if it was already desirable.
109+
if ((mode & flags) == flags) {
110+
return kInvalidFlag;
111+
}
108112
if (flags != 0) {
109113
const DWORD request = mode | flags;
110114
SetConsoleMode(h, request);

‎runtime/bin/io_natives.cc

+2
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ namespace bin {
176176
V(Stdin_ReadByte, 1) \
177177
V(Stdin_GetEchoMode, 1) \
178178
V(Stdin_SetEchoMode, 2) \
179+
V(Stdin_GetEchoNewlineMode, 1) \
180+
V(Stdin_SetEchoNewlineMode, 2) \
179181
V(Stdin_GetLineMode, 1) \
180182
V(Stdin_SetLineMode, 2) \
181183
V(Stdin_AnsiSupported, 1) \

‎runtime/bin/stdio.cc

+33
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ void FUNCTION_NAME(Stdin_SetEchoMode)(Dart_NativeArguments args) {
8585
}
8686
}
8787

88+
void FUNCTION_NAME(Stdin_GetEchoNewlineMode)(Dart_NativeArguments args) {
89+
bool enabled = false;
90+
intptr_t fd;
91+
if (!GetIntptrArgument(args, 0, &fd)) {
92+
return;
93+
}
94+
if (Stdin::GetEchoNewlineMode(fd, &enabled)) {
95+
Dart_SetBooleanReturnValue(args, enabled);
96+
} else {
97+
Dart_SetReturnValue(args, DartUtils::NewDartOSError());
98+
}
99+
}
100+
101+
void FUNCTION_NAME(Stdin_SetEchoNewlineMode)(Dart_NativeArguments args) {
102+
intptr_t fd;
103+
if (!GetIntptrArgument(args, 0, &fd)) {
104+
return;
105+
}
106+
bool enabled;
107+
Dart_Handle status = Dart_GetNativeBooleanArgument(args, 1, &enabled);
108+
if (Dart_IsError(status)) {
109+
// The caller is expecting an OSError if something goes wrong.
110+
OSError os_error(-1, "Invalid argument", OSError::kUnknown);
111+
Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
112+
return;
113+
}
114+
if (Stdin::SetEchoNewlineMode(fd, enabled)) {
115+
Dart_SetReturnValue(args, Dart_True());
116+
} else {
117+
Dart_SetReturnValue(args, DartUtils::NewDartOSError());
118+
}
119+
}
120+
88121
void FUNCTION_NAME(Stdin_GetLineMode)(Dart_NativeArguments args) {
89122
bool enabled = false;
90123
intptr_t fd;

‎runtime/bin/stdio.h

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class Stdin {
2020
static bool GetEchoMode(intptr_t fd, bool* enabled);
2121
static bool SetEchoMode(intptr_t fd, bool enabled);
2222

23+
static bool GetEchoNewlineMode(intptr_t fd, bool* enabled);
24+
static bool SetEchoNewlineMode(intptr_t fd, bool enabled);
25+
2326
static bool GetLineMode(intptr_t fd, bool* enabled);
2427
static bool SetLineMode(intptr_t fd, bool enabled);
2528

‎runtime/bin/stdio_android.cc

+27-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,34 @@ bool Stdin::SetEchoMode(intptr_t fd, bool enabled) {
4444
return false;
4545
}
4646
if (enabled) {
47-
term.c_lflag |= (ECHO | ECHONL);
47+
term.c_lflag |= ECHO;
4848
} else {
49-
term.c_lflag &= ~(ECHO | ECHONL);
49+
term.c_lflag &= ~(ECHO);
50+
}
51+
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
52+
return (status == 0);
53+
}
54+
55+
bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
56+
struct termios term;
57+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
58+
if (status != 0) {
59+
return false;
60+
}
61+
*enabled = ((term.c_lflag & ECHONL) != 0);
62+
return true;
63+
}
64+
65+
bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
66+
struct termios term;
67+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
68+
if (status != 0) {
69+
return false;
70+
}
71+
if (enabled) {
72+
term.c_lflag |= ECHONL;
73+
} else {
74+
term.c_lflag &= ~(ECHONL);
5075
}
5176
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
5277
return (status == 0);

‎runtime/bin/stdio_fuchsia.cc

+10
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ bool Stdin::SetEchoMode(intptr_t fd, bool enabled) {
3434
return false;
3535
}
3636

37+
bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
38+
errno = ENOSYS;
39+
return false;
40+
}
41+
42+
bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
43+
errno = ENOSYS;
44+
return false;
45+
}
46+
3747
bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
3848
errno = ENOSYS;
3949
return false;

‎runtime/bin/stdio_linux.cc

+27-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,34 @@ bool Stdin::SetEchoMode(intptr_t fd, bool enabled) {
4444
return false;
4545
}
4646
if (enabled) {
47-
term.c_lflag |= (ECHO | ECHONL);
47+
term.c_lflag |= ECHO;
4848
} else {
49-
term.c_lflag &= ~(ECHO | ECHONL);
49+
term.c_lflag &= ~(ECHO);
50+
}
51+
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
52+
return (status == 0);
53+
}
54+
55+
bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
56+
struct termios term;
57+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
58+
if (status != 0) {
59+
return false;
60+
}
61+
*enabled = ((term.c_lflag & ECHONL) != 0);
62+
return true;
63+
}
64+
65+
bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
66+
struct termios term;
67+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
68+
if (status != 0) {
69+
return false;
70+
}
71+
if (enabled) {
72+
term.c_lflag |= ECHONL;
73+
} else {
74+
term.c_lflag &= ~(ECHONL);
5075
}
5176
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
5277
return (status == 0);

‎runtime/bin/stdio_macos.cc

+27-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,34 @@ bool Stdin::SetEchoMode(intptr_t fd, bool enabled) {
4444
return false;
4545
}
4646
if (enabled) {
47-
term.c_lflag |= (ECHO | ECHONL);
47+
term.c_lflag |= ECHO;
4848
} else {
49-
term.c_lflag &= ~(ECHO | ECHONL);
49+
term.c_lflag &= ~(ECHO);
50+
}
51+
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
52+
return (status == 0);
53+
}
54+
55+
bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
56+
struct termios term;
57+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
58+
if (status != 0) {
59+
return false;
60+
}
61+
*enabled = ((term.c_lflag & ECHONL) != 0);
62+
return true;
63+
}
64+
65+
bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
66+
struct termios term;
67+
int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
68+
if (status != 0) {
69+
return false;
70+
}
71+
if (enabled) {
72+
term.c_lflag |= ECHONL;
73+
} else {
74+
term.c_lflag &= ~(ECHONL);
5075
}
5176
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
5277
return (status == 0);

‎runtime/bin/stdio_win.cc

+13
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ bool Stdin::SetEchoMode(intptr_t fd, bool enabled) {
5656
return SetConsoleMode(h, mode);
5757
}
5858

59+
bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
60+
*enabled = false;
61+
return true;
62+
}
63+
64+
bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
65+
if (enabled) {
66+
SetLastError(ERROR_NOT_CAPABLE);
67+
return false;
68+
}
69+
return true;
70+
}
71+
5972
bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
6073
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
6174
DWORD mode;

‎sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart

+10
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,16 @@ class Stdin {
687687
throw UnsupportedError("Stdin.echoMode");
688688
}
689689

690+
@patch
691+
bool get echoNewlineMode {
692+
throw UnsupportedError("Stdin.echoNewlineMode");
693+
}
694+
695+
@patch
696+
void set echoNewlineMode(bool enabled) {
697+
throw UnsupportedError("Stdin.echoNewlineMode");
698+
}
699+
690700
@patch
691701
bool get lineMode {
692702
throw UnsupportedError("Stdin.lineMode");

‎sdk/lib/_internal/js_runtime/lib/io_patch.dart

+10
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,16 @@ class Stdin {
687687
throw new UnsupportedError("Stdin.echoMode");
688688
}
689689

690+
@patch
691+
bool get echoNewlineMode {
692+
throw UnsupportedError("Stdin.echoNewlineMode");
693+
}
694+
695+
@patch
696+
void set echoNewlineMode(bool enabled) {
697+
throw UnsupportedError("Stdin.echoNewlineMode");
698+
}
699+
690700
@patch
691701
bool get lineMode {
692702
throw new UnsupportedError("Stdin.lineMode");
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.