diff options
Diffstat (limited to 'source/Plugins/Platform/Android/AdbClient.cpp')
-rw-r--r-- | source/Plugins/Platform/Android/AdbClient.cpp | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/source/Plugins/Platform/Android/AdbClient.cpp b/source/Plugins/Platform/Android/AdbClient.cpp new file mode 100644 index 0000000000000..736447fd22d21 --- /dev/null +++ b/source/Plugins/Platform/Android/AdbClient.cpp @@ -0,0 +1,569 @@ +//===-- AdbClient.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// Other libraries and framework includes +#include "lldb/Core/DataBuffer.h" +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataEncoder.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Host/FileSpec.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileUtilities.h" + +// Project includes +#include "AdbClient.h" + +#include <limits.h> + +#include <algorithm> +#include <fstream> +#include <sstream> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::platform_android; + +namespace { + +const uint32_t kReadTimeout = 1000000; // 1 second +const char * kOKAY = "OKAY"; +const char * kFAIL = "FAIL"; +const char * kDATA = "DATA"; +const char * kDONE = "DONE"; + +const char * kSEND = "SEND"; +const char * kRECV = "RECV"; +const char * kSTAT = "STAT"; + +const size_t kSyncPacketLen = 8; +// Maximum size of a filesync DATA packet. +const size_t kMaxPushData = 2*1024; +// Default mode for pushed files. +const uint32_t kDefaultMode = 0100770; // S_IFREG | S_IRWXU | S_IRWXG + +const char * kSocketNamespaceAbstract = "localabstract"; +const char * kSocketNamespaceFileSystem = "localfilesystem"; + +} // namespace + +Error +AdbClient::CreateByDeviceID(const std::string &device_id, AdbClient &adb) +{ + DeviceIDList connect_devices; + auto error = adb.GetDevices(connect_devices); + if (error.Fail()) + return error; + + if (device_id.empty()) + { + if (connect_devices.size() != 1) + return Error("Expected a single connected device, got instead %" PRIu64, + static_cast<uint64_t>(connect_devices.size())); + + adb.SetDeviceID(connect_devices.front()); + } + else + { + auto find_it = std::find(connect_devices.begin(), connect_devices.end(), device_id); + if (find_it == connect_devices.end()) + return Error("Device \"%s\" not found", device_id.c_str()); + + adb.SetDeviceID(*find_it); + } + return error; +} + +AdbClient::AdbClient (const std::string &device_id) + : m_device_id (device_id) +{ +} + +void +AdbClient::SetDeviceID (const std::string &device_id) +{ + m_device_id = device_id; +} + +const std::string& +AdbClient::GetDeviceID() const +{ + return m_device_id; +} + +Error +AdbClient::Connect () +{ + Error error; + m_conn.Connect ("connect://localhost:5037", &error); + + return error; +} + +Error +AdbClient::GetDevices (DeviceIDList &device_list) +{ + device_list.clear (); + + auto error = SendMessage ("host:devices"); + if (error.Fail ()) + return error; + + error = ReadResponseStatus (); + if (error.Fail ()) + return error; + + std::vector<char> in_buffer; + error = ReadMessage (in_buffer); + + llvm::StringRef response (&in_buffer[0], in_buffer.size ()); + llvm::SmallVector<llvm::StringRef, 4> devices; + response.split (devices, "\n", -1, false); + + for (const auto device: devices) + device_list.push_back (device.split ('\t').first); + + return error; +} + +Error +AdbClient::SetPortForwarding (const uint16_t local_port, const uint16_t remote_port) +{ + char message[48]; + snprintf (message, sizeof (message), "forward:tcp:%d;tcp:%d", local_port, remote_port); + + const auto error = SendDeviceMessage (message); + if (error.Fail ()) + return error; + + return ReadResponseStatus (); +} + +Error +AdbClient::SetPortForwarding (const uint16_t local_port, + const char* remote_socket_name, + const UnixSocketNamespace socket_namespace) +{ + char message[PATH_MAX]; + const char * sock_namespace_str = (socket_namespace == UnixSocketNamespaceAbstract) ? + kSocketNamespaceAbstract : kSocketNamespaceFileSystem; + snprintf (message, sizeof (message), "forward:tcp:%d;%s:%s", + local_port, + sock_namespace_str, + remote_socket_name); + + const auto error = SendDeviceMessage (message); + if (error.Fail ()) + return error; + + return ReadResponseStatus (); +} + +Error +AdbClient::DeletePortForwarding (const uint16_t local_port) +{ + char message[32]; + snprintf (message, sizeof (message), "killforward:tcp:%d", local_port); + + const auto error = SendDeviceMessage (message); + if (error.Fail ()) + return error; + + return ReadResponseStatus (); +} + +Error +AdbClient::SendMessage (const std::string &packet, const bool reconnect) +{ + Error error; + if (reconnect) + { + error = Connect (); + if (error.Fail ()) + return error; + } + + char length_buffer[5]; + snprintf (length_buffer, sizeof (length_buffer), "%04x", static_cast<int>(packet.size ())); + + ConnectionStatus status; + + m_conn.Write (length_buffer, 4, status, &error); + if (error.Fail ()) + return error; + + m_conn.Write (packet.c_str (), packet.size (), status, &error); + return error; +} + +Error +AdbClient::SendDeviceMessage (const std::string &packet) +{ + std::ostringstream msg; + msg << "host-serial:" << m_device_id << ":" << packet; + return SendMessage (msg.str ()); +} + +Error +AdbClient::ReadMessage (std::vector<char> &message) +{ + message.clear (); + + char buffer[5]; + buffer[4] = 0; + + auto error = ReadAllBytes (buffer, 4); + if (error.Fail ()) + return error; + + unsigned int packet_len = 0; + sscanf (buffer, "%x", &packet_len); + + message.resize (packet_len, 0); + error = ReadAllBytes (&message[0], packet_len); + if (error.Fail ()) + message.clear (); + + return error; +} + +Error +AdbClient::ReadMessageStream (std::vector<char>& message, uint32_t timeout_ms) +{ + auto start = std::chrono::steady_clock::now(); + message.clear(); + + Error error; + lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; + char buffer[1024]; + while (error.Success() && status == lldb::eConnectionStatusSuccess) + { + auto end = std::chrono::steady_clock::now(); + uint32_t elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); + if (elapsed_time >= timeout_ms) + return Error("Timed out"); + + size_t n = m_conn.Read(buffer, sizeof(buffer), 1000 * (timeout_ms - elapsed_time), status, &error); + if (n > 0) + message.insert(message.end(), &buffer[0], &buffer[n]); + } + return error; +} + +Error +AdbClient::ReadResponseStatus() +{ + char response_id[5]; + + static const size_t packet_len = 4; + response_id[packet_len] = 0; + + auto error = ReadAllBytes (response_id, packet_len); + if (error.Fail ()) + return error; + + if (strncmp (response_id, kOKAY, packet_len) != 0) + return GetResponseError (response_id); + + return error; +} + +Error +AdbClient::GetResponseError (const char *response_id) +{ + if (strcmp (response_id, kFAIL) != 0) + return Error ("Got unexpected response id from adb: \"%s\"", response_id); + + std::vector<char> error_message; + auto error = ReadMessage (error_message); + if (error.Success ()) + error.SetErrorString (std::string (&error_message[0], error_message.size ()).c_str ()); + + return error; +} + +Error +AdbClient::SwitchDeviceTransport () +{ + std::ostringstream msg; + msg << "host:transport:" << m_device_id; + + auto error = SendMessage (msg.str ()); + if (error.Fail ()) + return error; + + return ReadResponseStatus (); +} + +Error +AdbClient::PullFile (const FileSpec &remote_file, const FileSpec &local_file) +{ + auto error = StartSync (); + if (error.Fail ()) + return error; + + const auto local_file_path = local_file.GetPath (); + llvm::FileRemover local_file_remover (local_file_path.c_str ()); + + std::ofstream dst (local_file_path, std::ios::out | std::ios::binary); + if (!dst.is_open ()) + return Error ("Unable to open local file %s", local_file_path.c_str()); + + const auto remote_file_path = remote_file.GetPath (false); + error = SendSyncRequest (kRECV, remote_file_path.length (), remote_file_path.c_str ()); + if (error.Fail ()) + return error; + + std::vector<char> chunk; + bool eof = false; + while (!eof) + { + error = PullFileChunk (chunk, eof); + if (error.Fail ()) + return error; + if (!eof) + dst.write (&chunk[0], chunk.size ()); + } + + local_file_remover.releaseFile (); + return error; +} + +Error +AdbClient::PushFile (const FileSpec &local_file, const FileSpec &remote_file) +{ + auto error = StartSync (); + if (error.Fail ()) + return error; + + const auto local_file_path (local_file.GetPath ()); + std::ifstream src (local_file_path.c_str(), std::ios::in | std::ios::binary); + if (!src.is_open ()) + return Error ("Unable to open local file %s", local_file_path.c_str()); + + std::stringstream file_description; + file_description << remote_file.GetPath(false).c_str() << "," << kDefaultMode; + std::string file_description_str = file_description.str(); + error = SendSyncRequest (kSEND, file_description_str.length(), file_description_str.c_str()); + if (error.Fail ()) + return error; + + char chunk[kMaxPushData]; + while (!src.eof() && !src.read(chunk, kMaxPushData).bad()) + { + size_t chunk_size = src.gcount(); + error = SendSyncRequest(kDATA, chunk_size, chunk); + if (error.Fail ()) + return Error ("Failed to send file chunk: %s", error.AsCString ()); + } + error = SendSyncRequest(kDONE, local_file.GetModificationTime().seconds(), nullptr); + if (error.Fail ()) + return error; + + std::string response_id; + uint32_t data_len; + error = ReadSyncHeader (response_id, data_len); + if (error.Fail ()) + return Error ("Failed to read DONE response: %s", error.AsCString ()); + if (response_id == kFAIL) + { + std::string error_message (data_len, 0); + error = ReadAllBytes (&error_message[0], data_len); + if (error.Fail ()) + return Error ("Failed to read DONE error message: %s", error.AsCString ()); + return Error ("Failed to push file: %s", error_message.c_str ()); + } + else if (response_id != kOKAY) + return Error ("Got unexpected DONE response: %s", response_id.c_str ()); + + // If there was an error reading the source file, finish the adb file + // transfer first so that adb isn't expecting any more data. + if (src.bad()) + return Error ("Failed read on %s", local_file_path.c_str()); + return error; +} + +Error +AdbClient::StartSync () +{ + auto error = SwitchDeviceTransport (); + if (error.Fail ()) + return Error ("Failed to switch to device transport: %s", error.AsCString ()); + + error = Sync (); + if (error.Fail ()) + return Error ("Sync failed: %s", error.AsCString ()); + + return error; +} + +Error +AdbClient::Sync () +{ + auto error = SendMessage ("sync:", false); + if (error.Fail ()) + return error; + + return ReadResponseStatus (); +} + +Error +AdbClient::PullFileChunk (std::vector<char> &buffer, bool &eof) +{ + buffer.clear (); + + std::string response_id; + uint32_t data_len; + auto error = ReadSyncHeader (response_id, data_len); + if (error.Fail ()) + return error; + + if (response_id == kDATA) + { + buffer.resize (data_len, 0); + error = ReadAllBytes (&buffer[0], data_len); + if (error.Fail ()) + buffer.clear (); + } + else if (response_id == kDONE) + { + eof = true; + } + else if (response_id == kFAIL) + { + std::string error_message (data_len, 0); + error = ReadAllBytes (&error_message[0], data_len); + if (error.Fail ()) + return Error ("Failed to read pull error message: %s", error.AsCString ()); + return Error ("Failed to pull file: %s", error_message.c_str ()); + } + else + return Error ("Pull failed with unknown response: %s", response_id.c_str ()); + + return Error (); +} + +Error +AdbClient::SendSyncRequest (const char *request_id, const uint32_t data_len, const void *data) +{ + const DataBufferSP data_sp (new DataBufferHeap (kSyncPacketLen, 0)); + DataEncoder encoder (data_sp, eByteOrderLittle, sizeof (void*)); + auto offset = encoder.PutData (0, request_id, strlen(request_id)); + encoder.PutU32 (offset, data_len); + + Error error; + ConnectionStatus status; + m_conn.Write (data_sp->GetBytes (), kSyncPacketLen, status, &error); + if (error.Fail ()) + return error; + + if (data) + m_conn.Write (data, data_len, status, &error); + return error; +} + +Error +AdbClient::ReadSyncHeader (std::string &response_id, uint32_t &data_len) +{ + char buffer[kSyncPacketLen]; + + auto error = ReadAllBytes (buffer, kSyncPacketLen); + if (error.Success ()) + { + response_id.assign (&buffer[0], 4); + DataExtractor extractor (&buffer[4], 4, eByteOrderLittle, sizeof (void*)); + offset_t offset = 0; + data_len = extractor.GetU32 (&offset); + } + + return error; +} + +Error +AdbClient::ReadAllBytes (void *buffer, size_t size) +{ + Error error; + ConnectionStatus status; + char *read_buffer = static_cast<char*>(buffer); + + size_t tota_read_bytes = 0; + while (tota_read_bytes < size) + { + auto read_bytes = m_conn.Read (read_buffer + tota_read_bytes, size - tota_read_bytes, kReadTimeout, status, &error); + if (error.Fail ()) + return error; + tota_read_bytes += read_bytes; + } + return error; +} + +Error +AdbClient::Stat (const FileSpec &remote_file, uint32_t &mode, uint32_t &size, uint32_t &mtime) +{ + auto error = StartSync (); + if (error.Fail ()) + return error; + + const std::string remote_file_path (remote_file.GetPath (false)); + error = SendSyncRequest (kSTAT, remote_file_path.length (), remote_file_path.c_str ()); + if (error.Fail ()) + return Error ("Failed to send request: %s", error.AsCString ()); + + static const size_t stat_len = strlen (kSTAT); + static const size_t response_len = stat_len + (sizeof (uint32_t) * 3); + + std::vector<char> buffer (response_len); + error = ReadAllBytes (&buffer[0], buffer.size ()); + if (error.Fail ()) + return Error ("Failed to read response: %s", error.AsCString ()); + + DataExtractor extractor (&buffer[0], buffer.size (), eByteOrderLittle, sizeof (void*)); + offset_t offset = 0; + + const void* command = extractor.GetData (&offset, stat_len); + if (!command) + return Error ("Failed to get response command"); + const char* command_str = static_cast<const char*> (command); + if (strncmp (command_str, kSTAT, stat_len)) + return Error ("Got invalid stat command: %s", command_str); + + mode = extractor.GetU32 (&offset); + size = extractor.GetU32 (&offset); + mtime = extractor.GetU32 (&offset); + return Error (); +} + +Error +AdbClient::Shell (const char* command, uint32_t timeout_ms, std::string* output) +{ + auto error = SwitchDeviceTransport (); + if (error.Fail ()) + return Error ("Failed to switch to device transport: %s", error.AsCString ()); + + StreamString adb_command; + adb_command.Printf("shell:%s", command); + error = SendMessage (adb_command.GetData(), false); + if (error.Fail ()) + return error; + + error = ReadResponseStatus (); + if (error.Fail ()) + return error; + + std::vector<char> in_buffer; + error = ReadMessageStream (in_buffer, timeout_ms); + if (error.Fail()) + return error; + + if (output) + output->assign(in_buffer.begin(), in_buffer.end()); + return error; +} |