| /* | |
| * 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("exec-out"); | |
| 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("exec-out"); | |
| 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; | |
| } | |