Added support for 'bugreport -z'.

Dumpstate now supports zipped bugreport, whose output is more complete
than the flat-file bugreports provided prior to N.

The whole workflow is split in different components:

- adb supports a 'bugreport -z <ZIP_FILE>' option, which calls a
  bugreportz binary.
- bugreportz starts the dumpstatez service.
- dumpstatez starts dumpstate with some flags that opens a socket for
  control (not output).
- Once dumpstate is finished, it prints the bugreport location to
  stdout.
- adb pulls the zip file and renames according to the command-line
  argument.
- bugreport prints a deprecation message.

The reason for a new binary (bugreportz) instead of passing arguments to
bugreport (like -z) is backward compatibility: pre-N versions of
bugreport would ignore such argument and generate a text bugreport,
which is not what adb would be expecting.

BUG: 27653204

Change-Id: I47f6f677eba11d5fb54818ae5a0b3cab069776ee
diff --git a/cmds/bugreport/bugreport.cpp b/cmds/bugreport/bugreport.cpp
index 6892b57..917c813 100644
--- a/cmds/bugreport/bugreport.cpp
+++ b/cmds/bugreport/bugreport.cpp
@@ -28,6 +28,11 @@
 // output. All of the dumpstate output is written to stdout, including
 // any errors encountered while reading/writing the output.
 int main() {
+
+  fprintf(stderr, "=============================================================================\n");
+  fprintf(stderr, "WARNING: flat bugreports are deprecated, use adb bugreport <zip_file> instead\n");
+  fprintf(stderr, "=============================================================================\n\n\n");
+
   // Start the dumpstate service.
   property_set("ctl.start", "dumpstate");
 
diff --git a/cmds/bugreportz/Android.mk b/cmds/bugreportz/Android.mk
new file mode 100644
index 0000000..14ba225
--- /dev/null
+++ b/cmds/bugreportz/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= bugreportz.cpp
+
+LOCAL_MODULE:= bugreportz
+
+LOCAL_CFLAGS := -Wall
+
+LOCAL_SHARED_LIBRARIES := libcutils
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/bugreportz/bugreportz.cpp b/cmds/bugreportz/bugreportz.cpp
new file mode 100644
index 0000000..b6856bb
--- /dev/null
+++ b/cmds/bugreportz/bugreportz.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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 <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+#include <cutils/sockets.h>
+
+// TODO: code below was copy-and-pasted from bugreport.cpp (except by the timeout value);
+// should be reused instead.
+int main() {
+
+    // Start the dumpstatez service.
+    property_set("ctl.start", "dumpstatez");
+
+    // Socket will not be available until service starts.
+    int s;
+    for (int i = 0; i < 20; i++) {
+        s = socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
+        if (s >= 0)
+            break;
+        // Try again in 1 second.
+        sleep(1);
+    }
+
+    if (s == -1) {
+        printf("Failed to connect to dumpstatez service: %s\n", strerror(errno));
+        return 1;
+    }
+
+    // Set a timeout so that if nothing is read in 10 minutes, we'll stop
+    // reading and quit. No timeout in dumpstate is longer than 60 seconds,
+    // so this gives lots of leeway in case of unforeseen time outs.
+    struct timeval tv;
+    tv.tv_sec = 10 * 60;
+    tv.tv_usec = 0;
+    if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
+        printf("WARNING: Cannot set socket timeout: %s\n", strerror(errno));
+    }
+
+    while (1) {
+        char buffer[65536];
+        ssize_t bytes_read = TEMP_FAILURE_RETRY(
+                read(s, buffer, sizeof(buffer)));
+        if (bytes_read == 0) {
+            break;
+        } else if (bytes_read == -1) {
+            // EAGAIN really means time out, so change the errno.
+            if (errno == EAGAIN) {
+                errno = ETIMEDOUT;
+            }
+            printf("\nBugreport read terminated abnormally (%s).\n",
+                    strerror(errno));
+            break;
+        }
+
+        ssize_t bytes_to_send = bytes_read;
+        ssize_t bytes_written;
+        do {
+            bytes_written = TEMP_FAILURE_RETRY(
+                    write(STDOUT_FILENO, buffer + bytes_read - bytes_to_send,
+                            bytes_to_send));
+            if (bytes_written == -1) {
+                printf(
+                        "Failed to write data to stdout: read %zd, trying to send %zd (%s)\n",
+                        bytes_read, bytes_to_send, strerror(errno));
+                return 1;
+            }
+            bytes_to_send -= bytes_written;
+        } while (bytes_written != 0 && bytes_to_send > 0);
+    }
+
+    close(s);
+    return 0;
+
+}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index af95d16..3fedef5 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -63,6 +63,7 @@
 void add_mountinfo();
 static bool add_zip_entry(const std::string& entry_name, const std::string& entry_path);
 static bool add_zip_entry_from_fd(const std::string& entry_name, int fd);
