Added option (-z) to generate a zipfile with the bugreport.

Currently, dumpstate generates a text file and Shell zips it, but that
breaks the android.intent.action.BUGREPORT_FINISHED contract.

Also changed some string variables from char* to std::string.

BUG: 25563823
Change-Id: I6e61798bf559fa9f79f629b22fd410752da2bbf7
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 6442701..4ab8d3d 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -15,6 +15,8 @@
 LOCAL_MODULE := dumpstate
 
 LOCAL_SHARED_LIBRARIES := libcutils liblog libselinux
+# ZipArchive support, the order matters here to get all symbols.
+LOCAL_STATIC_LIBRARIES := libziparchive libz libbase
 LOCAL_HAL_STATIC_LIBRARIES := libdumpstate
 LOCAL_CFLAGS += -Wall -Wno-unused-parameter -std=gnu99
 LOCAL_INIT_RC := dumpstate.rc
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 1c8db6f..cf02647 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -18,9 +18,11 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <memory>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string>
 #include <string.h>
 #include <sys/capability.h>
 #include <sys/prctl.h>
@@ -30,6 +32,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <base/stringprintf.h>
 #include <cutils/properties.h>
 
 #include "private/android_filesystem_config.h"
@@ -38,12 +41,16 @@
 #include <cutils/log.h>
 
 #include "dumpstate.h"
+#include "ScopedFd.h"
+#include "ziparchive/zip_writer.h"
+
+using android::base::StringPrintf;
 
 /* read before root is shed */
 static char cmdline_buf[16384] = "(unknown)";
 static const char *dump_traces_path = NULL;
 
-static char screenshot_path[PATH_MAX] = "";
+static std::string screenshot_path;
 
 #define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
 
@@ -323,10 +330,11 @@
     for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
     for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
 
-    if (screenshot_path[0]) {
+    if (!screenshot_path.empty()) {
         ALOGI("taking screenshot\n");
-        run_command(NULL, 10, "/system/bin/screencap", "-p", screenshot_path, NULL);
-        ALOGI("wrote screenshot: %s\n", screenshot_path);
+        const char *args[] = { "/system/bin/screencap", "-p", screenshot_path.c_str(), NULL };
+        run_command_always(NULL, 10, args);
+        ALOGI("wrote screenshot: %s\n", screenshot_path.c_str());
     }
 
     // dump_file("EVENT LOG TAGS", "/etc/event-log-tags");
@@ -587,6 +595,7 @@
     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"
             "  -b: play sound file instead of vibrate, at beginning of job\n"
@@ -606,9 +615,71 @@
     fflush(vibrator);
 }
 
