adb shell SIGWINCH support.

Bug: http://b/19734542
Change-Id: Ic9404a132cb9c42cb6a378bcd4b3dea9188d0a44
This commit is contained in:
Elliott Hughes 2015-11-03 11:18:40 -08:00
parent 19d9454ee3
commit c15b17f1ac
4 changed files with 234 additions and 181 deletions

View File

@ -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
@ -477,8 +533,8 @@ static void* stdin_read_thread(void* x) {
// 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 @@ static void* stdin_read_thread(void* x) {
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 @@ 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;
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 @@ 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");

View File

@ -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;
}

View File

@ -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

View File

@ -3342,44 +3342,41 @@ 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
// 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());
}
}
}