+static int control_socket_fd;
 
 #define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
 
@@ -929,15 +930,17 @@
 
 static void usage() {
   fprintf(stderr,
-          "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] "
-          "[-z]] [-s] [-q] [-B] [-P] [-R] [-V version]\n"
+          "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o file [-d] [-p] "
+          "[-z]] [-s] [-S] [-q] [-B] [-P] [-R] [-V version]\n"
+          "  -h: display this help message\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"
+          "  -z: generate zipped file (requires -o)\n"
           "  -s: write output to control socket (for init)\n"
+          "  -S: write file location to control socket (for init; requires -o and -z)"
           "  -q: disable vibrate\n"
           "  -B: send broadcast when finished (requires -o)\n"
           "  -P: send broadcast when started and update system properties on "
@@ -1017,6 +1020,7 @@
     int do_vibrate = 1;
     char* use_outfile = 0;
     int use_socket = 0;
+    int use_control_socket = 0;
     int do_fb = 0;
     int do_broadcast = 0;
     int do_early_screenshot = 0;
@@ -1062,12 +1066,13 @@
     format_args(argc, const_cast<const char **>(argv), &args);
     MYLOGD("Dumpstate command line: %s\n", args.c_str());
     int c;
-    while ((c = getopt(argc, argv, "dho:svqzpPBRV:")) != -1) {
+    while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -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 'S': use_control_socket = 1;   break;
             case 'v': break;  // compatibility no-op
             case 'q': do_vibrate = 0;           break;
             case 'p': do_fb = 1;                break;
@@ -1087,6 +1092,11 @@
         exit(1);
     }
 
+    if (use_control_socket && !do_zip_file) {
+        usage();
+        exit(1);
+    }
+
     if (do_update_progress && !do_broadcast) {
         usage();
         exit(1);
@@ -1112,6 +1122,11 @@
         redirect_to_socket(stdout, "dumpstate");
     }
 
+    if (use_control_socket) {
+        MYLOGD("Opening control socket\n");
+        control_socket_fd = open_socket("dumpstate");
+    }
+
     /* full path of the directory where the bugreport files will be written */
     std::string bugreport_dir;
 
@@ -1351,6 +1366,14 @@
                 path.clear();
             }
         }
+        if (use_control_socket) {
+            if (do_text_file) {
+                dprintf(control_socket_fd, "FAIL:could not create zip file, check %s "
+                        "for more details\n", log_path.c_str());
+            } else {
+                dprintf(control_socket_fd, "OK:%s\n", path.c_str());
+            }
+        }
     }
 
     /* vibrate a few but shortly times to let user know it's finished */
@@ -1398,5 +1421,10 @@
         fclose(stderr);
     }
 
+    if (use_control_socket && control_socket_fd >= 0) {
+        MYLOGD("Closing control socket\n");
+        close(control_socket_fd);
+    }
+
     return 0;
 }
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index baba0f9..c51c79a 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -130,6 +130,9 @@
 /* prints all the system properties */
 void print_properties();
 
+/** opens a socket and returns its file descriptor */
+int open_socket(const char *service);
+
 /* redirect output to a service control socket */
 void redirect_to_socket(FILE *redirect, const char *service);
 
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index 96232c4..1f56d21 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -9,6 +9,15 @@
     disabled
     oneshot
 
+# dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
+# it is finished.
+service dumpstatez /system/bin/dumpstate -S -d -z \
+        -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
+    socket dumpstate stream 0660 shell log
+    class main
+    disabled
+    oneshot
+
 # bugreportplus is an enhanced version of bugreport that provides a better
 # user interface (like displaying progress and allowing user to enter details).
 # It's typically triggered by the power button or developer settings.
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index d9738bb..da4b5ad 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -909,8 +909,7 @@
     printf("\n");
 }
 
-/* redirect output to a service control socket */
-void redirect_to_socket(FILE *redirect, const char *service) {
+int open_socket(const char *service) {
     int s = android_get_control_socket(service);
     if (s < 0) {
         MYLOGE("android_get_control_socket(%s): %s\n", service, strerror(errno));
@@ -930,11 +929,18 @@
         exit(1);
     }
 
+    return fd;
+}
+
+/* redirect output to a service control socket */
+void redirect_to_socket(FILE *redirect, const char *service) {
+    int fd = open_socket(service);
     fflush(redirect);
     dup2(fd, fileno(redirect));
     close(fd);
 }
 
+// TODO: should call is_valid_output_file and/or be merged into it.
 void create_parent_dirs(const char *path) {
     char *chp = const_cast<char *> (path);