Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1358e286 authored by Elliott Hughes's avatar Elliott Hughes Committed by Gerrit Code Review
Browse files

Merge "adb shell SIGWINCH support."

parents 834799f0 c15b17f1
Loading
Loading
Loading
Loading
+181 −143
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@

#if !defined(_WIN32)
#include <signal.h>
#include <termio.h>
#include <termios.h>
#include <unistd.h>
#endif
@@ -247,17 +248,17 @@ static int usage() {
#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 @@ static void stdin_raw_init(int fd) {
    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 @@ static void copy_to_file(int inFd, int outFd) {
    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 @@ static void copy_to_file(int inFd, int outFd) {
    }

    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 @@ static void copy_to_file(int inFd, int outFd) {
    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 @@ struct StdinReadArgs {
    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
@@ -494,8 +550,8 @@ static void* stdin_read_thread(void* x) {
                    break;
                case '.':
                    if (state == 2) {
                        stdin_raw_restore(args->stdin_fd);
                        fprintf(stderr,"\n* disconnect *\n");
                        fprintf(stderr,"\r\n* disconnect *\r\n");
                        stdin_raw_restore();
                        exit(0);
                    }
                default:
@@ -574,53 +630,121 @@ static int RemoteShell(bool use_shell_protocol, const std::string& type_arg,
        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;
    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;
        }
    }

    if (adb_query(format_host_command("features", transport_type, serial),
                  &result, &error)) {
        return StringToFeatureSet(result);
    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 FeatureSet();

    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 @@ int adb_commandline(int argc, const char **argv) {
    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");
+13 −2
Original line number Diff line number Diff line
@@ -513,6 +513,18 @@ ScopedFd* Subprocess::PassInput() {

        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 @@ ScopedFd* Subprocess::PassInput() {
                        // 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;
            }
+9 −2
Original line number Diff line number Diff line
@@ -54,8 +54,15 @@ class ShellProtocol {
        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
+33 −36
Original line number Diff line number Diff line
@@ -3342,9 +3342,8 @@ static int _console_read(const HANDLE console, void* buf, size_t len) {
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
@@ -3353,7 +3352,8 @@ void stdin_raw_init(const int fd) {
    // 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))) {
                                                  ENABLE_LINE_INPUT |
                                                  ENABLE_ECHO_INPUT))) {
        // This really should not fail.
        D("stdin_raw_init: SetConsoleMode() failed: %s",
          SystemErrorCodeToString(GetLastError()).c_str());
@@ -3367,10 +3367,8 @@ void stdin_raw_init(const int fd) {
    // 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) {
void stdin_raw_restore() {
    if (_console_handle != NULL) {
        const HANDLE in = _console_handle;
        _console_handle = NULL;  // clear state
@@ -3382,7 +3380,6 @@ void stdin_raw_restore(const int fd) {
        }
    }
}
}

// Called by 'adb shell' and 'adb exec-in' to read from stdin.
int unix_read(int fd, void* buf, size_t len) {