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);