/* * 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.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_t size; if (!in->ReadVarint32(&size)) { fprintf(stderr, "Fail to read size of %s\n", descriptor->name().c_str()); 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 { fprintf(stderr, "Fail to read string of field %s, expect size %d, read %lu\n", field->full_name().c_str(), size, str.size()); fprintf(stderr, "String read \"%s\"\n", str.c_str()); 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 { fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n", tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } case WireFormatLite::WIRETYPE_FIXED64: if (in->ReadLittleEndian64(&value64)) { message->addInt64(fieldId, value64); break; } else { fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n", tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: if (!read_length_delimited(in, fieldId, descriptor, message)) { fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d of field %s\n", tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } break; case WireFormatLite::WIRETYPE_FIXED32: if (in->ReadLittleEndian32(&value32)) { message->addInt32(fieldId, value32); break; } else { fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d of field %s\n", tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } default: fprintf(stderr, "bad tag: 0x%x (%d) at index %d of field %s\n", tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } } } // ================================================================================ static void print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node) { 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 type %d: value32 %d (0x%x)", type, node.value32, node.value32); break; } break; case GenericMessage::TYPE_VALUE64: switch (type) { case FieldDescriptor::TYPE_DOUBLE: out->printf("%f", *(double*)&node.value64); break; // Int32s here were added with addInt64 from a WIRETYPE_VARINT, // even if the definition is for a 32 bit int. case FieldDescriptor::TYPE_SINT32: case FieldDescriptor::TYPE_INT32: out->printf("%d", node.value64); break; case FieldDescriptor::TYPE_INT64: case FieldDescriptor::TYPE_SINT64: case FieldDescriptor::TYPE_SFIXED64: out->printf("%lld", node.value64); break; case FieldDescriptor::TYPE_UINT32: case FieldDescriptor::TYPE_UINT64: case FieldDescriptor::TYPE_FIXED64: out->printf("%u", node.value64); break; case FieldDescriptor::TYPE_BOOL: if (node.value64) { out->printf("true"); } else { out->printf("false"); } break; case FieldDescriptor::TYPE_ENUM: if (field->enum_type()->FindValueByNumber((int)node.value64) == NULL) { out->printf("%lld", (int) node.value64); } else { out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64) ->name().c_str()); } break; default: out->printf("(unexpected type %d: value64 %lld (0x%x))", type, 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("[\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); 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 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, " -r REASON human readable description of why the report is taken.\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* reason = NULL; const char* adbSerial = NULL; pid_t childPid = -1; vector<string> sections; const char* privacy = NULL; int opt; while ((opt = getopt(argc, argv, "bhi:o:r:s:twp:")) != -1) { switch (opt) { case 'b': outputFormat = OUTPUT_PROTO; break; case 'i': inFilename = optarg; break; case 'o': outFilename = optarg; break; case 'r': reason = optarg; break; case 's': adbSerial = optarg; break; case 't': outputFormat = OUTPUT_TEXT; break; case 'h': usage(stdout); return 0; case 'p': privacy = optarg; 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) { // child dup2(pfd[1], STDOUT_FILENO); close(pfd[0]); close(pfd[1]); char const** args = (char const**)malloc(sizeof(char*) * (10 + sections.size())); int argpos = 0; args[argpos++] = "adb"; if (adbSerial != NULL) { args[argpos++] = "-s"; args[argpos++] = adbSerial; } args[argpos++] = "shell"; args[argpos++] = "incident"; if (privacy != NULL) { args[argpos++] = "-p"; args[argpos++] = privacy; } if (reason != NULL) { args[argpos++] = "-r"; args[argpos++] = reason; } 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)); free(args); 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; }