android_system_core/adb/shell_service.cpp
David Pursell a932058504 adb: refactor subprocess code.
Refactor shell_service.cpp to remove dependencies on service.cpp and
combine some common logic between PTY and raw subprocesses.

This will make it easier to add additional common code paths for
the upcoming shell protocol.

Change-Id: I497d30dd388de61b6e68d9086dce38f33dd92876
2015-09-04 11:18:51 -07:00

329 lines
8.9 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define TRACE_TAG TRACE_SHELL
#include "shell_service.h"
#if !ADB_HOST
#include <errno.h>
#include <pty.h>
#include <termios.h>
#include <base/logging.h>
#include <base/stringprintf.h>
#include <paths.h>
#include "adb.h"
#include "adb_io.h"
#include "adb_trace.h"
#include "sysdeps.h"
namespace {
void init_subproc_child()
{
setsid();
// Set OOM score adjustment to prevent killing
int fd = adb_open("/proc/self/oom_score_adj", O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
adb_write(fd, "0", 1);
adb_close(fd);
} else {
D("adb: unable to update oom_score_adj");
}
}
// Reads from |fd| until close or failure.
std::string ReadAll(int fd) {
char buffer[512];
std::string received;
while (1) {
int bytes = adb_read(fd, buffer, sizeof(buffer));
if (bytes <= 0) {
break;
}
received.append(buffer, bytes);
}
return received;
}
// Helper to automatically close an FD when it goes out of scope.
class ScopedFd {
public:
ScopedFd() {}
~ScopedFd() { Reset(); }
void Reset(int fd=-1) {
if (fd != fd_) {
if (valid()) {
adb_close(fd_);
}
fd_ = fd;
}
}
int Release() {
int temp = fd_;
fd_ = -1;
return temp;
}
bool valid() const { return fd_ >= 0; }
int fd() const { return fd_; }
private:
int fd_ = -1;
DISALLOW_COPY_AND_ASSIGN(ScopedFd);
};
// Creates a socketpair and saves the endpoints to |fd1| and |fd2|.
bool CreateSocketpair(ScopedFd* fd1, ScopedFd* fd2) {
int sockets[2];
if (adb_socketpair(sockets) < 0) {
PLOG(ERROR) << "cannot create socket pair";
return false;
}
fd1->Reset(sockets[0]);
fd2->Reset(sockets[1]);
return true;
}
class Subprocess {
public:
Subprocess(const std::string& command, SubprocessType type);
~Subprocess();
const std::string& command() const { return command_; }
bool is_interactive() const { return command_.empty(); }
int local_socket_fd() const { return local_socket_sfd_.fd(); }
pid_t pid() const { return pid_; }
// Sets up FDs, forks a subprocess, starts the subprocess manager thread,
// and exec's the child. Returns false on failure.
bool ForkAndExec();
private:
// Opens the file at |pts_name|.
int OpenPtyChildFd(const char* pts_name, ScopedFd* error_sfd);
static void* ThreadHandler(void* userdata);
void WaitForExit();
const std::string command_;
SubprocessType type_;
pid_t pid_ = -1;
ScopedFd local_socket_sfd_;
DISALLOW_COPY_AND_ASSIGN(Subprocess);
};
Subprocess::Subprocess(const std::string& command, SubprocessType type)
: command_(command), type_(type) {
}
Subprocess::~Subprocess() {
}
bool Subprocess::ForkAndExec() {
ScopedFd parent_sfd, child_sfd, parent_error_sfd, child_error_sfd;
char pts_name[PATH_MAX];
// Create a socketpair for the fork() child to report any errors back to
// the parent. Since we use threads, logging directly from the child could
// create a race condition.
if (!CreateSocketpair(&parent_error_sfd, &child_error_sfd)) {
LOG(ERROR) << "failed to create pipe for subprocess error reporting";
}
if (type_ == SubprocessType::kPty) {
int fd;
pid_ = forkpty(&fd, pts_name, nullptr, nullptr);
parent_sfd.Reset(fd);
} else {
if (!CreateSocketpair(&parent_sfd, &child_sfd)) {
return false;
}
pid_ = fork();
}
if (pid_ == -1) {
PLOG(ERROR) << "fork failed";
return false;
}
if (pid_ == 0) {
// Subprocess child.
init_subproc_child();
if (type_ == SubprocessType::kPty) {
child_sfd.Reset(OpenPtyChildFd(pts_name, &child_error_sfd));
}
dup2(child_sfd.fd(), STDIN_FILENO);
dup2(child_sfd.fd(), STDOUT_FILENO);
dup2(child_sfd.fd(), STDERR_FILENO);
// exec doesn't trigger destructors, close the FDs manually.
parent_sfd.Reset();
child_sfd.Reset();
parent_error_sfd.Reset();
close_on_exec(child_error_sfd.fd());
if (is_interactive()) {
execl(_PATH_BSHELL, _PATH_BSHELL, "-", nullptr);
} else {
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command_.c_str(), nullptr);
}
WriteFdExactly(child_error_sfd.fd(), "exec '" _PATH_BSHELL "' failed");
child_error_sfd.Reset();
exit(-1);
}
// Subprocess parent.
D("subprocess parent: subprocess FD = %d", parent_sfd.fd());
// Wait to make sure the subprocess exec'd without error.
child_error_sfd.Reset();
std::string error_message = ReadAll(parent_error_sfd.fd());
if (!error_message.empty()) {
LOG(ERROR) << error_message;
return false;
}
local_socket_sfd_.Reset(parent_sfd.Release());
if (!adb_thread_create(ThreadHandler, this)) {
PLOG(ERROR) << "failed to create subprocess thread";
return false;
}
return true;
}
int Subprocess::OpenPtyChildFd(const char* pts_name, ScopedFd* error_sfd) {
int child_fd = adb_open(pts_name, O_RDWR | O_CLOEXEC);
if (child_fd == -1) {
// Don't use WriteFdFmt; since we're in the fork() child we don't want
// to allocate any heap memory to avoid race conditions.
const char* messages[] = {"child failed to open pseudo-term slave ",
pts_name, ": ", strerror(errno)};
for (const char* message : messages) {
WriteFdExactly(error_sfd->fd(), message);
}
exit(-1);
}
if (!is_interactive()) {
termios tattr;
if (tcgetattr(child_fd, &tattr) == -1) {
WriteFdExactly(error_sfd->fd(), "tcgetattr failed");
exit(-1);
}
cfmakeraw(&tattr);
if (tcsetattr(child_fd, TCSADRAIN, &tattr) == -1) {
WriteFdExactly(error_sfd->fd(), "tcsetattr failed");
exit(-1);
}
}
return child_fd;
}
void* Subprocess::ThreadHandler(void* userdata) {
Subprocess* subprocess = reinterpret_cast<Subprocess*>(userdata);
adb_thread_setname(android::base::StringPrintf(
"shell srvc %d", subprocess->local_socket_fd()));
subprocess->WaitForExit();
D("deleting Subprocess");
delete subprocess;
return nullptr;
}
void Subprocess::WaitForExit() {
D("waiting for pid %d", pid_);
while (true) {
int status;
if (pid_ == waitpid(pid_, &status, 0)) {
D("post waitpid (pid=%d) status=%04x", pid_, status);
if (WIFSIGNALED(status)) {
D("subprocess killed by signal %d", WTERMSIG(status));
break;
} else if (!WIFEXITED(status)) {
D("subprocess didn't exit");
break;
} else if (WEXITSTATUS(status) >= 0) {
D("subprocess exit code = %d", WEXITSTATUS(status));
break;
}
}
}
// Pass the local socket FD to the shell cleanup fdevent.
if (SHELL_EXIT_NOTIFY_FD >= 0) {
int fd = local_socket_sfd_.fd();
if (WriteFdExactly(SHELL_EXIT_NOTIFY_FD, &fd, sizeof(fd))) {
D("passed fd %d to SHELL_EXIT_NOTIFY_FD (%d) for pid %d",
fd, SHELL_EXIT_NOTIFY_FD, pid_);
// The shell exit fdevent now owns the FD and will close it once
// the last bit of data flushes through.
local_socket_sfd_.Release();
} else {
PLOG(ERROR) << "failed to write fd " << fd
<< " to SHELL_EXIT_NOTIFY_FD (" << SHELL_EXIT_NOTIFY_FD
<< ") for pid " << pid_;
}
}
}
} // namespace
int StartSubprocess(const char *name, SubprocessType type) {
D("starting %s subprocess: '%s'",
type == SubprocessType::kRaw ? "raw" : "PTY", name);
Subprocess* subprocess = new Subprocess(name, type);
if (!subprocess) {
LOG(ERROR) << "failed to allocate new subprocess";
return -1;
}
if (!subprocess->ForkAndExec()) {
LOG(ERROR) << "failed to start subprocess";
delete subprocess;
return -1;
}
D("subprocess creation successful: local_socket_fd=%d, pid=%d",
subprocess->local_socket_fd(), subprocess->pid());
return subprocess->local_socket_fd();
}
#endif // !ADB_HOST