Merge "adb: non-interactive shell stdin."
This commit is contained in:
commit
ca0d66d597
6 changed files with 163 additions and 91 deletions
|
|
@ -425,6 +425,7 @@ namespace {
|
||||||
// Used to pass multiple values to the stdin read thread.
|
// Used to pass multiple values to the stdin read thread.
|
||||||
struct StdinReadArgs {
|
struct StdinReadArgs {
|
||||||
int stdin_fd, write_fd;
|
int stdin_fd, write_fd;
|
||||||
|
bool raw_stdin;
|
||||||
std::unique_ptr<ShellProtocol> protocol;
|
std::unique_ptr<ShellProtocol> protocol;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -452,26 +453,42 @@ static void* stdin_read_thread(void* x) {
|
||||||
D("stdin_read_thread(): pre unix_read(fdi=%d,...)", args->stdin_fd);
|
D("stdin_read_thread(): pre unix_read(fdi=%d,...)", args->stdin_fd);
|
||||||
int r = unix_read(args->stdin_fd, buffer_ptr, buffer_size);
|
int r = unix_read(args->stdin_fd, buffer_ptr, buffer_size);
|
||||||
D("stdin_read_thread(): post unix_read(fdi=%d,...)", args->stdin_fd);
|
D("stdin_read_thread(): post unix_read(fdi=%d,...)", args->stdin_fd);
|
||||||
if (r <= 0) break;
|
if (r <= 0) {
|
||||||
for (int n = 0; n < r; n++){
|
// Only devices using the shell protocol know to close subprocess
|
||||||
switch(buffer_ptr[n]) {
|
// stdin. For older devices we want to just leave the connection
|
||||||
case '\n':
|
// open, otherwise an unpredictable amount of return data could
|
||||||
state = 1;
|
// be lost due to the FD closing before all data has been received.
|
||||||
break;
|
if (args->protocol) {
|
||||||
case '\r':
|
args->protocol->Write(ShellProtocol::kIdCloseStdin, 0);
|
||||||
state = 1;
|
}
|
||||||
break;
|
break;
|
||||||
case '~':
|
}
|
||||||
if(state == 1) state++;
|
// If we made stdin raw, check input for the "~." escape sequence. In
|
||||||
break;
|
// this situation signals like Ctrl+C are sent remotely rather than
|
||||||
case '.':
|
// interpreted locally so this provides an emergency out if the remote
|
||||||
if(state == 2) {
|
// process starts ignoring the signal. SSH also does this, see the
|
||||||
fprintf(stderr,"\n* disconnect *\n");
|
// "escape characters" section on the ssh man page for more info.
|
||||||
stdin_raw_restore(args->stdin_fd);
|
if (args->raw_stdin) {
|
||||||
exit(0);
|
for (int n = 0; n < r; n++){
|
||||||
|
switch(buffer_ptr[n]) {
|
||||||
|
case '\n':
|
||||||
|
state = 1;
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
state = 1;
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
if(state == 1) state++;
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
if(state == 2) {
|
||||||
|
stdin_raw_restore(args->stdin_fd);
|
||||||
|
fprintf(stderr,"\n* disconnect *\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
state = 0;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
state = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (args->protocol) {
|
if (args->protocol) {
|
||||||
|
|
@ -488,8 +505,44 @@ static void* stdin_read_thread(void* x) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int interactive_shell(const std::string& service_string,
|
// Returns a shell service string with the indicated arguments and command.
|
||||||
bool use_shell_protocol) {
|
static std::string ShellServiceString(bool use_shell_protocol,
|
||||||
|
const std::string& type_arg,
|
||||||
|
const std::string& command) {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (use_shell_protocol) {
|
||||||
|
args.push_back(kShellServiceArgShellProtocol);
|
||||||
|
}
|
||||||
|
if (!type_arg.empty()) {
|
||||||
|
args.push_back(type_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell service string can look like: shell[,arg1,arg2,...]:[command].
|
||||||
|
return android::base::StringPrintf("shell%s%s:%s",
|
||||||
|
args.empty() ? "" : ",",
|
||||||
|
android::base::Join(args, ',').c_str(),
|
||||||
|
command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connects to a shell on the device and read/writes data.
|
||||||
|
//
|
||||||
|
// Note: currently this function doesn't properly clean up resources; the
|
||||||
|
// FD connected to the adb server is never closed and the stdin read thread
|
||||||
|
// may never exit.
|
||||||
|
//
|
||||||
|
// On success returns the remote exit code if |use_shell_protocol| is true,
|
||||||
|
// 0 otherwise. On failure returns 1.
|
||||||
|
static int RemoteShell(bool use_shell_protocol, const std::string& type_arg,
|
||||||
|
const std::string& command) {
|
||||||
|
std::string service_string = ShellServiceString(use_shell_protocol,
|
||||||
|
type_arg, command);
|
||||||
|
|
||||||
|
// Make local stdin raw if the device allocates a PTY, which happens if:
|
||||||
|
// 1. We are explicitly asking for a PTY shell, or
|
||||||
|
// 2. We don't specify shell type and are starting an interactive session.
|
||||||
|
bool raw_stdin = (type_arg == kShellServiceArgPty ||
|
||||||
|
(type_arg.empty() && command.empty()));
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
int fd = adb_connect(service_string, &error);
|
int fd = adb_connect(service_string, &error);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
|
|
@ -502,13 +555,16 @@ static int interactive_shell(const std::string& service_string,
|
||||||
LOG(ERROR) << "couldn't allocate StdinReadArgs object";
|
LOG(ERROR) << "couldn't allocate StdinReadArgs object";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
args->stdin_fd = 0;
|
args->stdin_fd = STDIN_FILENO;
|
||||||
args->write_fd = fd;
|
args->write_fd = fd;
|
||||||
|
args->raw_stdin = raw_stdin;
|
||||||
if (use_shell_protocol) {
|
if (use_shell_protocol) {
|
||||||
args->protocol.reset(new ShellProtocol(args->write_fd));
|
args->protocol.reset(new ShellProtocol(args->write_fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin_raw_init(args->stdin_fd);
|
if (raw_stdin) {
|
||||||
|
stdin_raw_init(STDIN_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
int exit_code = 0;
|
int exit_code = 0;
|
||||||
if (!adb_thread_create(stdin_read_thread, args)) {
|
if (!adb_thread_create(stdin_read_thread, args)) {
|
||||||
|
|
@ -519,7 +575,12 @@ static int interactive_shell(const std::string& service_string,
|
||||||
exit_code = read_and_dump(fd, use_shell_protocol);
|
exit_code = read_and_dump(fd, use_shell_protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin_raw_restore(args->stdin_fd);
|
if (raw_stdin) {
|
||||||
|
stdin_raw_restore(STDIN_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dpursell): properly exit stdin_read_thread and close |fd|.
|
||||||
|
|
||||||
return exit_code;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -795,25 +856,6 @@ static bool wait_for_device(const char* service, TransportType t, const char* se
|
||||||
return adb_command(cmd);
|
return adb_command(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a shell service string with the indicated arguments and command.
|
|
||||||
static std::string ShellServiceString(bool use_shell_protocol,
|
|
||||||
const std::string& type_arg,
|
|
||||||
const std::string& command) {
|
|
||||||
std::vector<std::string> args;
|
|
||||||
if (use_shell_protocol) {
|
|
||||||
args.push_back(kShellServiceArgShellProtocol);
|
|
||||||
}
|
|
||||||
if (!type_arg.empty()) {
|
|
||||||
args.push_back(type_arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell service string can look like: shell[,arg1,arg2,...]:[command].
|
|
||||||
return android::base::StringPrintf("shell%s%s:%s",
|
|
||||||
args.empty() ? "" : ",",
|
|
||||||
android::base::Join(args, ',').c_str(),
|
|
||||||
command.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connects to the device "shell" service with |command| and prints the
|
// Connects to the device "shell" service with |command| and prints the
|
||||||
// resulting output.
|
// resulting output.
|
||||||
static int send_shell_command(TransportType transport_type, const char* serial,
|
static int send_shell_command(TransportType transport_type, const char* serial,
|
||||||
|
|
@ -1320,51 +1362,26 @@ int adb_commandline(int argc, const char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (h) {
|
||||||
printf("\x1b[41;33m");
|
printf("\x1b[41;33m");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argc) {
|
r = RemoteShell(use_shell_protocol, shell_type_arg, command);
|
||||||
D("starting interactive shell");
|
|
||||||
std::string service_string =
|
if (h) {
|
||||||
ShellServiceString(use_shell_protocol, shell_type_arg, "");
|
printf("\x1b[0m");
|
||||||
r = interactive_shell(service_string, use_shell_protocol);
|
fflush(stdout);
|
||||||
if (h) {
|
|
||||||
printf("\x1b[0m");
|
|
||||||
fflush(stdout);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't escape here, just like ssh(1). http://b/20564385.
|
return r;
|
||||||
std::string command = android::base::Join(
|
|
||||||
std::vector<const char*>(argv, argv + argc), ' ');
|
|
||||||
std::string service_string =
|
|
||||||
ShellServiceString(use_shell_protocol, shell_type_arg, command);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
D("non-interactive shell loop. cmd=%s", service_string.c_str());
|
|
||||||
std::string error;
|
|
||||||
int fd = adb_connect(service_string, &error);
|
|
||||||
int r;
|
|
||||||
if (fd >= 0) {
|
|
||||||
D("about to read_and_dump(fd=%d)", fd);
|
|
||||||
r = read_and_dump(fd, use_shell_protocol);
|
|
||||||
D("read_and_dump() done.");
|
|
||||||
adb_close(fd);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr,"error: %s\n", error.c_str());
|
|
||||||
r = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h) {
|
|
||||||
printf("\x1b[0m");
|
|
||||||
fflush(stdout);
|
|
||||||
}
|
|
||||||
D("non-interactive shell loop. return r=%d", r);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!strcmp(argv[0], "exec-in") || !strcmp(argv[0], "exec-out")) {
|
else if (!strcmp(argv[0], "exec-in") || !strcmp(argv[0], "exec-out")) {
|
||||||
int exec_in = !strcmp(argv[0], "exec-in");
|
int exec_in = !strcmp(argv[0], "exec-in");
|
||||||
|
|
|
||||||
|
|
@ -382,7 +382,7 @@ void* Subprocess::ThreadHandler(void* userdata) {
|
||||||
subprocess->PassDataStreams();
|
subprocess->PassDataStreams();
|
||||||
subprocess->WaitForExit();
|
subprocess->WaitForExit();
|
||||||
|
|
||||||
D("deleting Subprocess");
|
D("deleting Subprocess for PID %d", subprocess->pid());
|
||||||
delete subprocess;
|
delete subprocess;
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -501,11 +501,31 @@ ScopedFd* Subprocess::PassInput() {
|
||||||
return &protocol_sfd_;
|
return &protocol_sfd_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only care about stdin packets.
|
if (stdinout_sfd_.valid()) {
|
||||||
if (stdinout_sfd_.valid() && input_->id() == ShellProtocol::kIdStdin) {
|
switch (input_->id()) {
|
||||||
input_bytes_left_ = input_->data_length();
|
case ShellProtocol::kIdStdin:
|
||||||
} else {
|
input_bytes_left_ = input_->data_length();
|
||||||
input_bytes_left_ = 0;
|
break;
|
||||||
|
case ShellProtocol::kIdCloseStdin:
|
||||||
|
if (type_ == SubprocessType::kRaw) {
|
||||||
|
if (adb_shutdown(stdinout_sfd_.fd(), SHUT_WR) == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PLOG(ERROR) << "failed to shutdown writes to FD "
|
||||||
|
<< stdinout_sfd_.fd();
|
||||||
|
return &stdinout_sfd_;
|
||||||
|
} else {
|
||||||
|
// PTYs can't close just input, so rather than close the
|
||||||
|
// FD and risk losing subprocess output, leave it open.
|
||||||
|
// This only happens if the client starts a PTY shell
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,7 +552,9 @@ ScopedFd* Subprocess::PassInput() {
|
||||||
ScopedFd* Subprocess::PassOutput(ScopedFd* sfd, ShellProtocol::Id id) {
|
ScopedFd* Subprocess::PassOutput(ScopedFd* sfd, ShellProtocol::Id id) {
|
||||||
int bytes = adb_read(sfd->fd(), output_->data(), output_->data_capacity());
|
int bytes = adb_read(sfd->fd(), output_->data(), output_->data_capacity());
|
||||||
if (bytes == 0 || (bytes < 0 && errno != EAGAIN)) {
|
if (bytes == 0 || (bytes < 0 && errno != EAGAIN)) {
|
||||||
if (bytes < 0) {
|
// read() returns EIO if a PTY closes; don't report this as an error,
|
||||||
|
// it just means the subprocess completed.
|
||||||
|
if (bytes < 0 && !(type_ == SubprocessType::kPty && errno == EIO)) {
|
||||||
PLOG(ERROR) << "error reading output FD " << sfd->fd();
|
PLOG(ERROR) << "error reading output FD " << sfd->fd();
|
||||||
}
|
}
|
||||||
return sfd;
|
return sfd;
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,12 @@ class ShellProtocol {
|
||||||
public:
|
public:
|
||||||
// This is an unscoped enum to make it easier to compare against raw bytes.
|
// This is an unscoped enum to make it easier to compare against raw bytes.
|
||||||
enum Id : uint8_t {
|
enum Id : uint8_t {
|
||||||
kIdStdin = 0,
|
kIdStdin = 0,
|
||||||
kIdStdout = 1,
|
kIdStdout = 1,
|
||||||
kIdStderr = 2,
|
kIdStderr = 2,
|
||||||
kIdExit = 3,
|
kIdExit = 3,
|
||||||
kIdInvalid = 255, // Indicates an invalid or unknown packet.
|
kIdCloseStdin = 4, // Close subprocess stdin if possible.
|
||||||
|
kIdInvalid = 255, // Indicates an invalid or unknown packet.
|
||||||
};
|
};
|
||||||
|
|
||||||
// ShellPackets will probably be too large to allocate on the stack so they
|
// ShellPackets will probably be too large to allocate on the stack so they
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,25 @@ TEST_F(ShellServiceTest, InteractivePtySubprocess) {
|
||||||
EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
|
EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests closing raw subprocess stdin.
|
||||||
|
TEST_F(ShellServiceTest, CloseClientStdin) {
|
||||||
|
ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
|
||||||
|
"cat; echo TEST_DONE",
|
||||||
|
SubprocessType::kRaw, SubprocessProtocol::kShell));
|
||||||
|
|
||||||
|
std::string input = "foo\nbar";
|
||||||
|
ShellProtocol* protocol = new ShellProtocol(subprocess_fd_);
|
||||||
|
memcpy(protocol->data(), input.data(), input.length());
|
||||||
|
ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
|
||||||
|
ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
|
||||||
|
delete protocol;
|
||||||
|
|
||||||
|
std::string stdout, stderr;
|
||||||
|
EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
|
||||||
|
ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
|
||||||
|
ExpectLinesEqual(stderr, {});
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that nothing breaks when the stdin/stdout pipe closes.
|
// Tests that nothing breaks when the stdin/stdout pipe closes.
|
||||||
TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
|
TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
|
||||||
ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
|
ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
|
||||||
|
|
|
||||||
|
|
@ -488,6 +488,10 @@ static __inline__ int adb_shutdown(int fd)
|
||||||
{
|
{
|
||||||
return shutdown(fd, SHUT_RDWR);
|
return shutdown(fd, SHUT_RDWR);
|
||||||
}
|
}
|
||||||
|
static __inline__ int adb_shutdown(int fd, int direction)
|
||||||
|
{
|
||||||
|
return shutdown(fd, direction);
|
||||||
|
}
|
||||||
#undef shutdown
|
#undef shutdown
|
||||||
#define shutdown ____xxx_shutdown
|
#define shutdown ____xxx_shutdown
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3265,6 +3265,15 @@ int unix_read(int fd, void* buf, size_t len) {
|
||||||
// terminal.
|
// terminal.
|
||||||
return _console_read(_console_handle, buf, len);
|
return _console_read(_console_handle, buf, len);
|
||||||
} else {
|
} else {
|
||||||
|
// On older versions of Windows (definitely 7, definitely not 10),
|
||||||
|
// ReadConsole() with a size >= 31367 fails, so if |fd| is a console
|
||||||
|
// we need to limit the read size. This may also catch devices like NUL,
|
||||||
|
// but that is OK as we just want to avoid capping pipes and files which
|
||||||
|
// don't need size limiting. This isatty() test is very simple and quick
|
||||||
|
// and doesn't call the OS.
|
||||||
|
if (isatty(fd) && len > 4096) {
|
||||||
|
len = 4096;
|
||||||
|
}
|
||||||
// Just call into C Runtime which can read from pipes/files and which
|
// Just call into C Runtime which can read from pipes/files and which
|
||||||
// can do LF/CR translation (which is overridable with _setmode()).
|
// can do LF/CR translation (which is overridable with _setmode()).
|
||||||
// Undefine the macro that is set in sysdeps.h which bans calls to
|
// Undefine the macro that is set in sysdeps.h which bans calls to
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue