| /* |
| * Copyright (C) 2016 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 "adb.h" |
| |
| #include "command.h" |
| #include "print.h" |
| #include "util.h" |
| |
| #include <errno.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <limits.h> |
| |
| #include <iostream> |
| #include <istream> |
| #include <streambuf> |
| |
| using namespace std; |
| |
| struct Buffer: public streambuf |
| { |
| Buffer(char* begin, size_t size); |
| }; |
| |
| Buffer::Buffer(char* begin, size_t size) |
| { |
| this->setg(begin, begin, begin + size); |
| } |
| |
| int |
| run_adb(const char* first, ...) |
| { |
| Command cmd("adb"); |
| |
| if (first == NULL) { |
| return 0; |
| } |
| |
| cmd.AddArg(first); |
| |
| va_list args; |
| va_start(args, first); |
| while (true) { |
| const char* arg = va_arg(args, char*); |
| if (arg == NULL) { |
| break; |
| } |
| cmd.AddArg(arg); |
| } |
| va_end(args); |
| |
| return run_command(cmd); |
| } |
| |
| string |
| get_system_property(const string& name, int* err) |
| { |
| Command cmd("adb"); |
| cmd.AddArg("shell"); |
| cmd.AddArg("getprop"); |
| cmd.AddArg(name); |
| |
| return trim(get_command_output(cmd, err, false)); |
| } |
| |
| |
| static uint64_t |
| read_varint(int fd, int* err, bool* done) |
| { |
| uint32_t bits = 0; |
| uint64_t result = 0; |
| while (true) { |
| uint8_t byte; |
| ssize_t amt = read(fd, &byte, 1); |
| if (amt == 0) { |
| *done = true; |
| return result; |
| } else if (amt < 0) { |
| return *err = errno; |
| } |
| result |= uint64_t(byte & 0x7F) << bits; |
| if ((byte & 0x80) == 0) { |
| return result; |
| } |
| bits += 7; |
| if (bits > 64) { |
| *err = -1; |
| return 0; |
| } |
| } |
| } |
| |
| static char* |
| read_sized_buffer(int fd, int* err, size_t* resultSize) |
| { |
| bool done = false; |
| uint64_t size = read_varint(fd, err, &done); |
| if (*err != 0 || done) { |
| return NULL; |
| } |
| if (size == 0) { |
| *resultSize = 0; |
| return NULL; |
| } |
| // 10 MB seems like a reasonable limit. |
| if (size > 10*1024*1024) { |
| print_error("result buffer too large: %llu", size); |
| return NULL; |
| } |
| char* buf = (char*)malloc(size); |
| if (buf == NULL) { |
| print_error("Can't allocate a buffer of size for test results: %llu", size); |
| return NULL; |
| } |
| int pos = 0; |
| while (size - pos > 0) { |
| ssize_t amt = read(fd, buf+pos, size-pos); |
| if (amt == 0) { |
| // early end of pipe |
| print_error("Early end of pipe."); |
| *err = -1; |
| free(buf); |
| return NULL; |
| } else if (amt < 0) { |
| // error |
| *err = errno; |
| free(buf); |
| return NULL; |
| } |
| pos += amt; |
| } |
| *resultSize = (size_t)size; |
| return buf; |
| } |
| |
| static int |
| read_sized_proto(int fd, Message* message) |
| { |
| int err = 0; |
| size_t size; |
| char* buf = read_sized_buffer(fd, &err, &size); |
| if (err != 0) { |
| if (buf != NULL) { |
| free(buf); |
| } |
| return err; |
| } else if (size == 0) { |
| if (buf != NULL) { |
| free(buf); |
| } |
| return 0; |
| } else if (buf == NULL) { |
| return -1; |
| } |
| Buffer buffer(buf, size); |
| istream in(&buffer); |
| |
| err = message->ParseFromIstream(&in) ? 0 : -1; |
| |
| free(buf); |
| return err; |
| } |
| |
| static int |
| skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize) |
| { |
| while (size > 0) { |
| ssize_t amt = size < scratchSize ? size : scratchSize; |
| fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt); |
| amt = read(fd, scratch, amt); |
| if (amt == 0) { |
| // early end of pipe |
| print_error("Early end of pipe."); |
| return -1; |
| } else if (amt < 0) { |
| // error |
| return errno; |
| } |
| size -= amt; |
| } |
| return 0; |
| } |
| |
| static int |
| skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) { |
| bool done = false; |
| int err; |
| uint64_t size; |
| switch (tag & 0x7) { |
| case 0: // varint |
| read_varint(fd, &err, &done); |
| if (err != 0) { |
| return err; |
| } else if (done) { |
| return -1; |
| } else { |
| return 0; |
| } |
| case 1: |
| return skip_bytes(fd, 8, scratch, scratchSize); |
| case 2: |
| size = read_varint(fd, &err, &done); |
| if (err != 0) { |
| return err; |
| } else if (done) { |
| return -1; |
| } |
| if (size > INT_MAX) { |
| // we'll be here a long time but this keeps it from overflowing |
| return -1; |
| } |
| return skip_bytes(fd, (ssize_t)size, scratch, scratchSize); |
| case 5: |
| return skip_bytes(fd, 4, scratch, scratchSize); |
| default: |
| print_error("bad wire type for tag 0x%lx\n", tag); |
| return -1; |
| } |
| } |
| |
| static int |
| read_instrumentation_results(int fd, char* scratch, int scratchSize, |
| InstrumentationCallbacks* callbacks) |
| { |
| bool done = false; |
| int err = 0; |
| string result; |
| while (true) { |
| uint64_t tag = read_varint(fd, &err, &done); |
| if (done) { |
| // Done reading input (this is the only place that a stream end isn't an error). |
| return 0; |
| } else if (err != 0) { |
| return err; |
| } else if (tag == 0xa) { // test_status |
| TestStatus status; |
| err = read_sized_proto(fd, &status); |
| if (err != 0) { |
| return err; |
| } |
| callbacks->OnTestStatus(status); |
| } else if (tag == 0x12) { // session_status |
| SessionStatus status; |
| err = read_sized_proto(fd, &status); |
| if (err != 0) { |
| return err; |
| } |
| callbacks->OnSessionStatus(status); |
| } else { |
| err = skip_unknown_field(fd, tag, scratch, scratchSize); |
| if (err != 0) { |
| return err; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int |
| run_instrumentation_test(const string& packageName, const string& runner, const string& className, |
| InstrumentationCallbacks* callbacks) |
| { |
| Command cmd("adb"); |
| cmd.AddArg("shell"); |
| cmd.AddArg("am"); |
| cmd.AddArg("instrument"); |
| cmd.AddArg("-w"); |
| cmd.AddArg("-m"); |
| const int classLen = className.length(); |
| if (classLen > 0) { |
| if (classLen > 1 && className[classLen - 1] == '.') { |
| cmd.AddArg("-e"); |
| cmd.AddArg("package"); |
| |
| // "am" actually accepts without removing the last ".", but for cleanlines... |
| cmd.AddArg(className.substr(0, classLen - 1)); |
| } else { |
| cmd.AddArg("-e"); |
| cmd.AddArg("class"); |
| cmd.AddArg(className); |
| } |
| } |
| cmd.AddArg(packageName + "/" + runner); |
| |
| print_command(cmd); |
| |
| int fds[2]; |
| if (0 != pipe(fds)) { |
| return errno; |
| } |
| |
| pid_t pid = fork(); |
| |
| if (pid == -1) { |
| // fork error |
| return errno; |
| } else if (pid == 0) { |
| // child |
| while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} |
| close(fds[1]); |
| close(fds[0]); |
| const char* prog = cmd.GetProg(); |
| char* const* argv = cmd.GetArgv(); |
| char* const* env = cmd.GetEnv(); |
| exec_with_path_search(prog, argv, env); |
| print_error("Unable to run command: %s", prog); |
| exit(1); |
| } else { |
| // parent |
| close(fds[1]); |
| string result; |
| const int size = 16*1024; |
| char* buf = (char*)malloc(size); |
| int err = read_instrumentation_results(fds[0], buf, size, callbacks); |
| free(buf); |
| int status; |
| waitpid(pid, &status, 0); |
| if (err != 0) { |
| return err; |
| } |
| if (WIFEXITED(status)) { |
| return WEXITSTATUS(status); |
| } else { |
| return -1; |
| } |
| } |
| } |
| |
| /** |
| * Get the second to last bundle in the args list. Stores the last name found |
| * in last. If the path is not found or if the args list is empty, returns NULL. |
| */ |
| static const ResultsBundleEntry * |
| find_penultimate_entry(const ResultsBundle& bundle, va_list args) |
| { |
| const ResultsBundle* b = &bundle; |
| const char* arg = va_arg(args, char*); |
| while (arg) { |
| string last = arg; |
| arg = va_arg(args, char*); |
| bool found = false; |
| for (int i=0; i<b->entries_size(); i++) { |
| const ResultsBundleEntry& e = b->entries(i); |
| if (e.key() == last) { |
| if (arg == NULL) { |
| return &e; |
| } else if (e.has_value_bundle()) { |
| b = &e.value_bundle(); |
| found = true; |
| } |
| } |
| } |
| if (!found) { |
| return NULL; |
| } |
| if (arg == NULL) { |
| return NULL; |
| } |
| } |
| return NULL; |
| } |
| |
| string |
| get_bundle_string(const ResultsBundle& bundle, bool* found, ...) |
| { |
| va_list args; |
| va_start(args, found); |
| const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); |
| va_end(args); |
| if (entry == NULL) { |
| *found = false; |
| return string(); |
| } |
| if (entry->has_value_string()) { |
| *found = true; |
| return entry->value_string(); |
| } |
| *found = false; |
| return string(); |
| } |
| |
| int32_t |
| get_bundle_int(const ResultsBundle& bundle, bool* found, ...) |
| { |
| va_list args; |
| va_start(args, found); |
| const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); |
| va_end(args); |
| if (entry == NULL) { |
| *found = false; |
| return 0; |
| } |
| if (entry->has_value_int()) { |
| *found = true; |
| return entry->value_int(); |
| } |
| *found = false; |
| return 0; |
| } |
| |
| float |
| get_bundle_float(const ResultsBundle& bundle, bool* found, ...) |
| { |
| va_list args; |
| va_start(args, found); |
| const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); |
| va_end(args); |
| if (entry == NULL) { |
| *found = false; |
| return 0; |
| } |
| if (entry->has_value_float()) { |
| *found = true; |
| return entry->value_float(); |
| } |
| *found = false; |
| return 0; |
| } |
| |
| double |
| get_bundle_double(const ResultsBundle& bundle, bool* found, ...) |
| { |
| va_list args; |
| va_start(args, found); |
| const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); |
| va_end(args); |
| if (entry == NULL) { |
| *found = false; |
| return 0; |
| } |
| if (entry->has_value_double()) { |
| *found = true; |
| return entry->value_double(); |
| } |
| *found = false; |
| return 0; |
| } |
| |
| int64_t |
| get_bundle_long(const ResultsBundle& bundle, bool* found, ...) |
| { |
| va_list args; |
| va_start(args, found); |
| const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); |
| va_end(args); |
| if (entry == NULL) { |
| *found = false; |
| return 0; |
| } |
| if (entry->has_value_long()) { |
| *found = true; |
| return entry->value_long(); |
| } |
| *found = false; |
| return 0; |
| } |
| |