From af06581b841e7655374b80563556af66e8eb9be7 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 12 Dec 2017 21:59:57 +0100 Subject: [PATCH] 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 PR-URL: https://github.com/nodejs/node/pull/20592 Fixes: https://github.com/nodejs/node/issues/14752 Fixes: https://github.com/nodejs/node/issues/21020 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/node.cc | 100 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/src/node.cc b/src/node.cc index e5d43a965882b5..d3cf8b3d233c4e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -100,6 +100,7 @@ typedef int mode_t; #else #include #include // getrlimit, setrlimit +#include // tcgetattr, tcsetattr #include // setuid, getuid #endif @@ -172,6 +173,9 @@ using v8::Value; static Mutex process_mutex; static Mutex environ_mutex; +// Safe to call more than once and from signal handlers. +inline void PlatformExit(); + static bool print_eval = false; static bool force_repl = false; static bool syntax_check_only = false; @@ -1091,7 +1095,7 @@ void AppendExceptionLine(Environment* env, Mutex::ScopedLock lock(process_mutex); env->set_printed_error(true); - uv_tty_reset_mode(); + PlatformExit(); PrintErrorString("\n%s", arrow); return; } @@ -3025,7 +3029,7 @@ void SetupProcessObject(Environment* env, void SignalExit(int signo) { - uv_tty_reset_mode(); + PlatformExit(); v8_platform.StopTracingAgent(); #ifdef __FreeBSD__ // FreeBSD has a nasty bug, see RegisterSignalHandler for details @@ -3846,6 +3850,27 @@ static void DebugEnd(const FunctionCallbackInfo& args) { } +#ifdef __POSIX__ +static struct { + int flags; + bool isatty; + struct stat stat; + struct termios termios; +} stdio[1 + STDERR_FILENO]; + + +inline int GetFileDescriptorFlags(int fd) { + int flags; + + do { + flags = fcntl(fd, F_GETFL); + } while (flags == -1 && errno == EINTR); + + return flags; +} +#endif // __POSIX__ + + inline void PlatformInit() { #ifdef __POSIX__ #if HAVE_INSPECTOR @@ -3856,9 +3881,9 @@ inline void PlatformInit() { #endif // HAVE_INSPECTOR // Make sure file descriptors 0-2 are valid before we start logging anything. - for (int fd = STDIN_FILENO; fd <= STDERR_FILENO; fd += 1) { - struct stat ignored; - if (fstat(fd, &ignored) == 0) + for (auto& s : stdio) { + const int fd = &s - stdio; + if (fstat(fd, &s.stat) == 0) continue; // Anything but EBADF means something is seriously wrong. We don't // have to special-case EINTR, fstat() is not interruptible. @@ -3866,6 +3891,8 @@ inline void PlatformInit() { ABORT(); if (fd != open("/dev/null", O_RDWR)) ABORT(); + if (fstat(fd, &s.stat) != 0) + ABORT(); } #if HAVE_INSPECTOR @@ -3888,6 +3915,24 @@ inline void PlatformInit() { } #endif // !NODE_SHARED_MODE + // Record the state of the stdio file descriptors so we can restore it + // on exit. Needs to happen before installing signal handlers because + // they make use of that information. + for (auto& s : stdio) { + const int fd = &s - stdio; + int err; + + s.flags = GetFileDescriptorFlags(fd); + CHECK_NE(s.flags, -1); + + if (!isatty(fd)) continue; + s.isatty = true; + do { + err = tcgetattr(fd, &s.termios); + } while (err == -1 && errno == EINTR); + CHECK_EQ(err, 0); + } + RegisterSignalHandler(SIGINT, SignalExit, true); RegisterSignalHandler(SIGTERM, SignalExit, true); @@ -3928,6 +3973,49 @@ inline void PlatformInit() { } +// This function must be safe to call more than once and from signal handlers. +inline void PlatformExit() { +#ifdef __POSIX__ + for (auto& s : stdio) { + const int fd = &s - stdio; + + struct stat tmp; + if (-1 == fstat(fd, &tmp)) { + CHECK_EQ(errno, EBADF); // Program closed file descriptor. + continue; + } + + bool is_same_file = + (s.stat.st_dev == tmp.st_dev && s.stat.st_ino == tmp.st_ino); + if (!is_same_file) continue; // Program reopened file descriptor. + + int flags = GetFileDescriptorFlags(fd); + CHECK_NE(flags, -1); + + // Restore the O_NONBLOCK flag if it changed. + if (O_NONBLOCK & (flags ^ s.flags)) { + flags &= ~O_NONBLOCK; + flags |= s.flags & O_NONBLOCK; + + int err; + do { + err = fcntl(fd, F_SETFL, flags); + } while (err == -1 && errno == EINTR); + CHECK_NE(err, -1); + } + + if (s.isatty) { + int err; + do { + err = tcsetattr(fd, TCSANOW, &s.termios); + } while (err == -1 && errno == EINTR); + CHECK_NE(err, -1); + } + } +#endif // __POSIX__ +} + + void ProcessArgv(int* argc, const char** argv, int* exec_argc, @@ -4392,7 +4480,7 @@ inline int Start(uv_loop_t* event_loop, } int Start(int argc, char** argv) { - atexit([] () { uv_tty_reset_mode(); }); + atexit([] () { PlatformExit(); }); PlatformInit(); performance::performance_node_start = PERFORMANCE_NOW();