+/* generates a zipfile on 'path' with an entry with the contents of 'tmp_path'
+   and removes the temporary file.
+ */
+static bool generate_zip_file(std::string tmp_path, std::string path,
+                             std::string entry_name, time_t entry_time) {
+    std::unique_ptr<FILE, int(*)(FILE*)> file(fopen(path.c_str(), "wb"), fclose);
+    if (!file) {
+        ALOGE("fopen(%s, 'wb'): %s\n", path.c_str(), strerror(errno));
+        return false;
+    }
+
+    ZipWriter writer(file.get());
+    int32_t err = writer.StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, entry_time);
+    if (err) {
+        ALOGE("writer.StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err));
+        return false;
+    }
+
+    ScopedFd fd(TEMP_FAILURE_RETRY(open(tmp_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+    if (fd.get() == -1) {
+        ALOGE("open(%s): %s\n", tmp_path.c_str(), strerror(errno));
+        return false;
+    }
+
+    while (1) {
+        std::vector<uint8_t> buffer(65536);
+        ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd.get(), buffer.data(), sizeof(buffer)));
+        if (bytes_read == 0) {
+            break;
+        } else if (bytes_read == -1) {
+            ALOGE("read(%s): %s\n", tmp_path.c_str(), strerror(errno));
+            return false;
+       }
+       err = writer.WriteBytes(buffer.data(), bytes_read);
+       if (err) {
+           ALOGE("writer.WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
+           return false;
+       }
+    }
+
+    err = writer.FinishEntry();
+    if (err) {
+        ALOGE("writer.FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
+        return false;
+    }
+
+    err = writer.Finish();
+    if (err) {
+        ALOGE("writer.Finish(): %s\n", ZipWriter::ErrorCodeString(err));
+        return false;
+    }
+
+    if (remove(tmp_path.c_str())) {
+        ALOGE("remove(%s): %s\n", tmp_path.c_str(), strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
+
 int main(int argc, char *argv[]) {
     struct sigaction sigact;
     int do_add_date = 0;
+    int do_zip_file = 0;
     int do_vibrate = 1;
     char* use_outfile = 0;
     int use_socket = 0;
@@ -644,6 +715,7 @@
     while ((c = getopt(argc, argv, "dho:svqzpB")) != -1) {
         switch (c) {
             case 'd': do_add_date = 1;       break;
+            case 'z': do_zip_file = 1;       break;
             case 'o': use_outfile = optarg;  break;
             case 's': use_socket = 1;        break;
             case 'v': break;  // compatibility no-op
@@ -657,6 +729,12 @@
         }
     }
 
+    if ((do_zip_file || do_add_date) && !use_outfile) {
+        usage();
+        exit(1);
+    }
+
+
     // If we are going to use a socket, do it as early as possible
     // to avoid timeouts from bugreport.
     if (use_socket) {
@@ -664,17 +742,17 @@
     }
 
     /* open the vibrator before dropping root */
-    FILE *vibrator = 0;
+    std::unique_ptr<FILE, int(*)(FILE*)> vibrator(NULL, fclose);
     if (do_vibrate) {
-        vibrator = fopen("/sys/class/timed_output/vibrator/enable", "we");
+        vibrator.reset(fopen("/sys/class/timed_output/vibrator/enable", "we"));
         if (vibrator) {
-            vibrate(vibrator, 150);
+            vibrate(vibrator.get(), 150);
         }
     }
 
     /* read /proc/cmdline before dropping root */
     FILE *cmdline = fopen("/proc/cmdline", "re");
-    if (cmdline != NULL) {
+    if (cmdline) {
         fgets(cmdline_buf, sizeof(cmdline_buf), cmdline);
         fclose(cmdline);
     }
@@ -725,24 +803,34 @@
     }
 
     /* redirect output if needed */
-    char path[PATH_MAX], tmp_path[PATH_MAX];
+    std::string text_path, zip_path, tmp_path, entry_name;
+
+    /* pointer to the actual path, be it zip or text */
+    std::string path;
+
+    time_t now = time(NULL);
 
     if (!use_socket && use_outfile) {
-        strlcpy(path, use_outfile, sizeof(path));
+        text_path = use_outfile;
         if (do_add_date) {
             char date[80];
-            time_t now = time(NULL);
             strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now));
-            strlcat(path, date, sizeof(path));
+            text_path += date;
         }
         if (do_fb) {
-            strlcpy(screenshot_path, path, sizeof(screenshot_path));
-            strlcat(screenshot_path, ".png", sizeof(screenshot_path));
+            screenshot_path = text_path + ".png";
         }
-        strlcat(path, ".txt", sizeof(path));
-        strlcpy(tmp_path, path, sizeof(tmp_path));
-        strlcat(tmp_path, ".tmp", sizeof(tmp_path));
-        redirect_to_file(stdout, tmp_path);
+        zip_path = text_path + ".zip";
+        text_path += ".txt";
+        tmp_path = text_path + ".tmp";
+        entry_name = basename(text_path.c_str());
+
+        ALOGD("Temporary path: %s\ntext path: %s\nzip path: %s\nzip entry: %s",
+              tmp_path.c_str(), text_path.c_str(), zip_path.c_str(), entry_name.c_str());
+        /* TODO: rather than generating a text file now and zipping it later,
+           it would be more efficient to redirect stdout to the zip entry
+           directly, but the libziparchive doesn't support that option yet. */
+        redirect_to_file(stdout, const_cast<char*>(tmp_path.c_str()));
     }
 
     dumpstate();
@@ -750,10 +838,9 @@
     /* done */
     if (vibrator) {
         for (int i = 0; i < 3; i++) {
-            vibrate(vibrator, 75);
+            vibrate(vibrator.get(), 75);
             usleep((75 + 50) * 1000);
         }
-        fclose(vibrator);
     }
 
     /* close output if needed */
@@ -761,19 +848,40 @@
         fclose(stdout);
     }
 
-    /* rename the (now complete) .tmp file to its final location */
-    if (use_outfile && rename(tmp_path, path)) {
-        fprintf(stderr, "rename(%s, %s): %s\n", tmp_path, path, strerror(errno));
+    /* rename or zip the (now complete) .tmp file to its final location */
+    if (use_outfile) {
+        bool do_text_file = true;
+        if (do_zip_file) {
+            path = zip_path;
+            if (generate_zip_file(tmp_path, zip_path, entry_name, now)) {
+                ALOGE("Failed to generate zip file; sending text bugreport instead\n");
+                do_text_file = true;
+            } else {
+                do_text_file = false;
+            }
+        }
+        if (do_text_file) {
+            path = text_path;
+            if (rename(tmp_path.c_str(), text_path.c_str())) {
+                ALOGE("rename(%s, %s): %s\n", tmp_path.c_str(), text_path.c_str(), strerror(errno));
+                path.clear();
+            }
+        }
     }
 
     /* tell activity manager we're done */
     if (do_broadcast && use_outfile && do_fb) {
-        const char *args[] = { "/system/bin/am", "broadcast", "--user", "0",
-                "-a", "android.intent.action.BUGREPORT_FINISHED",
-                "--es", "android.intent.extra.BUGREPORT", path,
-                "--es", "android.intent.extra.SCREENSHOT", screenshot_path,
-                "--receiver-permission", "android.permission.DUMP", NULL };
-        run_command_always(NULL, 5, args);
+        if (!path.empty()) {
+            ALOGI("Final bugreport path: %s\n", path.c_str());
+            const char *args[] = { "/system/bin/am", "broadcast", "--user", "0",
+                  "-a", "android.intent.action.BUGREPORT_FINISHED",
+                  "--es", "android.intent.extra.BUGREPORT", path.c_str(),
+                  "--es", "android.intent.extra.SCREENSHOT", screenshot_path.c_str(),
+                  "--receiver-permission", "android.permission.DUMP", NULL };
+            run_command_always(NULL, 5, args);
+        } else {
+            ALOGE("Skipping broadcast because bugreport could not be generated\n");
+        }
     }
 
     ALOGI("done\n");