Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 809d74e9 authored by Felipe Leme's avatar Felipe Leme
Browse files

Added metadata entries to zipped bugreport:

- version.txt: bugreport format version
- main-entry.txt: name of the entry containing the main bugreport (flat file)

Also documented the zip format versions on bugreport-format.txt.

BUG: 26910355
BUG: 26639621

Change-Id: I76b9f6d330c36ad554fae8e691c9ea3ab3c97edd
parent c2a3d7aa
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
# Bugreport file format

This document specifies the format of the bugreport files generated by the
bugreport services (like `bugreport` and `bugreportplus`) and delivered to the
end user (i.e., it doesn’t include other tools like `adb bugreport`).

A _bugreport_ is initially generated by dumpstate, then processed by **Shell**,
which in turn delivers it to the end user through a `ACTION_SEND_MULTIPLE`
intent; the end user then select which app (like an email client) handles such
intent.

## Text file (Pre-M)
Prior to _Android M (Marshmallow)_, `dumpstate` generates a flat .txt file named
_bugreport-DATE.txt_ (where _DATE_ is date the bugreport was generated, in the
format _YYYY-MM-DD-HH-MM-SS_), and Shell simply propagates it as an attachment
in the `ACTION_SEND_MULTIPLE` intent.

## Version v0 (Android M)
On _Android M (Marshmallow)_, dumpstate still generates a flat
_bugreport-DATE.txt_ file, but then **Shell** creates a zip file called
_bugreport-DATE.zip_ containing a _bugreport-DATE.txt_ entry and sends that
file as the `ACTION_SEND_MULTIPLE` attachment.

## Version v1 (Android N)
On _Android N (TBD)_, `dumpstate` generates a zip file directly (unless there
is a failure, in which case it reverts to the flat file that is zipped by
**Shell** and hence the end result is the _v0_ format).

The zip file is by default called _bugreport-DATE.zip_ and it contains a
_bugreport-DATE.txt_ entry, although the end user can change the name (through
**Shell**), in which case they would be called _bugreport-NEW_NAME.zip_ and
_bugreport-NEW_NAME.txt_ respectively.

The zip file also contains 2 metadata entries generated by `dumpstate`:

- `version.txt`:  whose value is **v1**.
- `main-entry.txt`: whose value is the name of the flat text entry (i.e.,
  _bugreport-DATE.txt_ or _bugreport-NEW_NAME.txt_).

`dumpstate` can also copy files from the device’s filesystem into the zip file
under the `FS` folder. For example, a `/dirA/dirB/fileC` file in the device
would generate a `FS/dirA/dirB/fileC` entry in the zip file.

The flat file also has some minor changes:

- Tombstone files were removed and added to the zip file.
- The duration of each section is printed in the report.
- Some dumpsys sections (memory and cpuinfo) are reported earlier in the file.

Besides the files generated by `dumpstate`, **Shell** can also add 2 other
files upon the end user’s request:

- `title.txt`: whose value is a single-line summary of the problem.
- `description.txt`: whose value is a multi-line, detailed description of the problem.

## Intermediate versions
During development, the versions will be suffixed with _-devX_ or
_-devX-EXPERIMENTAL_FEATURE_, where _X_ is a number that increases as the
changes become stable.

For example, the initial version during _Android N_ development was
**v1-dev1**. When `dumpsys` was split in 2 sections but not all tools were
ready to parse that format, the version was named **v1-dev1-dumpsys-split**,
which had to be passed do `dumpsys` explicitly (i.e., trhough a
`-V v1-dev1-dumpsys-split` argument). Once that format became stable and tools
knew how to parse it, the default version became **v1-dev2**.

Similarly, if changes in the file format are made after the initial release of
Android defining that format, then a new _sub-version_ will be used.
For example, if after _Android N_ launches changes are made for the next _N_
release, the version will be called **v1.1** or something like that.

Determining version and main entry
-----------------------------------------------

Tools parsing the zipped bugreport file can use the following algorithm to
determine the bugreport format version and its main entry:

```
If [entries contain "version.txt"]
   version = read("version.txt")
   main_entry = read("main_entry.txt")
else
   version = v0
   main_entry = entries[0]
fi
```
+62 −10
Original line number Diff line number Diff line
@@ -83,6 +83,16 @@ static tombstone_data_t tombstone_data[NUM_TOMBSTONES];
// Root dir for all files copied as-is into the bugreport
const std::string& ZIP_ROOT_DIR = "FS";

/*
 * List of supported zip format versions.
 *
 * See bugreport-format.txt for more info.
 */
// TODO: change to "v1" before final N build
static std::string VERSION_DEFAULT = "v1-dev1";
// TODO: remove before final N build
static std::string VERSION_DUMPSYS_SPLIT = "v1-dev1-dumpsys-split";

/* gets the tombstone data, according to the bugreport type: if zipped gets all tombstones,
 * otherwise gets just those modified in the last half an hour. */
