You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
16 KiB
455 lines
16 KiB
/*
|
|
* 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;
|
|
}
|