diff --git a/adb/Android.mk b/adb/Android.mk index c633cee97..b0dcfac1a 100644 --- a/adb/Android.mk +++ b/adb/Android.mk @@ -131,6 +131,8 @@ LOCAL_CFLAGS := -DADB_HOST=0 $(LIBADB_CFLAGS) LOCAL_SRC_FILES := \ $(LIBADB_TEST_SRCS) \ $(LIBADB_TEST_linux_SRCS) \ + shell_service_protocol.cpp \ + shell_service_protocol_test.cpp \ LOCAL_SANITIZE := $(adb_target_sanitize) LOCAL_STATIC_LIBRARIES := libadbd @@ -145,7 +147,12 @@ LOCAL_MODULE := adb_test LOCAL_CFLAGS := -DADB_HOST=1 $(LIBADB_CFLAGS) LOCAL_CFLAGS_windows := $(LIBADB_windows_CFLAGS) LOCAL_CFLAGS_linux := $(LIBADB_linux_CFLAGS) -LOCAL_SRC_FILES := $(LIBADB_TEST_SRCS) services.cpp +LOCAL_SRC_FILES := \ + $(LIBADB_TEST_SRCS) \ + services.cpp \ + shell_service_protocol.cpp \ + shell_service_protocol_test.cpp \ + LOCAL_SRC_FILES_linux := $(LIBADB_TEST_linux_SRCS) LOCAL_SRC_FILES_darwin := $(LIBADB_TEST_darwin_SRCS) LOCAL_SANITIZE := $(adb_host_sanitize) @@ -201,6 +208,7 @@ LOCAL_SRC_FILES := \ adb_client.cpp \ services.cpp \ file_sync_client.cpp \ + shell_service_protocol.cpp \ LOCAL_CFLAGS += \ $(ADB_COMMON_CFLAGS) \ @@ -248,6 +256,8 @@ LOCAL_SRC_FILES := \ framebuffer_service.cpp \ remount_service.cpp \ set_verity_enable_state_service.cpp \ + shell_service.cpp \ + shell_service_protocol.cpp \ LOCAL_CFLAGS := \ $(ADB_COMMON_CFLAGS) \ diff --git a/adb/adb.cpp b/adb/adb.cpp index 14df8a152..a6b539fde 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -176,7 +176,8 @@ static void setup_trace_mask() { {"transport", TRACE_TRANSPORT}, {"jdwp", TRACE_JDWP}, {"services", TRACE_SERVICES}, - {"auth", TRACE_AUTH}}; + {"auth", TRACE_AUTH}, + {"shell", TRACE_SHELL}}; std::vector elements = android::base::Split(trace_setting, " "); for (const auto& elem : elements) { diff --git a/adb/adb_trace.h b/adb/adb_trace.h index 67ee854b7..623108e9b 100644 --- a/adb/adb_trace.h +++ b/adb/adb_trace.h @@ -37,6 +37,7 @@ enum AdbTrace { TRACE_SERVICES, TRACE_AUTH, TRACE_FDEVENT, + TRACE_SHELL }; extern int adb_trace_mask; diff --git a/adb/services.cpp b/adb/services.cpp index ac672f0f2..561431c82 100644 --- a/adb/services.cpp +++ b/adb/services.cpp @@ -24,11 +24,6 @@ #include #include -#if !ADB_HOST -#include -#include -#endif - #ifndef _WIN32 #include #include @@ -51,6 +46,7 @@ #include "adb_utils.h" #include "file_sync_service.h" #include "remount_service.h" +#include "shell_service.h" #include "transport.h" struct stinfo { @@ -59,11 +55,6 @@ struct stinfo { void *cookie; }; -enum class SubprocessType { - kPty, - kRaw, -}; - void *service_bootstrap_func(void *x) { stinfo* sti = reinterpret_cast(x); @@ -234,211 +225,6 @@ static int create_service_thread(void (*func)(int, void *), void *cookie) return s[0]; } -#if !ADB_HOST - -static 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"); - } -} - -#if !ADB_HOST -static int create_subproc_pty(const char* cmd, const char* arg0, - const char* arg1, pid_t* pid) { - D("create_subproc_pty(cmd=%s, arg0=%s, arg1=%s)", cmd, arg0, arg1); - char pts_name[PATH_MAX]; - int ptm; - *pid = forkpty(&ptm, pts_name, nullptr, nullptr); - if (*pid == -1) { - printf("- fork failed: %s -\n", strerror(errno)); - unix_close(ptm); - return -1; - } - - if (*pid == 0) { - init_subproc_child(); - - int pts = unix_open(pts_name, O_RDWR | O_CLOEXEC); - if (pts == -1) { - fprintf(stderr, "child failed to open pseudo-term slave %s: %s\n", - pts_name, strerror(errno)); - unix_close(ptm); - exit(-1); - } - - // arg0 is "-c" in batch mode and "-" in interactive mode. - if (strcmp(arg0, "-c") == 0) { - termios tattr; - if (tcgetattr(pts, &tattr) == -1) { - fprintf(stderr, "tcgetattr failed: %s\n", strerror(errno)); - unix_close(pts); - unix_close(ptm); - exit(-1); - } - - cfmakeraw(&tattr); - if (tcsetattr(pts, TCSADRAIN, &tattr) == -1) { - fprintf(stderr, "tcsetattr failed: %s\n", strerror(errno)); - unix_close(pts); - unix_close(ptm); - exit(-1); - } - } - - dup2(pts, STDIN_FILENO); - dup2(pts, STDOUT_FILENO); - dup2(pts, STDERR_FILENO); - - unix_close(pts); - unix_close(ptm); - - execl(cmd, cmd, arg0, arg1, nullptr); - fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", - cmd, strerror(errno), errno); - exit(-1); - } else { - return ptm; - } -} -#endif // !ADB_HOST - -static int create_subproc_raw(const char *cmd, const char *arg0, const char *arg1, pid_t *pid) -{ - D("create_subproc_raw(cmd=%s, arg0=%s, arg1=%s)", cmd, arg0, arg1); -#if defined(_WIN32) - fprintf(stderr, "error: create_subproc_raw not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1); - return -1; -#else - - // 0 is parent socket, 1 is child socket - int sv[2]; - if (adb_socketpair(sv) < 0) { - printf("[ cannot create socket pair - %s ]\n", strerror(errno)); - return -1; - } - D("socketpair: (%d,%d)", sv[0], sv[1]); - - *pid = fork(); - if (*pid < 0) { - printf("- fork failed: %s -\n", strerror(errno)); - adb_close(sv[0]); - adb_close(sv[1]); - return -1; - } - - if (*pid == 0) { - adb_close(sv[0]); - init_subproc_child(); - - dup2(sv[1], STDIN_FILENO); - dup2(sv[1], STDOUT_FILENO); - dup2(sv[1], STDERR_FILENO); - - adb_close(sv[1]); - - execl(cmd, cmd, arg0, arg1, NULL); - fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", - cmd, strerror(errno), errno); - exit(-1); - } else { - adb_close(sv[1]); - return sv[0]; - } -#endif /* !defined(_WIN32) */ -} -#endif /* !ABD_HOST */ - -#if ADB_HOST -#define SHELL_COMMAND "/bin/sh" -#else -#define SHELL_COMMAND "/system/bin/sh" -#endif - -#if !ADB_HOST -static void subproc_waiter_service(int fd, void *cookie) -{ - pid_t pid = (pid_t) (uintptr_t) cookie; - - D("entered. fd=%d of pid=%d", fd, pid); - while (true) { - int status; - pid_t p = waitpid(pid, &status, 0); - if (p == pid) { - D("fd=%d, post waitpid(pid=%d) status=%04x", fd, p, status); - if (WIFSIGNALED(status)) { - D("*** Killed by signal %d", WTERMSIG(status)); - break; - } else if (!WIFEXITED(status)) { - D("*** Didn't exit!!. status %d", status); - break; - } else if (WEXITSTATUS(status) >= 0) { - D("*** Exit code %d", WEXITSTATUS(status)); - break; - } - } - } - D("shell exited fd=%d of pid=%d err=%d", fd, pid, errno); - if (SHELL_EXIT_NOTIFY_FD >=0) { - int res; - res = WriteFdExactly(SHELL_EXIT_NOTIFY_FD, &fd, sizeof(fd)) ? 0 : -1; - D("notified shell exit via fd=%d for pid=%d res=%d errno=%d", - SHELL_EXIT_NOTIFY_FD, pid, res, errno); - } -} - -// Starts a subprocess and spawns a thread to wait for the subprocess to finish -// and trigger the necessary cleanup. -// -// |name| is the command to execute in the subprocess; empty string will start -// an interactive session. -// |type| selects between a PTY or raw subprocess. -// -// Returns an open file descriptor tied to the subprocess stdin/stdout/stderr. -static int create_subproc_thread(const char *name, SubprocessType type) { - const char *arg0, *arg1; - if (*name == '\0') { - arg0 = "-"; - arg1 = nullptr; - } else { - arg0 = "-c"; - arg1 = name; - } - - pid_t pid = -1; - int ret_fd; - if (type == SubprocessType::kPty) { - ret_fd = create_subproc_pty(SHELL_COMMAND, arg0, arg1, &pid); - } else { - ret_fd = create_subproc_raw(SHELL_COMMAND, arg0, arg1, &pid); - } - D("create_subproc ret_fd=%d pid=%d", ret_fd, pid); - - stinfo* sti = reinterpret_cast(malloc(sizeof(stinfo))); - if(sti == 0) fatal("cannot allocate stinfo"); - sti->func = subproc_waiter_service; - sti->cookie = (void*) (uintptr_t) pid; - sti->fd = ret_fd; - - if (!adb_thread_create(service_bootstrap_func, sti)) { - free(sti); - adb_close(ret_fd); - fprintf(stderr, "cannot create service thread\n"); - return -1; - } - - D("service thread started, fd=%d pid=%d", ret_fd, pid); - return ret_fd; -} -#endif - int service_to_fd(const char* name) { int ret = -1; @@ -483,13 +269,13 @@ int service_to_fd(const char* name) { const char* args = name + 6; if (*args) { // Non-interactive session uses a raw subprocess. - ret = create_subproc_thread(args, SubprocessType::kRaw); + ret = StartSubprocess(args, SubprocessType::kRaw); } else { // Interactive session uses a PTY subprocess. - ret = create_subproc_thread(args, SubprocessType::kPty); + ret = StartSubprocess(args, SubprocessType::kPty); } } else if(!strncmp(name, "exec:", 5)) { - ret = create_subproc_thread(name + 5, SubprocessType::kRaw); + ret = StartSubprocess(name + 5, SubprocessType::kRaw); } else if(!strncmp(name, "sync:", 5)) { ret = create_service_thread(file_sync_service, NULL); } else if(!strncmp(name, "remount:", 8)) { @@ -503,11 +289,11 @@ int service_to_fd(const char* name) { } else if(!strncmp(name, "unroot:", 7)) { ret = create_service_thread(restart_unroot_service, NULL); } else if(!strncmp(name, "backup:", 7)) { - ret = create_subproc_thread(android::base::StringPrintf("/system/bin/bu backup %s", - (name + 7)).c_str(), - SubprocessType::kRaw); + ret = StartSubprocess(android::base::StringPrintf("/system/bin/bu backup %s", + (name + 7)).c_str(), + SubprocessType::kRaw); } else if(!strncmp(name, "restore:", 8)) { - ret = create_subproc_thread("/system/bin/bu restore", SubprocessType::kRaw); + ret = StartSubprocess("/system/bin/bu restore", SubprocessType::kRaw); } else if(!strncmp(name, "tcpip:", 6)) { int port; if (sscanf(name + 6, "%d", &port) != 1) { diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp new file mode 100644 index 000000000..5f80a593e --- /dev/null +++ b/adb/shell_service.cpp @@ -0,0 +1,329 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#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(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 diff --git a/adb/shell_service.h b/adb/shell_service.h new file mode 100644 index 000000000..81d7036a5 --- /dev/null +++ b/adb/shell_service.h @@ -0,0 +1,135 @@ +/* + * 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. + */ + +// This file contains classes and functionality to launch shell subprocesses +// in adbd and communicate between those subprocesses and the adb client. +// +// The main features exposed here are: +// 1. A ShellPacket class to wrap data in a simple protocol. Both adbd and +// the adb client use this class to transmit data between them. +// 2. Functions to launch a subprocess on the adbd side. + +#ifndef SHELL_SERVICE_H_ +#define SHELL_SERVICE_H_ + +#include + +#include + +#include "adb.h" + +// Class to send and receive shell protocol packets. +// +// To keep things simple and predictable, reads and writes block until an entire +// packet is complete. +// +// Example: read raw data from |fd| and send it in a packet. +// ShellProtocol* p = new ShellProtocol(protocol_fd); +// int len = adb_read(stdout_fd, p->data(), p->data_capacity()); +// packet->WritePacket(ShellProtocol::kIdStdout, len); +// +// Example: read a packet and print it to |stdout|. +// ShellProtocol* p = new ShellProtocol(protocol_fd); +// if (p->ReadPacket() && p->id() == kIdStdout) { +// fwrite(p->data(), 1, p->data_length(), stdout); +// } +class ShellProtocol { + public: + // This is an unscoped enum to make it easier to compare against raw bytes. + enum Id : uint8_t { + kIdStdin = 0, + kIdStdout = 1, + kIdStderr = 2, + kIdExit = 3, + kIdInvalid = 255, // Indicates an invalid or unknown packet. + }; + + // ShellPackets will probably be too large to allocate on the stack so they + // should be dynamically allocated on the heap instead. + // + // |fd| is an open file descriptor to be used to send or receive packets. + explicit ShellProtocol(int fd); + virtual ~ShellProtocol(); + + // Returns a pointer to the data buffer. + const char* data() const { return buffer_ + kHeaderSize; } + char* data() { return buffer_ + kHeaderSize; } + + // Returns the total capacity of the data buffer. + size_t data_capacity() const { return buffer_end_ - data(); } + + // Reads a packet from the FD. + // + // If a packet is too big to fit in the buffer then Read() will split the + // packet across multiple calls. For example, reading a 50-byte packet into + // a 20-byte buffer would read 20 bytes, 20 bytes, then 10 bytes. + // + // Returns false if the FD closed or errored. + bool Read(); + + // Returns the ID of the packet in the buffer. + int id() const { return buffer_[0]; } + + // Returns the number of bytes that have been read into the data buffer. + size_t data_length() const { return data_length_; } + + // Writes the packet currently in the buffer to the FD. + // + // Returns false if the FD closed or errored. + bool Write(Id id, size_t length); + + private: + // Packets support 4-byte lengths. + typedef uint32_t length_t; + + enum { + // It's OK if MAX_PAYLOAD doesn't match on the sending and receiving + // end, reading will split larger packets into multiple smaller ones. + kBufferSize = MAX_PAYLOAD, + + // Header is 1 byte ID + 4 bytes length. + kHeaderSize = sizeof(Id) + sizeof(length_t) + }; + + int fd_; + char buffer_[kBufferSize]; + size_t data_length_ = 0, bytes_left_ = 0; + + // We need to be able to modify this value for testing purposes, but it + // will stay constant during actual program use. + char* buffer_end_ = buffer_ + sizeof(buffer_); + + friend class ShellProtocolTest; + + DISALLOW_COPY_AND_ASSIGN(ShellProtocol); +}; + +#if !ADB_HOST + +enum class SubprocessType { + kPty, + kRaw, +}; + +// Forks and starts a new shell subprocess. If |name| is empty an interactive +// shell is started, otherwise |name| is executed non-interactively. +// +// Returns an open FD connected to the subprocess or -1 on failure. +int StartSubprocess(const char* name, SubprocessType type); + +#endif // !ADB_HOST + +#endif // SHELL_SERVICE_H_ diff --git a/adb/shell_service_protocol.cpp b/adb/shell_service_protocol.cpp new file mode 100644 index 000000000..623629c75 --- /dev/null +++ b/adb/shell_service_protocol.cpp @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#include "shell_service.h" + +#include + +#include + +#include "adb_io.h" + +ShellProtocol::ShellProtocol(int fd) : fd_(fd) { + buffer_[0] = kIdInvalid; +} + +ShellProtocol::~ShellProtocol() { +} + +bool ShellProtocol::Read() { + // Only read a new header if we've finished the last packet. + if (!bytes_left_) { + if (!ReadFdExactly(fd_, buffer_, kHeaderSize)) { + return false; + } + + length_t packet_length; + memcpy(&packet_length, &buffer_[1], sizeof(packet_length)); + bytes_left_ = packet_length; + data_length_ = 0; + } + + size_t read_length = std::min(bytes_left_, data_capacity()); + if (read_length && !ReadFdExactly(fd_, data(), read_length)) { + return false; + } + + bytes_left_ -= read_length; + data_length_ = read_length; + + return true; +} + +bool ShellProtocol::Write(Id id, size_t length) { + buffer_[0] = id; + length_t typed_length = length; + memcpy(&buffer_[1], &typed_length, sizeof(typed_length)); + + return WriteFdExactly(fd_, buffer_, kHeaderSize + length); +} diff --git a/adb/shell_service_protocol_test.cpp b/adb/shell_service_protocol_test.cpp new file mode 100644 index 000000000..6c7521576 --- /dev/null +++ b/adb/shell_service_protocol_test.cpp @@ -0,0 +1,192 @@ +/* + * 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. + */ + +#include "shell_service.h" + +#include + +#include +#include + +#include "sysdeps.h" + +class ShellProtocolTest : public ::testing::Test { + public: + static void SetUpTestCase() { +#if !defined(_WIN32) + // This is normally done in main.cpp. + saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN); +#endif + } + + static void TearDownTestCase() { +#if !defined(_WIN32) + signal(SIGPIPE, saved_sigpipe_handler_); +#endif + } + + // Initializes the socketpair and ShellProtocols needed for testing. + void SetUp() { + int fds[2]; + ASSERT_EQ(0, adb_socketpair(fds)); + read_fd_ = fds[0]; + write_fd_ = fds[1]; + + write_protocol_ = new ShellProtocol(write_fd_); + ASSERT_TRUE(write_protocol_ != nullptr); + + read_protocol_ = new ShellProtocol(read_fd_); + ASSERT_TRUE(read_protocol_ != nullptr); + } + + // Cleans up FDs and ShellProtocols. If an FD is closed manually during a + // test, set it to -1 to prevent TearDown() trying to close it again. + void TearDown() { + for (int fd : {read_fd_, write_fd_}) { + if (fd >= 0) { + adb_close(fd); + } + } + for (ShellProtocol* protocol : {read_protocol_, write_protocol_}) { + if (protocol) { + delete protocol; + } + } + } + + // Fakes the buffer size so we can test filling buffers. + void SetReadDataCapacity(size_t size) { + read_protocol_->buffer_end_ = read_protocol_->data() + size; + } + + static sighandler_t saved_sigpipe_handler_; + + int read_fd_ = -1, write_fd_ = -1; + ShellProtocol *read_protocol_ = nullptr, *write_protocol_ = nullptr; +}; + +sighandler_t ShellProtocolTest::saved_sigpipe_handler_ = nullptr; + +namespace { + +// Returns true if the packet contains the given values. +bool PacketEquals(const ShellProtocol* protocol, ShellProtocol::Id id, + const void* data, size_t data_length) { + return (protocol->id() == id && + protocol->data_length() == data_length && + !memcmp(data, protocol->data(), data_length)); +} + +} // namespace + +// Tests data that can fit in a single packet. +TEST_F(ShellProtocolTest, FullPacket) { + ShellProtocol::Id id = ShellProtocol::kIdStdout; + char data[] = "abc 123 \0\r\n"; + + memcpy(write_protocol_->data(), data, sizeof(data)); + ASSERT_TRUE(write_protocol_->Write(id, sizeof(data))); + + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, data, sizeof(data))); +} + +// Tests data that has to be read multiple times due to smaller read buffer. +TEST_F(ShellProtocolTest, ReadBufferOverflow) { + ShellProtocol::Id id = ShellProtocol::kIdStdin; + + memcpy(write_protocol_->data(), "1234567890", 10); + ASSERT_TRUE(write_protocol_->Write(id, 10)); + + SetReadDataCapacity(4); + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, "1234", 4)); + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, "5678", 4)); + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, "90", 2)); +} + +// Tests a zero length packet. +TEST_F(ShellProtocolTest, ZeroLengthPacket) { + ShellProtocol::Id id = ShellProtocol::kIdStderr; + + ASSERT_TRUE(write_protocol_->Write(id, 0)); + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, nullptr, 0)); +} + +// Tests exit code packets. +TEST_F(ShellProtocolTest, ExitCodePacket) { + write_protocol_->data()[0] = 20; + ASSERT_TRUE(write_protocol_->Write(ShellProtocol::kIdExit, 1)); + + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_EQ(ShellProtocol::kIdExit, read_protocol_->id()); + ASSERT_EQ(20, read_protocol_->data()[0]); +} + +// Tests writing to a closed pipe. +TEST_F(ShellProtocolTest, WriteToClosedPipeFail) { + adb_close(read_fd_); + read_fd_ = -1; + + ASSERT_FALSE(write_protocol_->Write(ShellProtocol::kIdStdout, 0)); +} + +// Tests writing to a closed FD. +TEST_F(ShellProtocolTest, WriteToClosedFdFail) { + adb_close(write_fd_); + write_fd_ = -1; + + ASSERT_FALSE(write_protocol_->Write(ShellProtocol::kIdStdout, 0)); +} + +// Tests reading from a closed pipe. +TEST_F(ShellProtocolTest, ReadFromClosedPipeFail) { + adb_close(write_fd_); + write_fd_ = -1; + + ASSERT_FALSE(read_protocol_->Read()); +} + +// Tests reading from a closed FD. +TEST_F(ShellProtocolTest, ReadFromClosedFdFail) { + adb_close(read_fd_); + read_fd_ = -1; + + ASSERT_FALSE(read_protocol_->Read()); +} + +// Tests reading from a closed pipe that has a packet waiting. This checks that +// even if the pipe closes before we can fully read its contents we will still +// be able to access the last packets. +TEST_F(ShellProtocolTest, ReadPacketFromClosedPipe) { + ShellProtocol::Id id = ShellProtocol::kIdStdout; + char data[] = "foo bar"; + + memcpy(write_protocol_->data(), data, sizeof(data)); + ASSERT_TRUE(write_protocol_->Write(id, sizeof(data))); + adb_close(write_fd_); + write_fd_ = -1; + + // First read should grab the packet. + ASSERT_TRUE(read_protocol_->Read()); + ASSERT_TRUE(PacketEquals(read_protocol_, id, data, sizeof(data))); + + // Second read should fail. + ASSERT_FALSE(read_protocol_->Read()); +}