Merge "Add debug dump if about to segfault" into nyc-dev
diff --git a/cmds/dumpstate/bugreport-format.md b/cmds/dumpstate/bugreport-format.md
index fc43250..484f97f 100644
--- a/cmds/dumpstate/bugreport-format.md
+++ b/cmds/dumpstate/bugreport-format.md
@@ -41,6 +41,8 @@
 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.
 
+When systrace is enabled, the zip file will contain a `systrace.txt` file as well.
+
 The flat file also has some minor changes:
 
 - Tombstone files were removed and added to the zip file.
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 5898b41..356b68f 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -27,7 +27,6 @@
 #include <stdlib.h>
 #include <string>
 #include <string.h>
-#include <sys/capability.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
@@ -63,6 +62,7 @@
 static std::set<std::string> mount_points;
 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);
 
 #define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
 
@@ -90,7 +90,7 @@
  * See bugreport-format.txt for more info.
  */
 // TODO: change to "v1" before final N build
-static std::string VERSION_DEFAULT = "v1-dev2";
+static std::string VERSION_DEFAULT = "v1-dev3";
 
 /* 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. */
@@ -168,6 +168,42 @@
     closedir(d);
 }
 
+static void dump_systrace() {
+    if (!zip_writer) {
+        MYLOGD("Not dumping systrace because zip_writer is not set\n");
+        return;
+    }
+    const char* path = "/sys/kernel/debug/tracing/tracing_on";
+    long int is_tracing;
+    if (read_file_as_long(path, &is_tracing)) {
+        return; // error already logged
+    }
+    if (is_tracing <= 0) {
+        MYLOGD("Skipping systrace because '%s' content is '%ld'\n", path, is_tracing);
+        return;
+    }
+
+    DurationReporter duration_reporter("SYSTRACE", nullptr);
+    // systrace output can be many MBs, so we need to redirect its stdout straight to the zip file
+    // by forking and using a pipe.
+    int pipefd[2];
+    pipe(pipefd);
+    if (fork() == 0) {
+        close(pipefd[0]);    // close reading end in the child
+        dup2(pipefd[1], STDOUT_FILENO);  // send stdout to the pipe
+        dup2(pipefd[1], STDERR_FILENO);  // send stderr to the pipe
+        close(pipefd[1]);    // this descriptor is no longer needed
+
+        // TODO: ideally it should use run_command, but it doesn't work well with pipes.
+        // The drawback of calling execl directly is that we're not timing out if it hangs.
+        MYLOGD("Running '/system/bin/atrace --async_dump', which can take several seconds");
+        execl("/system/bin/atrace", "/system/bin/atrace", "--async_dump", nullptr);
+    } else {
+        close(pipefd[1]);  // close the write end of the pipe in the parent
+        add_zip_entry_from_fd("systrace.txt", pipefd[0]); // write output to zip file
+    }
+}
+
 static bool skip_not_stat(const char *path) {
     static const char stat[] = "/stat";
     size_t len = strlen(path);
@@ -967,49 +1003,6 @@
     return std::string(hash_buffer);
 }
 
