adb: create shell protocol class (take 2).
Adds a new class ShellProtocol to help read and write data with `adb shell`. This will allow splitting streams and sending out-of-band data such as exit codes. Nothing uses the new class yet except the unit tests. This is the second attempt at this CL, the first is at http://r.android.com/169600. The problems was using sighandler_t which is not available on mac. sig_t is used instead which is available due to _GNU_SOURCE being defined in Android.mk, which causes _BSD_SOURCE -> __USE_BSD -> sig_t to be defined. Nothing else has been changed from the original CL. Bug: http://b/23030641 Change-Id: I7bd7f5a82ad811fbca7a3eee1236d2c55ae57c48
This commit is contained in:
parent
da0b0116ba
commit
b9e2e84e7e
|
@ -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) \
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
|
||||
#include <base/macros.h>
|
||||
|
||||
#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 {
|
||||
|
|
|
@ -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 <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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 <gtest/gtest.h>
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
#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());
|
||||
}
|
Loading…
Reference in New Issue