adb shell SIGWINCH support.

Bug: http://b/19734542
Change-Id: Ic9404a132cb9c42cb6a378bcd4b3dea9188d0a44
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 8f0a7cc..2aa922d 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -41,6 +41,7 @@
 
 #if !defined(_WIN32)
 #include <signal.h>
+#include <termio.h>
 #include <termios.h>
 #include <unistd.h>
 #endif
@@ -247,17 +248,17 @@
 #if defined(_WIN32)
 
 // Implemented in sysdeps_win32.cpp.
-void stdin_raw_init(int fd);
-void stdin_raw_restore(int fd);
+void stdin_raw_init();
+void stdin_raw_restore();
 
 #else
 static termios g_saved_terminal_state;
 
-static void stdin_raw_init(int fd) {
-    if (tcgetattr(fd, &g_saved_terminal_state)) return;
+static void stdin_raw_init() {
+    if (tcgetattr(STDIN_FILENO, &g_saved_terminal_state)) return;
 
     termios tio;
-    if (tcgetattr(fd, &tio)) return;
+    if (tcgetattr(STDIN_FILENO, &tio)) return;
 
     cfmakeraw(&tio);
 
@@ -265,11 +266,11 @@
     tio.c_cc[VTIME] = 0;
     tio.c_cc[VMIN] = 1;
 
-    tcsetattr(fd, TCSAFLUSH, &tio);
+    tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
 }
 
-static void stdin_raw_restore(int fd) {
-    tcsetattr(fd, TCSAFLUSH, &g_saved_terminal_state);
+static void stdin_raw_restore() {
+    tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_saved_terminal_state);
 }
 #endif
 
@@ -358,7 +359,7 @@
     D("copy_to_file(%d -> %d)", inFd, outFd);
 
     if (inFd == STDIN_FILENO) {
-        stdin_raw_init(STDIN_FILENO);
+        stdin_raw_init();
 #ifdef _WIN32
         old_stdin_mode = _setmode(STDIN_FILENO, _O_BINARY);
         if (old_stdin_mode == -1) {
@@ -400,7 +401,7 @@
     }
 
     if (inFd == STDIN_FILENO) {
-        stdin_raw_restore(STDIN_FILENO);
+        stdin_raw_restore();
 #ifdef _WIN32
         if (_setmode(STDIN_FILENO, old_stdin_mode) == -1) {
             fatal_errno("could not restore stdin mode");
@@ -420,7 +421,44 @@
     free(buf);
 }
 
-namespace {
+static std::string format_host_command(const char* command,
+                                       TransportType type, const char* serial) {
+    if (serial) {
+        return android::base::StringPrintf("host-serial:%s:%s", serial, command);
+    }
+
+    const char* prefix = "host";
+    if (type == kTransportUsb) {
+        prefix = "host-usb";
+    } else if (type == kTransportLocal) {
+        prefix = "host-local";
+    }
+    return android::base::StringPrintf("%s:%s", prefix, command);
+}
+
+// Returns the FeatureSet for the indicated transport.
+static FeatureSet GetFeatureSet(TransportType transport_type, const char* serial) {
+    std::string result, error;
+    if (adb_query(format_host_command("features", transport_type, serial), &result, &error)) {
+        return StringToFeatureSet(result);
+    }
+    return FeatureSet();
+}
+
+static void send_window_size_change(int fd, std::unique_ptr<ShellProtocol>& shell) {
+#if !defined(_WIN32)
+    // Old devices can't handle window size changes.
+    if (shell == nullptr) return;
+
+    winsize ws;
+    if (ioctl(fd, TIOCGWINSZ, &ws) == -1) return;
+
+    // Send the new window size as human-readable ASCII for debugging convenience.
+    size_t l = snprintf(shell->data(), shell->data_capacity(), "%dx%d,%dx%d",
+                        ws.ws_row, ws.ws_col, ws.ws_xpixel, ws.ws_ypixel);
+    shell->Write(ShellProtocol::kIdWindowSizeChange, l + 1);
+#endif
+}
 
 // Used to pass multiple values to the stdin read thread.
 struct StdinReadArgs {
@@ -429,38 +467,56 @@
     std::unique_ptr<ShellProtocol> protocol;
 };
 
-}  // namespace
-
 // Loops to read from stdin and push the data to the given FD.
 // The argument should be a pointer to a StdinReadArgs object. This function
 // will take ownership of the object and delete it when finished.
-static void* stdin_read_thread(void* x) {
+static void* stdin_read_thread_loop(void* x) {
     std::unique_ptr<StdinReadArgs> args(reinterpret_cast<StdinReadArgs*>(x));
     int state = 0;
 
-    adb_thread_setname("stdin reader");
-
-#ifndef _WIN32
-    // Mask SIGTTIN in case we're in a backgrounded process
+#if !defined(_WIN32)
+    // Mask SIGTTIN in case we're in a backgrounded process.
     sigset_t sigset;
     sigemptyset(&sigset);
     sigaddset(&sigset, SIGTTIN);
     pthread_sigmask(SIG_BLOCK, &sigset, nullptr);
 #endif
 
+#if !defined(_WIN32)
+    // Unblock SIGWINCH for this thread, so our read(2) below will be
+    // interrupted if the window size changes.
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGWINCH);
+    pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
+#endif
+
+    // Set up the initial window size.
+    send_window_size_change(args->stdin_fd, args->protocol);
+
     char raw_buffer[1024];
     char* buffer_ptr = raw_buffer;
     size_t buffer_size = sizeof(raw_buffer);
-    if (args->protocol) {
+    if (args->protocol != nullptr) {
         buffer_ptr = args->protocol->data();
         buffer_size = args->protocol->data_capacity();
     }
 
     while (true) {
         // Use unix_read() rather than adb_read() for stdin.
-        D("stdin_read_thread(): pre unix_read(fdi=%d,...)", args->stdin_fd);
+        D("stdin_read_thread_loop(): pre unix_read(fdi=%d,...)", args->stdin_fd);
+#if !defined(_WIN32)
+#undef read
+        int r = read(args->stdin_fd, buffer_ptr, buffer_size);
+        if (r == -1 && errno == EINTR) {
+            send_window_size_change(args->stdin_fd, args->protocol);
+            continue;
+        }
+#define read ___xxx_read
+#else
         int r = unix_read(args->stdin_fd, buffer_ptr, buffer_size);
-        D("stdin_read_thread(): post unix_read(fdi=%d,...)", args->stdin_fd);
+#endif
+        D("stdin_read_thread_loop(): post unix_read(fdi=%d,...)", args->stdin_fd);
         if (r <= 0) {
             // Only devices using the shell protocol know to close subprocess
             // stdin. For older devices we want to just leave the connection
@@ -477,8 +533,8 @@
         // process starts ignoring the signal. SSH also does this, see the
         // "escape characters" section on the ssh man page for more info.
         if (args->raw_stdin) {
-            for (int n = 0; n < r; n++){
-                switch(buffer_ptr[n]) {
+            for (int n = 0; n < r; n++) {
+                switch (buffer_ptr[n]) {
                 case '\n':
                     state = 1;
                     break;
@@ -486,16 +542,16 @@
                     state = 1;
                     break;
                 case '~':
-                    if(state == 1) {
+                    if (state == 1) {
                         state++;
                     } else {
                         state = 0;
                     }
                     break;
                 case '.':
-                    if(state == 2) {
-                        stdin_raw_restore(args->stdin_fd);
-                        fprintf(stderr,"\n* disconnect *\n");
+                    if (state == 2) {
+                        fprintf(stderr,"\r\n* disconnect *\r\n");
+                        stdin_raw_restore();
                         exit(0);
                     }
                 default:
@@ -574,53 +630,121 @@
         args->protocol.reset(new ShellProtocol(args->write_fd));
     }
 
-    if (raw_stdin) {
-        stdin_raw_init(STDIN_FILENO);
-    }
+    if (raw_stdin) stdin_raw_init();
 
-    int exit_code = 0;
-    if (!adb_thread_create(stdin_read_thread, args)) {
+#if !defined(_WIN32)
+    // Ensure our process is notified if the local window size changes.
+    // We use sigaction(2) to ensure that the SA_RESTART flag is not set,
+    // because the whole reason we're sending signals is to unblock the read(2)!
+    // That also means we don't need to do anything in the signal handler:
+    // the side effect of delivering the signal is all we need.
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = [](int) {};
+    sa.sa_flags = 0;
+    sigaction(SIGWINCH, &sa, nullptr);
+
+    // Now block SIGWINCH in this thread (the main thread) and all threads spawned
+    // from it. The stdin read thread will unblock this signal to ensure that it's
+    // the thread that receives the signal.
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGWINCH);
+    pthread_sigmask(SIG_BLOCK, &mask, nullptr);
+#endif
+
+    // TODO: combine read_and_dump with stdin_read_thread to make life simpler?
+    int exit_code = 1;
+    if (!adb_thread_create(stdin_read_thread_loop, args)) {
         PLOG(ERROR) << "error starting stdin read thread";
-        exit_code = 1;
         delete args;
     } else {
         exit_code = read_and_dump(fd, use_shell_protocol);
     }
 
-    if (raw_stdin) {
-        stdin_raw_restore(STDIN_FILENO);
-    }
+    // TODO: properly exit stdin_read_thread_loop and close |fd|.
 
-    // TODO(dpursell): properly exit stdin_read_thread and close |fd|.
+    // TODO: we should probably install signal handlers for this.
+    // TODO: can we use atexit? even on Windows?
+    if (raw_stdin) stdin_raw_restore();
 
     return exit_code;
 }
 
+static int adb_shell(int argc, const char** argv,
+                     TransportType transport_type, const char* serial) {
+    FeatureSet features = GetFeatureSet(transport_type, serial);
 
-static std::string format_host_command(const char* command, TransportType type, const char* serial) {
-    if (serial) {
-        return android::base::StringPrintf("host-serial:%s:%s", serial, command);
+    bool use_shell_protocol = CanUseFeature(features, kFeatureShell2);
+    if (!use_shell_protocol) {
+        D("shell protocol not supported, using raw data transfer");
+    } else {
+        D("using shell protocol");
     }
 
-    const char* prefix = "host";
-    if (type == kTransportUsb) {
-        prefix = "host-usb";
-    } else if (type == kTransportLocal) {
-        prefix = "host-local";
+    // Parse shell-specific command-line options.
+    // argv[0] is always "shell".
+    --argc;
+    ++argv;
+    int t_arg_count = 0;
+    while (argc) {
+        if (!strcmp(argv[0], "-T") || !strcmp(argv[0], "-t")) {
+            if (!CanUseFeature(features, kFeatureShell2)) {
+                fprintf(stderr, "error: target doesn't support PTY args -Tt\n");
+                return 1;
+            }
+            // Like ssh, -t arguments are cumulative so that multiple -t's
+            // are needed to force a PTY.
+            if (argv[0][1] == 't') {
+                ++t_arg_count;
+            } else {
+                t_arg_count = -1;
+            }
+            --argc;
+            ++argv;
+        } else if (!strcmp(argv[0], "-x")) {
+            use_shell_protocol = false;
+            --argc;
+            ++argv;
+        } else {
+            break;
+        }
     }
-    return android::base::StringPrintf("%s:%s", prefix, command);
-}
 
-// Returns the FeatureSet for the indicated transport.
-static FeatureSet GetFeatureSet(TransportType transport_type,
-                                const char* serial) {
-    std::string result, error;
-
-    if (adb_query(format_host_command("features", transport_type, serial),
-                  &result, &error)) {
-        return StringToFeatureSet(result);
+    std::string shell_type_arg;
+    if (CanUseFeature(features, kFeatureShell2)) {
+        if (t_arg_count < 0) {
+            shell_type_arg = kShellServiceArgRaw;
+        } else if (t_arg_count == 0) {
+            // If stdin isn't a TTY, default to a raw shell; this lets
+            // things like `adb shell < my_script.sh` work as expected.
+            // Otherwise leave |shell_type_arg| blank which uses PTY for
+            // interactive shells and raw for non-interactive.
+            if (!unix_isatty(STDIN_FILENO)) {
+                shell_type_arg = kShellServiceArgRaw;
+            }
+        } else if (t_arg_count == 1) {
+            // A single -t arg isn't enough to override implicit -T.
+            if (!unix_isatty(STDIN_FILENO)) {
+                fprintf(stderr,
+                        "Remote PTY will not be allocated because stdin is not a terminal.\n"
+                        "Use multiple -t options to force remote PTY allocation.\n");
+                shell_type_arg = kShellServiceArgRaw;
+            } else {
+                shell_type_arg = kShellServiceArgPty;
+            }
+        } else {
+            shell_type_arg = kShellServiceArgPty;
+        }
     }
-    return FeatureSet();
+
+    std::string command;
+    if (argc) {
+        // We don't escape here, just like ssh(1). http://b/20564385.
+        command = android::base::Join(std::vector<const char*>(argv, argv + argc), ' ');
+    }
+
+    return RemoteShell(use_shell_protocol, shell_type_arg, command);
 }
 
 static int adb_download_buffer(const char *service, const char *fn, const void* data, unsigned sz,
@@ -1343,94 +1467,8 @@
     else if (!strcmp(argv[0], "emu")) {
         return adb_send_emulator_command(argc, argv, serial);
     }
-    else if (!strcmp(argv[0], "shell") || !strcmp(argv[0], "hell")) {
-        char h = (argv[0][0] == 'h');
-
-        FeatureSet features = GetFeatureSet(transport_type, serial);
-
-        bool use_shell_protocol = CanUseFeature(features, kFeatureShell2);
-        if (!use_shell_protocol) {
-            D("shell protocol not supported, using raw data transfer");
-        } else {
-            D("using shell protocol");
-        }
-
-        // Parse shell-specific command-line options.
-        // argv[0] is always "shell".
-        --argc;
-        ++argv;
-        int t_arg_count = 0;
-        while (argc) {
-            if (!strcmp(argv[0], "-T") || !strcmp(argv[0], "-t")) {
-                if (!CanUseFeature(features, kFeatureShell2)) {
-                    fprintf(stderr, "error: target doesn't support PTY args -Tt\n");
-                    return 1;
-                }
-                // Like ssh, -t arguments are cumulative so that multiple -t's
-                // are needed to force a PTY.
-                if (argv[0][1] == 't') {
-                    ++t_arg_count;
-                } else {
-                    t_arg_count = -1;
-                }
-                --argc;
-                ++argv;
-            } else if (!strcmp(argv[0], "-x")) {
-                use_shell_protocol = false;
-                --argc;
-                ++argv;
-            } else {
-                break;
-            }
-        }
-
-        std::string shell_type_arg;
-        if (CanUseFeature(features, kFeatureShell2)) {
-            if (t_arg_count < 0) {
-                shell_type_arg = kShellServiceArgRaw;
-            } else if (t_arg_count == 0) {
-                // If stdin isn't a TTY, default to a raw shell; this lets
-                // things like `adb shell < my_script.sh` work as expected.
-                // Otherwise leave |shell_type_arg| blank which uses PTY for
-                // interactive shells and raw for non-interactive.
-                if (!unix_isatty(STDIN_FILENO)) {
-                    shell_type_arg = kShellServiceArgRaw;
-                }
-            } else if (t_arg_count == 1) {
-                // A single -t arg isn't enough to override implicit -T.
-                if (!unix_isatty(STDIN_FILENO)) {
-                    fprintf(stderr,
-                            "Remote PTY will not be allocated because stdin is not a terminal.\n"
-                            "Use multiple -t options to force remote PTY allocation.\n");
-                    shell_type_arg = kShellServiceArgRaw;
-                } else {
-                    shell_type_arg = kShellServiceArgPty;
-                }
-            } else {
-                shell_type_arg = kShellServiceArgPty;
-            }
-        }
-
-        std::string command;
-        if (argc) {
-            // We don't escape here, just like ssh(1). http://b/20564385.
-            command = android::base::Join(
-                    std::vector<const char*>(argv, argv + argc), ' ');
-        }
-
-        if (h) {
-            printf("\x1b[41;33m");
-            fflush(stdout);
-        }
-
-        r = RemoteShell(use_shell_protocol, shell_type_arg, command);
-
-        if (h) {
-            printf("\x1b[0m");
-            fflush(stdout);
-        }
-
-        return r;
+    else if (!strcmp(argv[0], "shell")) {
+        return adb_shell(argc, argv, transport_type, serial);
     }
     else if (!strcmp(argv[0], "exec-in") || !strcmp(argv[0], "exec-out")) {
         int exec_in = !strcmp(argv[0], "exec-in");
diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp
index be5921d..e3fde26 100644
--- a/adb/shell_service.cpp
+++ b/adb/shell_service.cpp
@@ -513,6 +513,18 @@
 
         if (stdinout_sfd_.valid()) {
             switch (input_->id()) {
+                case ShellProtocol::kIdWindowSizeChange:
+                    int rows, cols, x_pixels, y_pixels;
+                    if (sscanf(input_->data(), "%dx%d,%dx%d",
+                               &rows, &cols, &x_pixels, &y_pixels) == 4) {
+                        winsize ws;
+                        ws.ws_row = rows;
+                        ws.ws_col = cols;
+                        ws.ws_xpixel = x_pixels;
+                        ws.ws_ypixel = y_pixels;
+                        ioctl(stdinout_sfd_.fd(), TIOCSWINSZ, &ws);
+                    }
+                    break;
                 case ShellProtocol::kIdStdin:
                     input_bytes_left_ = input_->data_length();
                     break;
@@ -531,8 +543,7 @@
                         // non-interactively which is rare and unsupported.
                         // If necessary, the client can manually close the shell
                         // with `exit` or by killing the adb client process.
-                        D("can't close input for PTY FD %d",
-                          stdinout_sfd_.fd());
+                        D("can't close input for PTY FD %d", stdinout_sfd_.fd());
                     }
                     break;
             }
diff --git a/adb/shell_service.h b/adb/shell_service.h
index 01410a9..63c00da 100644
--- a/adb/shell_service.h
+++ b/adb/shell_service.h
@@ -54,8 +54,15 @@
         kIdStdout = 1,
         kIdStderr = 2,
         kIdExit = 3,
-        kIdCloseStdin = 4,  // Close subprocess stdin if possible.
-        kIdInvalid = 255,  // Indicates an invalid or unknown packet.
+
+        // Close subprocess stdin if possible.
+        kIdCloseStdin = 4,
+
+        // Window size change (an ASCII version of struct winsize).
+        kIdWindowSizeChange = 5,
+
+        // Indicates an invalid or unknown packet.
+        kIdInvalid = 255,
     };
 
     // ShellPackets will probably be too large to allocate on the stack so they
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index d2e6cdb..e325889 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -3342,44 +3342,41 @@
 static DWORD _old_console_mode; // previous GetConsoleMode() result
 static HANDLE _console_handle;  // when set, console mode should be restored
 
-void stdin_raw_init(const int fd) {
-    if (STDIN_FILENO == fd) {
-        const HANDLE in = _get_console_handle(fd, &_old_console_mode);
+void stdin_raw_init() {
+    const HANDLE in = _get_console_handle(STDIN_FILENO, &_old_console_mode);
 
-        // Disable ENABLE_PROCESSED_INPUT so that Ctrl-C is read instead of
-        // calling the process Ctrl-C routine (configured by
-        // SetConsoleCtrlHandler()).
-        // Disable ENABLE_LINE_INPUT so that input is immediately sent.
-        // Disable ENABLE_ECHO_INPUT to disable local echo. Disabling this
-        // flag also seems necessary to have proper line-ending processing.
-        if (!SetConsoleMode(in, _old_console_mode & ~(ENABLE_PROCESSED_INPUT |
-            ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))) {
-            // This really should not fail.
-            D("stdin_raw_init: SetConsoleMode() failed: %s",
-              SystemErrorCodeToString(GetLastError()).c_str());
-        }
-
-        // Once this is set, it means that stdin has been configured for
-        // reading from and that the old console mode should be restored later.
-        _console_handle = in;
-
-        // Note that we don't need to configure C Runtime line-ending
-        // translation because _console_read() does not call the C Runtime to
-        // read from the console.
+    // Disable ENABLE_PROCESSED_INPUT so that Ctrl-C is read instead of
+    // calling the process Ctrl-C routine (configured by
+    // SetConsoleCtrlHandler()).
+    // Disable ENABLE_LINE_INPUT so that input is immediately sent.
+    // Disable ENABLE_ECHO_INPUT to disable local echo. Disabling this
+    // flag also seems necessary to have proper line-ending processing.
+    if (!SetConsoleMode(in, _old_console_mode & ~(ENABLE_PROCESSED_INPUT |
+                                                  ENABLE_LINE_INPUT |
+                                                  ENABLE_ECHO_INPUT))) {
+        // This really should not fail.
+        D("stdin_raw_init: SetConsoleMode() failed: %s",
+          SystemErrorCodeToString(GetLastError()).c_str());
     }
+
+    // Once this is set, it means that stdin has been configured for
+    // reading from and that the old console mode should be restored later.
+    _console_handle = in;
+
+    // Note that we don't need to configure C Runtime line-ending
+    // translation because _console_read() does not call the C Runtime to
+    // read from the console.
 }
 
-void stdin_raw_restore(const int fd) {
-    if (STDIN_FILENO == fd) {
-        if (_console_handle != NULL) {
-            const HANDLE in = _console_handle;
-            _console_handle = NULL;  // clear state
+void stdin_raw_restore() {
+    if (_console_handle != NULL) {
+        const HANDLE in = _console_handle;
+        _console_handle = NULL;  // clear state
 
-            if (!SetConsoleMode(in, _old_console_mode)) {
-                // This really should not fail.
-                D("stdin_raw_restore: SetConsoleMode() failed: %s",
-                  SystemErrorCodeToString(GetLastError()).c_str());
-            }
+        if (!SetConsoleMode(in, _old_console_mode)) {
+            // This really should not fail.
+            D("stdin_raw_restore: SetConsoleMode() failed: %s",
+              SystemErrorCodeToString(GetLastError()).c_str());
         }
     }
 }