-/* switch to non-root user and group */
-bool drop_root() {
-    /* ensure we will keep capabilities when we drop root */
-    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
-        MYLOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
-        return false;
-    }
-
-    gid_t groups[] = { AID_LOG, AID_SDCARD_R, AID_SDCARD_RW,
-            AID_MOUNT, AID_INET, AID_NET_BW_STATS, AID_READPROC };
-    if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
-        MYLOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
-        return false;
-    }
-    if (setgid(AID_SHELL) != 0) {
-        MYLOGE("Unable to setgid, aborting: %s\n", strerror(errno));
-        return false;
-    }
-    if (setuid(AID_SHELL) != 0) {
-        MYLOGE("Unable to setuid, aborting: %s\n", strerror(errno));
-        return false;
-    }
-
-    struct __user_cap_header_struct capheader;
-    struct __user_cap_data_struct capdata[2];
-    memset(&capheader, 0, sizeof(capheader));
-    memset(&capdata, 0, sizeof(capdata));
-    capheader.version = _LINUX_CAPABILITY_VERSION_3;
-    capheader.pid = 0;
-
-    capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
-    capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
-    capdata[0].inheritable = 0;
-    capdata[1].inheritable = 0;
-
-    if (capset(&capheader, &capdata[0]) < 0) {
-        MYLOGE("capset failed: %s\n", strerror(errno));
-        return false;
-    }
-
-    return true;
-}
-
 int main(int argc, char *argv[]) {
     struct sigaction sigact;
     int do_add_date = 0;
@@ -1252,10 +1245,13 @@
     // duration is logged into MYLOG instead.
     print_header(version);
 
+    // Dumps systrace right away, otherwise it will be filled with unnecessary events.
+    dump_systrace();
+
     // Invoking the following dumpsys calls before dump_traces() to try and
     // keep the system stats as close to its initial state as possible.
-    run_command("DUMPSYS MEMINFO", 30, SU_PATH, "shell", "dumpsys", "meminfo", "-a", NULL);
-    run_command("DUMPSYS CPUINFO", 30, SU_PATH, "shell", "dumpsys", "cpuinfo", "-a", NULL);
+    run_command_as_shell("DUMPSYS MEMINFO", 30, "dumpsys", "meminfo", "-a", NULL);
+    run_command_as_shell("DUMPSYS CPUINFO", 30, "dumpsys", "cpuinfo", "-a", NULL);
 
     /* collect stack traces from Dalvik and native processes (needs root) */
     dump_traces_path = dump_traces();
@@ -1265,7 +1261,7 @@
     add_dir(RECOVERY_DIR, true);
     add_mountinfo();
 
-    if (!drop_root()) {
+    if (!drop_root_user()) {
         return -1;
     }
 
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 288fe39..02d1256 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -85,6 +85,9 @@
 /* prints the contents of a file */
 int dump_file(const char *title, const char *path);
 
+/* saves the the contents of a file as a long */
+int read_file_as_long(const char *path, long int *output);
+
 /* prints the contents of the fd
  * fd must have been opened with the flag O_NONBLOCK.
  */
@@ -100,13 +103,20 @@
         bool (*skip)(const char *path),
         int (*dump_from_fd)(const char *title, const char *path, int fd));
 
+// TODO: need to refactor all those run_command variations; there shold be just one, receiving an
+// optional CommandOptions objects with values such as run_always, drop_root, etc...
+
 /* forks a command and waits for it to finish -- terminate args with NULL */
+int run_command_as_shell(const char *title, int timeout_seconds, const char *command, ...);
 int run_command(const char *title, int timeout_seconds, const char *command, ...);
 
 /* forks a command and waits for it to finish
    first element of args is the command, and last must be NULL.
    command is always ran, even when _DUMPSTATE_DRY_RUN_ is defined. */
-int run_command_always(const char *title, int timeout_seconds, const char *args[]);
+int run_command_always(const char *title, bool drop_root, int timeout_seconds, const char *args[]);
+
+/* switch to non-root user and group */
+bool drop_root_user();
 
 /* sends a broadcast using Activity Manager */
 void send_broadcast(const std::string& action, const std::vector<std::string>& args);
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index d21ef7b..89c9653 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <string>
 #include <string.h>
+#include <sys/capability.h>
 #include <sys/inotify.h>
 #include <sys/stat.h>
 #include <sys/sysconf.h>
@@ -474,6 +475,27 @@
     return _dump_file_from_fd(title, path, fd);
 }
 
+int read_file_as_long(const char *path, long int *output) {
+    int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+    if (fd < 0) {
+        int err = errno;
+        MYLOGE("Error opening file descriptor for %s: %s\n", path, strerror(err));
+        return -1;
+    }
+    char buffer[50];
+    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+    if (bytes_read == -1) {
+        MYLOGE("Error reading file %s: %s\n", path, strerror(errno));
+        return -2;
+    }
+    if (bytes_read == 0) {
+        MYLOGE("File %s is empty\n", path);
+        return -3;
+    }
+    *output = atoi(buffer);
+    return 0;
+}
+
 /* calls skip to gate calling dump_from_fd recursively
  * in the specified directory. dump_from_fd defaults to
  * dump_file_from_fd above when set to NULL. skip defaults
@@ -640,13 +662,48 @@
 
     ON_DRY_RUN({ update_progress(timeout_seconds); va_end(ap); return 0; });
 
-    int status = run_command_always(title, timeout_seconds, args);
+    int status = run_command_always(title, false, timeout_seconds, args);
+    va_end(ap);
+    return status;
+}
+
+int run_command_as_shell(const char *title, int timeout_seconds, const char *command, ...) {
+    DurationReporter duration_reporter(title);
+    fflush(stdout);
+
+    const char *args[1024] = {command};
+    size_t arg;
+    va_list ap;
+    va_start(ap, command);
+    if (title) printf("------ %s (%s", title, command);
+    bool null_terminated = false;
+    for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
+        args[arg] = va_arg(ap, const char *);
+        if (args[arg] == nullptr) {
+            null_terminated = true;
+            break;
+        }
+        if (title) printf(" %s", args[arg]);
+    }
+    if (title) printf(") ------\n");
+    fflush(stdout);
+    if (!null_terminated) {
+        // Fail now, otherwise execvp() call on run_command_always() might hang.
+        std::string cmd;
+        format_args(command, args, &cmd);
+        MYLOGE("skipping command %s because its args were not NULL-terminated", cmd.c_str());
+        return -1;
+    }
+
+    ON_DRY_RUN({ update_progress(timeout_seconds); va_end(ap); return 0; });
+
+    int status = run_command_always(title, true, timeout_seconds, args);
     va_end(ap);
     return status;
 }
 
 /* forks a command and waits for it to finish */
