[tint][utils] Fixes for TerminalIsDark()

Only query the background color if stdin isatty, and tcgetattr() / tcsetattr() succeed. Otherwise the terminal will echo the terminal colors.

Poll stdin, to ensure it doesn't block indefinitely.

Fixed: tint:2199
Fixed: tint:2196
Change-Id: Id94374fed3d89993fd5262c4089c17bb01f590d6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/178960
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/utils/system/terminal_posix.cc b/src/tint/utils/system/terminal_posix.cc
index fb2d93f..e820774 100644
--- a/src/tint/utils/system/terminal_posix.cc
+++ b/src/tint/utils/system/terminal_posix.cc
@@ -39,6 +39,7 @@
 #include <utility>
 
 #include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/macros/compiler.h"
 #include "src/tint/utils/macros/defer.h"
 #include "src/tint/utils/system/env.h"
 #include "src/tint/utils/system/terminal.h"
@@ -47,7 +48,8 @@
 namespace {
 
 std::optional<bool> TerminalIsDarkImpl(FILE* out) {
-    if (!TerminalSupportsColors(out)) {
+    // Check the terminal can be queried, and supports colors.
+    if (!isatty(STDIN_FILENO) || !TerminalSupportsColors(out)) {
         return std::nullopt;
     }
 
@@ -59,17 +61,22 @@
 
     // Store the current attributes for 'out', restore it before returning
     termios original_state{};
-    tcgetattr(out_fd, &original_state);
+    if (tcgetattr(out_fd, &original_state) != 0) {
+        return std::nullopt;
+    }
     TINT_DEFER(tcsetattr(out_fd, TCSADRAIN, &original_state));
 
     // Prevent echoing.
     termios state = original_state;
     state.c_lflag &= ~static_cast<tcflag_t>(ECHO | ICANON);
-    tcsetattr(out_fd, TCSADRAIN, &state);
+    if (tcsetattr(out_fd, TCSADRAIN, &state) != 0) {
+        return std::nullopt;
+    }
 
     // Emit the device control escape sequence to query the terminal colors.
     static constexpr std::string_view kQuery = "\033]11;?\033\\";
     fwrite(kQuery.data(), 1, kQuery.length(), out);
+    fflush(out);
 
     // Timeout for attempting to read the response.
     static constexpr auto kTimeout = std::chrono::milliseconds(300);
@@ -77,6 +84,22 @@
     // Record the start time.
     auto start = std::chrono::steady_clock::now();
 
+    // Returns true if there's data available on stdin, or false if no data was available after
+    // 100ms.
+    auto poll_stdin = [] {
+        // Note: These macros introduce identifiers that start with `__`.
+        TINT_BEGIN_DISABLE_WARNING(RESERVED_IDENTIFIER);
+        fd_set rfds{};
+        FD_ZERO(&rfds);
+        FD_SET(STDIN_FILENO, &rfds);
+        timeval tv{};
+        tv.tv_sec = 0;
+        tv.tv_usec = 100'000;
+        int res = select(STDIN_FILENO + 1, &rfds, nullptr, nullptr, &tv);
+        return res > 0 && FD_ISSET(STDIN_FILENO, &rfds);
+        TINT_END_DISABLE_WARNING(RESERVED_IDENTIFIER);
+    };
+
     // Helpers for parsing the response.
     Vector<char, 8> peek;
     auto read = [&]() -> std::optional<char> {
@@ -84,6 +107,10 @@
             return peek.Pop();
         }
         while ((std::chrono::steady_clock::now() - start) < kTimeout) {
+            if (!poll_stdin()) {
+                return std::nullopt;
+            }
+
             char c;
             if (fread(&c, 1, 1, stdin) == 1) {
                 return c;
@@ -163,7 +190,7 @@
             return std::nullopt;
     }
 
-    if (!match("\x07") && !match("\x1b\x5c")) {
+    if (!match("\x07") && !match("\x1b")) {
         return std::nullopt;
     }