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
diff --git a/cmds/dumpstate/bugreport-format.md b/cmds/dumpstate/bugreport-format.md
new file mode 100644
index 0000000..fc43250
--- /dev/null
+++ b/cmds/dumpstate/bugreport-format.md
@@ -0,0 +1,87 @@
+# 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
+```
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index aa7fc55..054db74 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -83,6 +83,16 @@
// 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 @@
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 @@
/* 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 @@
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 @@
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 @@
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 usage() {
- fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]\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"
- " -s: write output to control socket (for init)\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"
+ " -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"
" -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 @@
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 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 @@
/* 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 @@
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 @@
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 @@
} else {
zip_writer.reset(new ZipWriter(zip_file.get()));
}
+ add_text_zip_entry("version.txt", version);
}
if (do_update_progress) {
@@ -1054,7 +1106,7 @@
// 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);