Skip to content

Commit c2c9c0c

Browse files
bnoordhuisktaborski
authored andcommitted
src: restore stdio on program exit
Record the state of the stdio file descriptors on start-up and restore them to that state on exit. This should prevent issues where node.js sometimes leaves stdio in raw or non-blocking mode. Co-authored-by: Krzysztof Taborski <[email protected]> PR-URL: #20592 Fixes: #14752 Fixes: #21020 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 15c7a49 commit c2c9c0c

File tree

1 file changed

+94
-6
lines changed

1 file changed

+94
-6
lines changed

src/node.cc

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ typedef int mode_t;
100100
#else
101101
#include <pthread.h>
102102
#include <sys/resource.h> // getrlimit, setrlimit
103+
#include <termios.h> // tcgetattr, tcsetattr
103104
#include <unistd.h> // setuid, getuid
104105
#endif
105106

@@ -173,6 +174,9 @@ using v8::Value;
173174
static Mutex process_mutex;
174175
static Mutex environ_mutex;
175176

177+
// Safe to call more than once and from signal handlers.
178+
inline void PlatformExit();
179+
176180
static bool print_eval = false;
177181
static bool force_repl = false;
178182
static bool syntax_check_only = false;
@@ -1092,7 +1096,7 @@ void AppendExceptionLine(Environment* env,
10921096
Mutex::ScopedLock lock(process_mutex);
10931097
env->set_printed_error(true);
10941098

1095-
uv_tty_reset_mode();
1099+
PlatformExit();
10961100
PrintErrorString("\n%s", arrow);
10971101
return;
10981102
}
@@ -3011,7 +3015,7 @@ void SetupProcessObject(Environment* env,
30113015

30123016

30133017
void SignalExit(int signo) {
3014-
uv_tty_reset_mode();
3018+
PlatformExit();
30153019
v8_platform.StopTracingAgent();
30163020
#ifdef __FreeBSD__
30173021
// FreeBSD has a nasty bug, see RegisterSignalHandler for details
@@ -3828,6 +3832,27 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args) {
38283832
}
38293833

38303834

3835+
#ifdef __POSIX__
3836+
static struct {
3837+
int flags;
3838+
bool isatty;
3839+
struct stat stat;
3840+
struct termios termios;
3841+
} stdio[1 + STDERR_FILENO];
3842+
3843+
3844+
inline int GetFileDescriptorFlags(int fd) {
3845+
int flags;
3846+
3847+
do {
3848+
flags = fcntl(fd, F_GETFL);
3849+
} while (flags == -1 && errno == EINTR);
3850+
3851+
return flags;
3852+
}
3853+
#endif // __POSIX__
3854+
3855+
38313856
inline void PlatformInit() {
38323857
#ifdef __POSIX__
38333858
#if HAVE_INSPECTOR
@@ -3838,16 +3863,18 @@ inline void PlatformInit() {
38383863
#endif // HAVE_INSPECTOR
38393864

38403865
// Make sure file descriptors 0-2 are valid before we start logging anything.
3841-
for (int fd = STDIN_FILENO; fd <= STDERR_FILENO; fd += 1) {
3842-
struct stat ignored;
3843-
if (fstat(fd, &ignored) == 0)
3866+
for (auto& s : stdio) {
3867+
const int fd = &s - stdio;
3868+
if (fstat(fd, &s.stat) == 0)
38443869
continue;
38453870
// Anything but EBADF means something is seriously wrong. We don't
38463871
// have to special-case EINTR, fstat() is not interruptible.
38473872
if (errno != EBADF)
38483873
ABORT();
38493874
if (fd != open("/dev/null", O_RDWR))
38503875
ABORT();
3876+
if (fstat(fd, &s.stat) != 0)
3877+
ABORT();
38513878
}
38523879

38533880
#if HAVE_INSPECTOR
@@ -3870,6 +3897,24 @@ inline void PlatformInit() {
38703897
}
38713898
#endif // !NODE_SHARED_MODE
38723899

3900+
// Record the state of the stdio file descriptors so we can restore it
3901+
// on exit. Needs to happen before installing signal handlers because
3902+
// they make use of that information.
3903+
for (auto& s : stdio) {
3904+
const int fd = &s - stdio;
3905+
int err;
3906+
3907+
s.flags = GetFileDescriptorFlags(fd);
3908+
CHECK_NE(s.flags, -1);
3909+
3910+
if (!isatty(fd)) continue;
3911+
s.isatty = true;
3912+
do {
3913+
err = tcgetattr(fd, &s.termios);
3914+
} while (err == -1 && errno == EINTR);
3915+
CHECK_EQ(err, 0);
3916+
}
3917+
38733918
RegisterSignalHandler(SIGINT, SignalExit, true);
38743919
RegisterSignalHandler(SIGTERM, SignalExit, true);
38753920

@@ -3910,6 +3955,49 @@ inline void PlatformInit() {
39103955
}
39113956

39123957

3958+
// This function must be safe to call more than once and from signal handlers.
3959+
inline void PlatformExit() {
3960+
#ifdef __POSIX__
3961+
for (auto& s : stdio) {
3962+
const int fd = &s - stdio;
3963+
3964+
struct stat tmp;
3965+
if (-1 == fstat(fd, &tmp)) {
3966+
CHECK_EQ(errno, EBADF); // Program closed file descriptor.
3967+
continue;
3968+
}
3969+
3970+
bool is_same_file =
3971+
(s.stat.st_dev == tmp.st_dev && s.stat.st_ino == tmp.st_ino);
3972+
if (!is_same_file) continue; // Program reopened file descriptor.
3973+
3974+
int flags = GetFileDescriptorFlags(fd);
3975+
CHECK_NE(flags, -1);
3976+
3977+
// Restore the O_NONBLOCK flag if it changed.
3978+
if (O_NONBLOCK & (flags ^ s.flags)) {
3979+
flags &= ~O_NONBLOCK;
3980+
flags |= s.flags & O_NONBLOCK;
3981+
3982+
int err;
3983+
do {
3984+
err = fcntl(fd, F_SETFL, flags);
3985+
} while (err == -1 && errno == EINTR);
3986+
CHECK_NE(err, -1);
3987+
}
3988+
3989+
if (s.isatty) {
3990+
int err;
3991+
do {
3992+
err = tcsetattr(fd, TCSANOW, &s.termios);
3993+
} while (err == -1 && errno == EINTR);
3994+
CHECK_NE(err, -1);
3995+
}
3996+
}
3997+
#endif // __POSIX__
3998+
}
3999+
4000+
39134001
void ProcessArgv(int* argc,
39144002
const char** argv,
39154003
int* exec_argc,
@@ -4374,7 +4462,7 @@ inline int Start(uv_loop_t* event_loop,
43744462
}
43754463

43764464
int Start(int argc, char** argv) {
4377-
atexit([] () { uv_tty_reset_mode(); });
4465+
atexit([] () { PlatformExit(); });
43784466
PlatformInit();
43794467
performance::performance_node_start = PERFORMANCE_NOW();
43804468

0 commit comments

Comments
 (0)