static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) {
@@ -134,7 +144,7 @@ void add_mountinfo() {
    mount_points.clear();
    DurationReporter duration_reporter(title, NULL);
    for_each_pid(do_mountinfo, NULL);
    ALOGD("%s: %d entries added to zip file\n", title, mount_points.size());
    ALOGD("%s: %lu entries added to zip file\n", title, mount_points.size());
}

static void dump_dev_files(const char *title, const char *driverpath, const char *filename)
@@ -325,7 +335,7 @@ static unsigned long logcat_timeout(const char *name) {
/* End copy from system/core/logd/LogBuffer.cpp */

/* dumps the current system state to stdout */
static void print_header() {
static void print_header(std::string version) {
    char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX];
    char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX];
    char network[PROPERTY_VALUE_MAX], date[80];
@@ -352,6 +362,7 @@ static void print_header() {
    printf("Kernel: ");
    dump_file(NULL, "/proc/version");
    printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
    printf("Bugreport format version: %s\n", version.c_str());
    printf("\n");
}

@@ -361,7 +372,8 @@ static bool add_zip_entry_from_fd(const std::string& entry_name, int fd) {
    int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(),
            ZipWriter::kCompress, get_mtime(fd, now));
    if (err) {
        ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err));
        ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(),
                ZipWriter::ErrorCodeString(err));
        return false;
    }

@@ -413,6 +425,32 @@ void add_dir(const char *dir, bool recursive) {
    dump_files(NULL, dir, recursive ? skip_none : is_dir, _add_file_from_fd);
}

/* adds a text entry entry to the existing zip file. */
static bool add_text_zip_entry(const std::string& entry_name, const std::string& content) {
    ALOGD("Adding zip text entry %s (%s)", entry_name.c_str(), content.c_str());
    int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, now);
    if (err) {
        ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(),
                ZipWriter::ErrorCodeString(err));
        return false;
    }

    err = zip_writer->WriteBytes(content.c_str(), content.length());
    if (err) {
        ALOGE("zip_writer->WriteBytes(%s): %s\n", entry_name.c_str(),
                ZipWriter::ErrorCodeString(err));
        return false;
    }

    err = zip_writer->FinishEntry();
    if (err) {
        ALOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
        return false;
    }

    return true;
}

static void dumpstate(const std::string& screenshot_path) {
    DurationReporter duration_reporter("DUMPSTATE");
    unsigned long timeout;
@@ -724,19 +762,20 @@ static void dumpstate(const std::string& screenshot_path) {
}

static void usage() {
    fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]\n"
    fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q] [-B] [-P] [-R] [-V version]\n"
            "  -b: play sound file instead of vibrate, at beginning of job\n"
            "  -e: play sound file instead of vibrate, at end of job\n"
            "  -o: write to file (instead of stdout)\n"
            "  -d: append date to filename (requires -o)\n"
            "  -z: generates zipped file (requires -o)\n"
            "  -p: capture screenshot to filename.png (requires -o)\n"
            "  -z: generates zipped file (requires -o)\n"
            "  -s: write output to control socket (for init)\n"
            "  -b: play sound file instead of vibrate, at beginning of job\n"
            "  -e: play sound file instead of vibrate, at end of job\n"
            "  -q: disable vibrate\n"
            "  -B: send broadcast when finished (requires -o)\n"
            "  -P: send broadacast when started and update system properties on progress (requires -o and -B)\n"
            "  -R: take bugreport in remote mode (requires -o, -z, -d and -B, shouldn't be used with -P)\n"
                );
            "  -V: sets the bugreport format version (%s or %s)\n",
            VERSION_DEFAULT.c_str(), VERSION_DUMPSYS_SPLIT.c_str());
}

static void sigpipe_handler(int n) {
@@ -753,6 +792,9 @@ static bool finish_zip_file(const std::string& bugreport_name, const std::string
        ALOGE("Failed to add text entry to .zip file\n");
        return false;
    }
    if (!add_text_zip_entry("main_entry.txt", bugreport_name)) {
        ALOGE("Failed to add main_entry.txt to .zip file\n");
    }

    int32_t err = zip_writer->Finish();
    if (err) {
@@ -855,6 +897,7 @@ int main(int argc, char *argv[]) {
    int do_broadcast = 0;
    int do_early_screenshot = 0;
    int is_remote_mode = 0;
    std::string version = VERSION_DEFAULT;

    now = time(NULL);

@@ -884,7 +927,7 @@ int main(int argc, char *argv[]) {

    /* parse arguments */
    int c;
    while ((c = getopt(argc, argv, "dho:svqzpPBR")) != -1) {
    while ((c = getopt(argc, argv, "dho:svqzpPBRV:")) != -1) {
        switch (c) {
            case 'd': do_add_date = 1;          break;
            case 'z': do_zip_file = 1;          break;
@@ -896,6 +939,7 @@ int main(int argc, char *argv[]) {
            case 'P': do_update_progress = 1;   break;
            case 'R': is_remote_mode = 1;       break;
            case 'B': do_broadcast = 1;         break;
            case 'V': version = optarg;         break;
            case '?': printf("\n");
            case 'h':
                usage();
@@ -918,6 +962,13 @@ int main(int argc, char *argv[]) {
        exit(1);
    }

    if (version != VERSION_DEFAULT && version != VERSION_DUMPSYS_SPLIT) {
        usage();
        exit(1);
    }

    ALOGI("bugreport format version: %s\n", version.c_str());

    do_early_screenshot = do_update_progress;

    // If we are going to use a socket, do it as early as possible
@@ -983,6 +1034,7 @@ int main(int argc, char *argv[]) {
            } else {
                zip_writer.reset(new ZipWriter(zip_file.get()));
            }
            add_text_zip_entry("version.txt", version);
        }

        if (do_update_progress) {
@@ -1054,7 +1106,7 @@ int main(int argc, char *argv[]) {
    // NOTE: there should be no stdout output until now, otherwise it would break the header.
    // In particular, DurationReport objects should be created passing 'title, NULL', so their
    // duration is logged into ALOG instead.
    print_header();
    print_header(version);

    dumpstate(do_early_screenshot ? "": screenshot_path);