From 2628e9e939fda323fa44c5cb743f4a77b12a312a Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 12 Apr 2016 16:36:51 -0700 Subject: [PATCH] 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 ' 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 --- cmds/bugreport/bugreport.cpp | 5 ++ cmds/bugreportz/Android.mk | 12 +++++ cmds/bugreportz/bugreportz.cpp | 93 ++++++++++++++++++++++++++++++++++ cmds/dumpstate/dumpstate.cpp | 36 +++++++++++-- cmds/dumpstate/dumpstate.h | 3 ++ cmds/dumpstate/dumpstate.rc | 9 ++++ cmds/dumpstate/utils.cpp | 10 +++- 7 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 cmds/bugreportz/Android.mk create mode 100644 cmds/bugreportz/bugreportz.cpp diff --git a/cmds/bugreport/bugreport.cpp b/cmds/bugreport/bugreport.cpp index 6892b57f888..917c8132b77 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 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 00000000000..14ba2259ec3 --- /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 00000000000..b6856bb402d --- /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 +#include +#include +#include +#include + +#include +#include + +// 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 af95d162d5c..3fedef5be28 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -63,6 +63,7 @@ static std::set 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); +static int control_socket_fd; #define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops" @@ -929,15 +930,17 @@ static void dumpstate(const std::string& screenshot_path, const std::string& ver 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 main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { format_args(argc, const_cast(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 @@ int main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { 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 baba0f9541c..c51c79ab98c 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -130,6 +130,9 @@ void update_progress(int weight); /* 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 96232c42897..1f56d210cc0 100644 --- a/cmds/dumpstate/dumpstate.rc +++ b/cmds/dumpstate/dumpstate.rc @@ -9,6 +9,15 @@ service dumpstate /system/bin/dumpstate -s 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 d9738bbdb02..da4b5add3e2 100644 --- a/cmds/dumpstate/utils.cpp +++ b/cmds/dumpstate/utils.cpp @@ -909,8 +909,7 @@ void print_properties() { 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 @@ void redirect_to_socket(FILE *redirect, const char *service) { 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 (path);