summaryrefslogtreecommitdiff
path: root/tools/incident_report
diff options
context:
space:
mode:
authorJoe Onorato <joeo@google.com>2016-11-21 17:51:35 -0800
committerJoe Onorato <joeo@google.com>2016-12-15 11:23:05 -0800
commit1754d744a7a34731ffc07af1bc3dbfcb06864ab0 (patch)
tree8596241d2c9ccfb1b1748e3a5a37f2befa155a87 /tools/incident_report
parentde5b027d2cbd430ef5007911cd73084c081afaab (diff)
First checkin of incident reporting.
There are a few major pieces here: incidentd --------- This daemon (started by init) runs and accepts incoming requests to take incident reports. When prompted, it calls into various system services and fills in an IncidentProto data structure, and then writes the report into dropbox. The next steps for incidentd: - Security review of SELinux policies. These will be a subset of the dumpstate permissions. Until this is done, incidentd is not started at boot time. incident -------- This shell command calls into incidentd, and can initiate an incident report and either capture the output or leave for dropbox. incident_report --------------- This host side tool can call adb shell with the correct parameters and also format the incident report as text. This formatting code was left of the device on purpose. Right now it's pretty small, but as the number of fields increases, the metadata and code to do the formatting will start to grow. The incident_report command also contains a workaround to let it work before incidentd is turned on by default. Right now, it is implemented to call adb shell dumpsys <service> --proto directly, whereas in the future it will go through the full incidentd flow. incident_section_gen -------------------- A build-time tool that generates a stripped down set of information about the fields that are available. libincident ----------- This library contains the code to connect to incidentd, and the meta proto definitions that are used by the framework protos. The basics are here now, but they are not fully fleshed out yet. The privacy.proto file contains annotations that can go in the proto file that we will later use to filter which fields are uploaded, and which are used by local sources. For example, a device in a test lab is safe to upload much much more information than a real user. These will share the same mechanism, but the user's output will be filtered according to these annotations. frameworks/core/proto --------------------- These .proto files contain the definitions of the system's output. There is one master android.os.IncidentProto file that is the top level of an incident report, but some other services (notification, fingerprint, batterystats, etc) will have others that are used directly by the logging mechanism. Other files which are shared by several of the services also go here, such as ComponentName, Locale, Configuration, etc. There will be many more. There is also a first iplementation of a dump method handling --proto in the fingerprint service. IncidentManager --------------- The java API to trigger an incident report. Test: Not written yet Change-Id: I59568b115ac7fcf73af70c946c95752bf33ae67f
Diffstat (limited to 'tools/incident_report')
-rw-r--r--tools/incident_report/Android.mk39
-rw-r--r--tools/incident_report/formatter.cpp89
-rw-r--r--tools/incident_report/generic_message.cpp70
-rw-r--r--tools/incident_report/generic_message.h71
-rw-r--r--tools/incident_report/main.cpp576
-rw-r--r--tools/incident_report/printer.cpp127
-rw-r--r--tools/incident_report/printer.h44
7 files changed, 1016 insertions, 0 deletions
diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk
new file mode 100644
index 000000000000..ed89bd6251e3
--- /dev/null
+++ b/tools/incident_report/Android.mk
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := incident_report
+
+LOCAL_C_INCLUDES := \
+ external/protobuf/src
+
+LOCAL_SRC_FILES := \
+ generic_message.cpp \
+ main.cpp \
+ printer.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libplatformprotos \
+ libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
+
diff --git a/tools/incident_report/formatter.cpp b/tools/incident_report/formatter.cpp
new file mode 100644
index 000000000000..944348f30022
--- /dev/null
+++ b/tools/incident_report/formatter.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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 "proto_format.h"
+
+#include <string.h>
+
+extern int const PROTO_FORMAT_STRING_POOL_SIZE;
+extern int const PROTO_FORMAT_ENUM_LABELS_LENGTH;
+extern int const PROTO_FORMAT_MESSAGES_LENGTH;
+extern int const PROTO_FORMAT_FIELDS_LENGTH;
+
+extern char const PROTO_FORMAT_STRING_POOL[];
+extern ProtoFieldFormat const PROTO_FORMAT_FIELDS[];
+extern ProtoEnumLabel const PROTO_FORMAT_ENUM_LABELS[];
+extern ProtoMessageFormat const PROTO_FORMAT_MESSAGES[];
+
+static const char*
+get_string(int index)
+{
+ if (index >= 0 && index < PROTO_FORMAT_STRING_POOL_SIZE) {
+ return PROTO_FORMAT_STRING_POOL + index;
+ } else {
+ // These indices all come from within the generated table, so just crash now.
+ *(int*)NULL = 42;
+ return NULL;
+ }
+}
+
+static ProtoMessageFormat const*
+get_message(int index)
+{
+ if (index >= 0 && index < PROTO_FORMAT_MESSAGES_LENGTH) {
+ return PROTO_FORMAT_MESSAGES + index;
+ } else {
+ // These indices all come from within the generated table, so just crash now.
+ *(int*)NULL = 42;
+ return NULL;
+ }
+}
+
+static int
+compare_name(const char* full, const char* package, const char* clazz)
+{
+ int const packageLen = strlen(package);
+ int cmp = strncmp(full, package, packageLen);
+ if (cmp == 0) {
+ cmp = full[packageLen] - '.';
+ if (cmp == 0) {
+ return strcmp(full + packageLen, clazz);
+ }
+ }
+ return cmp;
+}
+
+int
+find_message_index(const char* name)
+{
+ size_t low = 0;
+ size_t high = PROTO_FORMAT_FIELDS_LENGTH - 1;
+
+ while (low <= high) {
+ size_t mid = (low + high) >> 1;
+ ProtoMessageFormat const* msg = get_message(mid);
+
+ int cmp = compare_name(name, get_string(msg->package_name), get_string(msg->package_name));
+ if (cmp < 0) {
+ low = mid + 1;
+ } else if (cmp > 0) {
+ high = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+ return -1;
+}
diff --git a/tools/incident_report/generic_message.cpp b/tools/incident_report/generic_message.cpp
new file mode 100644
index 000000000000..84d9d7cfe7c2
--- /dev/null
+++ b/tools/incident_report/generic_message.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "generic_message.h"
+
+GenericMessage::GenericMessage()
+{
+}
+
+GenericMessage::~GenericMessage()
+{
+}
+
+void
+GenericMessage::addInt32(int32_t fieldId, uint32_t value)
+{
+ Node node;
+ node.type = TYPE_VALUE32;
+ node.value32 = value;
+ mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+void
+GenericMessage::addInt64(int32_t fieldId, uint64_t value)
+{
+ Node node;
+ node.type = TYPE_VALUE64;
+ node.value64 = value;
+ mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+GenericMessage*
+GenericMessage::addMessage(int32_t fieldId)
+{
+ GenericMessage* result = new GenericMessage();
+ Node node;
+ node.type = TYPE_MESSAGE;
+ node.message = result;
+ mNodes.insert(pair<int32_t,Node>(fieldId, node));
+ return result;
+}
+
+void
+GenericMessage::addString(int32_t fieldId, const string& value)
+{
+ Node node;
+ node.type = TYPE_STRING;
+ node.str = new string(value);
+ mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+GenericMessage::const_iterator_pair
+GenericMessage::find(int fieldId) const
+{
+ return mNodes.equal_range(fieldId);
+}
+
diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h
new file mode 100644
index 000000000000..df3f7b22dfc7
--- /dev/null
+++ b/tools/incident_report/generic_message.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef GENERIC_MESSAGE_H
+#define GENERIC_MESSAGE_H
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+/**
+ * Class to represent a protobuf Message, where we don't actually
+ * know what any of the fields are, just their type codes. In other
+ * words, this loslessly stores a parsed protobuf object without
+ * having the .proto file that generated it.
+ */
+class GenericMessage
+{
+public:
+ GenericMessage();
+ ~GenericMessage();
+
+ enum {
+ TYPE_VALUE32,
+ TYPE_VALUE64,
+ TYPE_MESSAGE,
+ TYPE_STRING,
+ TYPE_DATA
+ };
+
+ struct Node {
+ uint32_t type;
+ union {
+ uint32_t value32;
+ uint64_t value64;
+ GenericMessage* message;
+ string* str;
+ string* data;
+ };
+ };
+
+ void addInt32(int32_t fieldId, uint32_t value);
+ void addInt64(int32_t fieldId, uint64_t value);
+ GenericMessage* addMessage(int32_t fieldId);
+ void addString(int32_t fieldId, const string& value);
+
+ typedef multimap<int32_t,Node>::const_iterator const_iterator;
+ typedef pair<const_iterator,const_iterator> const_iterator_pair;
+
+ const_iterator_pair find(int fieldId) const;
+
+private:
+ multimap<int,Node> mNodes;
+};
+
+#endif // GENERIC_MESSAGE_H
+
diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp
new file mode 100644
index 000000000000..a814847e41f7
--- /dev/null
+++ b/tools/incident_report/main.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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 "generic_message.h"
+#include "printer.h"
+
+#include <frameworks/base/core/proto/android/os/incident_proto.pb.h>
+#include <google/protobuf/wire_format.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+using namespace android::os;
+using namespace google::protobuf;
+using namespace google::protobuf::io;
+using namespace google::protobuf::internal;
+
+static bool read_message(CodedInputStream* in, Descriptor const* descriptor,
+ GenericMessage* message);
+static void print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message);
+
+// ================================================================================
+static bool
+read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor,
+ GenericMessage* message)
+{
+ uint32 size;
+ if (!in->ReadVarint32(&size)) {
+ return false;
+ }
+
+ FieldDescriptor const* field = descriptor->FindFieldByNumber(fieldId);
+ if (field != NULL) {
+ int type = field->type();
+ if (type == FieldDescriptor::TYPE_MESSAGE) {
+ GenericMessage* child = message->addMessage(fieldId);
+
+ CodedInputStream::Limit limit = in->PushLimit(size);
+ bool rv = read_message(in, field->message_type(), child);
+ in->PopLimit(limit);
+ return rv;
+ } else if (type == FieldDescriptor::TYPE_STRING) {
+ // TODO: do a version of readstring that just pumps the data
+ // rather than allocating a string which we don't care about.
+ string str;
+ if (in->ReadString(&str, size)) {
+ message->addString(fieldId, str);
+ return true;
+ } else {
+ return false;
+ }
+ } else if (type == FieldDescriptor::TYPE_BYTES) {
+ // TODO: Save bytes field.
+ return in->Skip(size);
+ }
+ }
+ return in->Skip(size);
+}
+
+// ================================================================================
+static bool
+read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message)
+{
+ uint32 value32;
+ uint64 value64;
+
+ while (true) {
+ uint32 tag = in->ReadTag();
+ if (tag == 0) {
+ return true;
+ }
+ int fieldId = WireFormatLite::GetTagFieldNumber(tag);
+ switch (WireFormatLite::GetTagWireType(tag)) {
+ case WireFormatLite::WIRETYPE_VARINT:
+ if (in->ReadVarint64(&value64)) {
+ message->addInt64(fieldId, value64);
+ break;
+ } else {
+ return false;
+ }
+ case WireFormatLite::WIRETYPE_FIXED64:
+ if (in->ReadLittleEndian64(&value64)) {
+ message->addInt64(fieldId, value64);
+ break;
+ } else {
+ return false;
+ }
+ case WireFormatLite::WIRETYPE_LENGTH_DELIMITED:
+ if (!read_length_delimited(in, fieldId, descriptor, message)) {
+ return false;
+ }
+ break;
+ case WireFormatLite::WIRETYPE_FIXED32:
+ if (in->ReadLittleEndian32(&value32)) {
+ message->addInt32(fieldId, value32);
+ break;
+ } else {
+ return false;
+ }
+ default:
+ fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag,
+ in->CurrentPosition());
+ return false;
+ }
+ }
+}
+
+// ================================================================================
+static void
+print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node)
+{
+ uint32_t val32;
+ FieldDescriptor::Type type = field->type();
+
+ switch (node.type) {
+ case GenericMessage::TYPE_VALUE32:
+ switch (type) {
+ case FieldDescriptor::TYPE_FIXED32:
+ out->printf("%u", node.value32);
+ break;
+ case FieldDescriptor::TYPE_SFIXED32:
+ out->printf("%d", node.value32);
+ break;
+ case FieldDescriptor::TYPE_FLOAT:
+ out->printf("%f", *(float*)&node.value32);
+ break;
+ default:
+ out->printf("(unexpected value %d (0x%x)", node.value32, node.value32);
+ break;
+ }
+ break;
+ case GenericMessage::TYPE_VALUE64:
+ switch (type) {
+ case FieldDescriptor::TYPE_FIXED64:
+ case FieldDescriptor::TYPE_SFIXED64:
+ case FieldDescriptor::TYPE_DOUBLE:
+ out->printf("%f", *(double*)&node.value64);
+ break;
+ case FieldDescriptor::TYPE_SINT32:
+ case FieldDescriptor::TYPE_INT32:
+ val32 = (uint32_t)node.value32;
+ out->printf("%d", val32);
+ break;
+ case FieldDescriptor::TYPE_INT64:
+ case FieldDescriptor::TYPE_UINT32:
+ val32 = (uint32_t)node.value32;
+ out->printf("%u", val32);
+ break;
+ case FieldDescriptor::TYPE_UINT64:
+ case FieldDescriptor::TYPE_SINT64:
+ case FieldDescriptor::TYPE_BOOL:
+ if (node.value64) {
+ out->printf("true");
+ } else {
+ out->printf("false");
+ }
+ break;
+ case FieldDescriptor::TYPE_ENUM:
+ default:
+ out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64);
+ break;
+ }
+ break;
+ case GenericMessage::TYPE_MESSAGE:
+ print_message(out, field->message_type(), node.message);
+ break;
+ case GenericMessage::TYPE_STRING:
+ // TODO: custom format for multi-line strings.
+ out->printf("%s", node.str->c_str());
+ break;
+ case GenericMessage::TYPE_DATA:
+ out->printf("<bytes>");
+ break;
+ }
+}
+
+static void
+print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message)
+{
+ out->printf("%s {\n", descriptor->name().c_str());
+ out->indent();
+
+ int const N = descriptor->field_count();
+ for (int i=0; i<N; i++) {
+ FieldDescriptor const* field = descriptor->field(i);
+
+ int fieldId = field->number();
+ bool repeated = field->label() == FieldDescriptor::LABEL_REPEATED;
+ FieldDescriptor::Type type = field->type();
+ GenericMessage::const_iterator_pair it = message->find(fieldId);
+
+ out->printf("%s=", field->name().c_str());
+ if (repeated) {
+ if (it.first != it.second) {
+ out->printf("[");
+ if (type == FieldDescriptor::TYPE_MESSAGE
+ || type == FieldDescriptor::TYPE_STRING
+ || type == FieldDescriptor::TYPE_BYTES) {
+ out->printf("\n");
+ }
+ out->indent();
+
+ for (GenericMessage::const_iterator_pair it = message->find(fieldId);
+ it.first != it.second; it.first++) {
+ print_value(out, field, it.first->second);
+ if (type == FieldDescriptor::TYPE_MESSAGE
+ || type == FieldDescriptor::TYPE_STRING
+ || type == FieldDescriptor::TYPE_BYTES) {
+ out->printf("\n");
+ }
+ }
+
+ out->dedent();
+ out->printf("]");
+ } else {
+ out->printf("[]");
+ }
+ } else {
+ if (it.first != it.second) {
+ print_value(out, field, it.first->second);
+ } else {
+ switch (type) {
+ case FieldDescriptor::TYPE_BOOL:
+ out->printf("false");
+ break;
+ case FieldDescriptor::TYPE_STRING:
+ case FieldDescriptor::TYPE_MESSAGE:
+ out->printf("");
+ break;
+ case FieldDescriptor::TYPE_ENUM:
+ out->printf("%s", field->default_value_enum()->name().c_str());
+ break;
+ default:
+ out->printf("0");
+ break;
+ }
+ }
+ }
+ out->printf("\n");
+ }
+ out->dedent();
+ out->printf("}");
+}
+
+// ================================================================================
+static uint8_t*
+write_raw_varint(uint8_t* buf, uint32_t val)
+{
+ uint8_t* p = buf;
+ while (true) {
+ if ((val & ~0x7F) == 0) {
+ *p++ = (uint8_t)val;
+ return p;
+ } else {
+ *p++ = (uint8_t)((val & 0x7F) | 0x80);
+ val >>= 7;
+ }
+ }
+}
+
+static int
+write_all(int fd, uint8_t const* buf, size_t size)
+{
+ while (size > 0) {
+ ssize_t amt = ::write(fd, buf, size);
+ if (amt < 0) {
+ return errno;
+ }
+ size -= amt;
+ buf += amt;
+ }
+ return 0;
+}
+
+static int
+adb_incident_workaround(const char* adbSerial, const vector<string>& sections)
+{
+ const int maxAllowedSize = 20 * 1024 * 1024; // 20MB
+ uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize);
+
+ for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) {
+ Descriptor const* descriptor = IncidentProto::descriptor();
+ FieldDescriptor const* field;
+
+ // Get the name and field id.
+ string name = *it;
+ char* end;
+ int id = strtol(name.c_str(), &end, 0);
+ if (*end == '\0') {
+ // If it's an id, find out the string.
+ field = descriptor->FindFieldByNumber(id);
+ if (field == NULL) {
+ fprintf(stderr, "Unable to find field number: %d\n", id);
+ return 1;
+ }
+ name = field->name();
+ } else {
+ // If it's a string, find out the id.
+ field = descriptor->FindFieldByName(name);
+ if (field == NULL) {
+ fprintf(stderr, "Unable to find field: %s\n", name.c_str());
+ return 1;
+ }
+ id = field->number();
+ }
+
+ int pfd[2];
+ if (pipe(pfd) != 0) {
+ fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+ return 1;
+ }
+
+ pid_t pid = fork();
+ if (pid == -1) {
+ fprintf(stderr, "fork failed: %s\n", strerror(errno));
+ return 1;
+ } else if (pid == 0) {
+ // child
+ dup2(pfd[1], STDOUT_FILENO);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ char const** args = (char const**)malloc(sizeof(char*) * 8);
+ int argpos = 0;
+ args[argpos++] = "adb";
+ if (adbSerial != NULL) {
+ args[argpos++] = "-s";
+ args[argpos++] = adbSerial;
+ }
+ args[argpos++] = "shell";
+ args[argpos++] = "dumpsys";
+ args[argpos++] = name.c_str();
+ args[argpos++] = "--proto";
+ args[argpos++] = NULL;
+ execvp(args[0], (char*const*)args);
+ fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+ return 1;
+ } else {
+ // parent
+ close(pfd[1]);
+
+ size_t size = 0;
+ while (size < maxAllowedSize) {
+ ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size);
+ if (amt == 0) {
+ break;
+ } else if (amt == -1) {
+ fprintf(stderr, "read error: %s\n", strerror(errno));
+ return 1;
+ }
+ size += amt;
+ }
+
+ int status;
+ do {
+ waitpid(pid, &status, 0);
+ } while (!WIFEXITED(status));
+ if (WEXITSTATUS(status) != 0) {
+ return WEXITSTATUS(status);
+ }
+
+ if (size > 0) {
+ uint8_t header[20];
+ uint8_t* p = write_raw_varint(header, (id << 3) | 2);
+ p = write_raw_varint(p, size);
+ int err = write_all(STDOUT_FILENO, header, p-header);
+ if (err != 0) {
+ fprintf(stderr, "write error: %s\n", strerror(err));
+ return 1;
+ }
+ err = write_all(STDOUT_FILENO, buffer, size);
+ if (err != 0) {
+ fprintf(stderr, "write error: %s\n", strerror(err));
+ return 1;
+ }
+ }
+
+ close(pfd[0]);
+ }
+ }
+
+ return 0;
+}
+
+// ================================================================================
+static void
+usage(FILE* out)
+{
+ fprintf(out, "usage: incident_report -i INPUT [-o OUTPUT]\n");
+ fprintf(out, "\n");
+ fprintf(out, "Pretty-prints an incident report protobuf file.\n");
+ fprintf(out, " -i INPUT the input file. INPUT may be '-' to use stdin\n");
+ fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: incident_report [-o OUTPUT] [-t|b] [-s SERIAL] [SECTION...]\n");
+ fprintf(out, "\n");
+ fprintf(out, "Take an incident report over adb (which must be in the PATH).\n");
+ fprintf(out, " -b output the incident report raw protobuf format\n");
+ fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n");
+ fprintf(out, " -s SERIAL sent to adb to choose which device, instead of $ANDROID_SERIAL\n");
+ fprintf(out, " -t output the incident report in pretty-printed text format\n");
+ fprintf(out, "\n");
+ fprintf(out, " SECTION which bugreport sections to print, either the int code of the\n");
+ fprintf(out, " section in the Incident proto or the field name. If ommited,\n");
+ fprintf(out, " the report will contain all fields\n");
+ fprintf(out, "\n");
+}
+
+int
+main(int argc, char** argv)
+{
+ enum { OUTPUT_TEXT, OUTPUT_PROTO } outputFormat = OUTPUT_TEXT;
+ const char* inFilename = NULL;
+ const char* outFilename = NULL;
+ const char* adbSerial = NULL;
+ bool adbIncidentWorkaround = true;
+ pid_t childPid = -1;
+ vector<string> sections;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "bhi:o:s:tw")) != -1) {
+ switch (opt) {
+ case 'b':
+ outputFormat = OUTPUT_PROTO;
+ break;
+ case 'i':
+ inFilename = optarg;
+ break;
+ case 'o':
+ outFilename = optarg;
+ break;
+ case 's':
+ adbSerial = optarg;
+ break;
+ case 't':
+ outputFormat = OUTPUT_TEXT;
+ break;
+ case 'h':
+ usage(stdout);
+ return 0;
+ case 'w':
+ adbIncidentWorkaround = false;
+ break;
+ default:
+ usage(stderr);
+ return 1;
+ }
+ }
+
+ while (optind < argc) {
+ sections.push_back(argv[optind++]);
+ }
+
+ int inFd;
+ if (inFilename != NULL) {
+ // translate-only mode - oepn the file or use stdin.
+ if (strcmp("-", inFilename) == 0) {
+ inFd = STDIN_FILENO;
+ } else {
+ inFd = open(inFilename, O_RDONLY | O_CLOEXEC);
+ if (inFd < 0) {
+ fprintf(stderr, "unable to open file for read (%s): %s\n", strerror(errno),
+ inFilename);
+ return 1;
+ }
+ }
+ } else {
+ // pipe mode - run adb shell incident ...
+ int pfd[2];
+ if (pipe(pfd) != 0) {
+ fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+ return 1;
+ }
+
+ childPid = fork();
+ if (childPid == -1) {
+ fprintf(stderr, "fork failed: %s\n", strerror(errno));
+ return 1;
+ } else if (childPid == 0) {
+ dup2(pfd[1], STDOUT_FILENO);
+ close(pfd[0]);
+ close(pfd[1]);
+ // child
+ if (adbIncidentWorkaround) {
+ // TODO: Until the device side incident command is checked in,
+ // the incident_report builds the outer Incident proto by hand
+ // from individual adb shell dumpsys <service> --proto calls,
+ // with a maximum allowed output size.
+ return adb_incident_workaround(adbSerial, sections);
+ }
+
+ // TODO: This is what the real implementation will be...
+ char const** args = (char const**)malloc(sizeof(char*) * (6 + sections.size()));
+ int argpos = 0;
+ args[argpos++] = "adb";
+ if (adbSerial != NULL) {
+ args[argpos++] = "-s";
+ args[argpos++] = adbSerial;
+ }
+ args[argpos++] = "shell";
+ args[argpos++] = "incident";
+ for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) {
+ args[argpos++] = it->c_str();
+ }
+ args[argpos++] = NULL;
+ execvp(args[0], (char*const*)args);
+ fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+ return 0;
+ } else {
+ // parent
+ inFd = pfd[0];
+ close(pfd[1]);
+ }
+ }
+
+ int outFd;
+ if (outFilename == NULL || strcmp("-", outFilename) == 0) {
+ outFd = STDOUT_FILENO;
+ } else {
+ outFd = open(outFilename, O_CREAT | O_RDWR, 0666);
+ if (outFd < 0) {
+ fprintf(stderr, "unable to open file for write: %s\n", outFilename);
+ return 1;
+ }
+ }
+
+ GenericMessage message;
+
+ Descriptor const* descriptor = IncidentProto::descriptor();
+ FileInputStream infile(inFd);
+ CodedInputStream in(&infile);
+
+ if (!read_message(&in, descriptor, &message)) {
+ fprintf(stderr, "unable to read incident\n");
+ return 1;
+ }
+
+ Out out(outFd);
+
+ print_message(&out, descriptor, &message);
+ out.printf("\n");
+
+ if (childPid != -1) {
+ int status;
+ do {
+ waitpid(childPid, &status, 0);
+ } while (!WIFEXITED(status));
+ if (WEXITSTATUS(status) != 0) {
+ return WEXITSTATUS(status);
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp
new file mode 100644
index 000000000000..1ab6bd87913e
--- /dev/null
+++ b/tools/incident_report/printer.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "printer.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define INITIAL_BUF_SIZE (16*1024)
+
+char const* SPACES = " ";
+const int SPACE_COUNT = strlen(SPACES);
+
+Out::Out(int fd)
+ :mOut(fd == STDOUT_FILENO ? stdout : fdopen(fd, "w")),
+ mBufSize(INITIAL_BUF_SIZE),
+ mBuf((char*)malloc(INITIAL_BUF_SIZE)),
+ mIndent(0),
+ mPendingIndent(false)
+{
+}
+
+Out::~Out()
+{
+ fclose(mOut);
+}
+
+int
+Out::reallocate(int size)
+{
+ if (size > mBufSize) {
+ char* p = (char*)malloc(size);
+ if (p != NULL) {
+ free(mBuf);
+ mBufSize = size;
+ mBuf = p;
+ return size;
+ }
+ }
+ return mBufSize;
+}
+
+void
+Out::printf(const char* format, ...)
+{
+ if (mPendingIndent) {
+ print_indent();
+ mPendingIndent = false;
+ }
+
+ int len;
+
+ va_list args;
+ va_start(args, format);
+
+ len = vsnprintf(mBuf, mBufSize, format, args);
+ bool truncated = (len >= mBufSize) && (reallocate(len) < len);
+
+ len = vsnprintf(mBuf, mBufSize, format, args);
+ va_end(args);
+
+ if (len > 0) {
+ if (mIndent == 0) {
+ fwrite(mBuf, len, 1, mOut);
+ } else {
+ char* last = mBuf;
+ char* p;
+ do {
+ p = strchr(last, '\n');
+ int size = p != NULL ? p - last + 1 : strlen(last);
+ fwrite(last, size, 1, mOut);
+ if (p != NULL) {
+ if (p[1] == '\0') {
+ mPendingIndent = true;
+ } else {
+ print_indent();
+ }
+ }
+ last = p+1;
+ } while (p != NULL);
+ }
+ }
+}
+
+void
+Out::indent()
+{
+ mPendingIndent = true;
+ mIndent += 2;
+}
+
+void
+Out::dedent()
+{
+ if (mIndent > 0) {
+ mIndent -= 2;
+ }
+}
+
+void
+Out::print_indent()
+{
+#if 0
+ fprintf(mOut, "[%d]", mIndent);
+#else
+ int indent = mIndent;
+ while (indent > SPACE_COUNT) {
+ fwrite(SPACES, SPACE_COUNT, 1, mOut);
+ indent -= SPACE_COUNT;
+ }
+ fwrite(SPACES + SPACE_COUNT - indent, indent, 1, mOut);
+#endif
+}
diff --git a/tools/incident_report/printer.h b/tools/incident_report/printer.h
new file mode 100644
index 000000000000..ed93fa19542c
--- /dev/null
+++ b/tools/incident_report/printer.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef PRINTER_H
+#define PRINTER_H
+
+#include <stdio.h>
+
+class Out
+{
+public:
+ Out(int fd);
+ ~Out();
+
+ void printf(const char* format, ...);
+
+ void indent();
+ void dedent();
+
+private:
+ FILE* mOut;
+ int mBufSize;
+ char* mBuf;
+ int mIndent;
+ bool mPendingIndent;
+
+ int reallocate(int size);
+ void print_indent();
+};
+
+#endif // PRINTER_H