/*
* 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;
}