diff --git a/adb/Android.mk b/adb/Android.mk index 778903561..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) \ @@ -249,6 +257,7 @@ LOCAL_SRC_FILES := \ 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/shell_service.h b/adb/shell_service.h index c2a048cc0..81d7036a5 100644 --- a/adb/shell_service.h +++ b/adb/shell_service.h @@ -14,9 +14,109 @@ * 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 { 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..85b2f917b --- /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 sig_t saved_sigpipe_handler_; + + int read_fd_ = -1, write_fd_ = -1; + ShellProtocol *read_protocol_ = nullptr, *write_protocol_ = nullptr; +}; + +sig_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()); +}