-int run_command_always(const char *title, int timeout_seconds, const char *args[]) {
+int run_command_always(const char *title, bool drop_root, int timeout_seconds, const char *args[]) {
     /* TODO: for now we're simplifying the progress calculation by using the timeout as the weight.
      * It's a good approximation for most cases, except when calling dumpsys, where its weight
      * should be much higher proportionally to its timeout. */
@@ -664,6 +721,10 @@
 
     /* handle child case */
     if (pid == 0) {
+        if (drop_root && !drop_root_user()) {
+            printf("*** could not drop root before running %s: %s\n", command, strerror(errno));
+            _exit(-1);
+        }
 
         /* make sure the child dies when dumpstate dies */
         prctl(PR_SET_PDEATHSIG, SIGKILL);
@@ -676,7 +737,6 @@
 
         execvp(command, (char**) args);
         // execvp's result will be handled after waitpid_with_timeout() below...
-        _exit(-1); // ...but it doesn't hurt to force exit, just in case
     }
 
     /* handle parent case */
@@ -726,14 +786,60 @@
     return status;
 }
 
+bool drop_root_user() {
+    if (getgid() == AID_SHELL && getuid() == AID_SHELL) {
+        MYLOGD("drop_root_user(): already running as Shell");
+        return true;
+    }
+    /* ensure we will keep capabilities when we drop root */
+    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+        MYLOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
+        return false;
+    }
+
+    gid_t groups[] = { AID_LOG, AID_SDCARD_R, AID_SDCARD_RW,
+            AID_MOUNT, AID_INET, AID_NET_BW_STATS, AID_READPROC };
+    if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
+        MYLOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
+        return false;
+    }
+    if (setgid(AID_SHELL) != 0) {
+        MYLOGE("Unable to setgid, aborting: %s\n", strerror(errno));
+        return false;
+    }
+    if (setuid(AID_SHELL) != 0) {
+        MYLOGE("Unable to setuid, aborting: %s\n", strerror(errno));
+        return false;
+    }
+
+    struct __user_cap_header_struct capheader;
+    struct __user_cap_data_struct capdata[2];
+    memset(&capheader, 0, sizeof(capheader));
+    memset(&capdata, 0, sizeof(capdata));
+    capheader.version = _LINUX_CAPABILITY_VERSION_3;
+    capheader.pid = 0;
+
+    capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
+    capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
+    capdata[0].inheritable = 0;
+    capdata[1].inheritable = 0;
+
+    if (capset(&capheader, &capdata[0]) < 0) {
+        MYLOGE("capset failed: %s\n", strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
 void send_broadcast(const std::string& action, const std::vector<std::string>& args) {
     if (args.size() > 1000) {
         MYLOGE("send_broadcast: too many arguments (%d)\n", (int) args.size());
         return;
     }
-    const char *am_args[1024] = { SU_PATH, "shell", "/system/bin/am", "broadcast",
-                                  "--user", "0", "-a", action.c_str() };
-    size_t am_index = 7; // Starts at the index of last initial value above.
+    const char *am_args[1024] = { "/system/bin/am", "broadcast", "--user", "0", "-a",
+                                  action.c_str() };
+    size_t am_index = 5; // Starts at the index of last initial value above.
     for (const std::string& arg : args) {
         am_args[++am_index] = arg.c_str();
     }
@@ -742,7 +848,7 @@
     std::string args_string;
     format_args(am_index + 1, am_args, &args_string);
     MYLOGD("send_broadcast command: %s\n", args_string.c_str());
-    run_command_always(NULL, 5, am_args);
+    run_command_always(NULL, 5, true, am_args);
 }
 
 size_t num_props = 0;
@@ -1081,7 +1187,7 @@
 
 void take_screenshot(const std::string& path) {
     const char *args[] = { "/system/bin/screencap", "-p", path.c_str(), NULL };
-    run_command_always(NULL, 10, args);
+    run_command_always(NULL, false, 10, args);
 }
 
 void vibrate(FILE* vibrator, int ms) {