Loading cmds/dumpstate/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -133,6 +133,7 @@ cc_test { name: "dumpstate_test", name: "dumpstate_test", defaults: ["dumpstate_defaults"], defaults: ["dumpstate_defaults"], srcs: [ srcs: [ "dumpstate.cpp", "tests/dumpstate_test.cpp", "tests/dumpstate_test.cpp", ], ], static_libs: ["libgmock"], static_libs: ["libgmock"], Loading cmds/dumpstate/dumpstate.cpp +133 −119 Original line number Original line Diff line number Diff line Loading @@ -1716,131 +1716,140 @@ static void Vibrate(int duration_ms) { // clang-format on // clang-format on } } /** Main entry point for dumpstate. */ int Dumpstate::ParseCommandlineOptions(int argc, char* argv[]) { int run_main(int argc, char* argv[]) { int ret = -1; // success int do_add_date = 0; int do_zip_file = 0; int do_vibrate = 1; char* use_outfile = nullptr; int use_socket = 0; int use_control_socket = 0; int do_fb = 0; int do_broadcast = 0; int is_remote_mode = 0; bool show_header_only = false; bool do_start_service = false; bool telephony_only = false; bool wifi_only = false; int dup_stdout_fd; int dup_stderr_fd; /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we"); if (oom_adj) { fputs("-1000", oom_adj); fclose(oom_adj); } else { /* fallback to kernels <= 2.6.35 */ oom_adj = fopen("/proc/self/oom_adj", "we"); if (oom_adj) { fputs("-17", oom_adj); fclose(oom_adj); } } /* parse arguments */ int c; int c; while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) { switch (c) { switch (c) { // clang-format off // clang-format off case 'd': do_add_date = 1; break; case 'd': options_.do_add_date = true; break; case 'z': do_zip_file = 1; break; case 'z': options_.do_zip_file = true; break; case 'o': use_outfile = optarg; break; case 'o': options_.use_outfile = optarg; break; case 's': use_socket = 1; break; case 's': options_.use_socket = true; break; case 'S': use_control_socket = 1; break; case 'S': options_.use_control_socket = true; break; case 'v': show_header_only = true; break; case 'v': options_.show_header_only = true; break; case 'q': do_vibrate = 0; break; case 'q': options_.do_vibrate = false; break; case 'p': do_fb = 1; break; case 'p': options_.do_fb = true; break; case 'P': ds.update_progress_ = true; break; case 'P': update_progress_ = true; break; case 'R': is_remote_mode = 1; break; case 'R': options_.is_remote_mode = true; break; case 'B': do_broadcast = 1; break; case 'B': options_.do_broadcast = true; break; case 'V': break; // compatibility no-op case 'V': break; // compatibility no-op case 'h': case 'h': ShowUsageAndExit(0); ret = 0; break; break; default: default: fprintf(stderr, "Invalid option: %c\n", c); fprintf(stderr, "Invalid option: %c\n", c); ShowUsageAndExit(); ret = 1; break; // clang-format on // clang-format on } } } } // TODO: use helper function to convert argv into a string // TODO: use helper function to convert argv into a string for (int i = 0; i < argc; i++) { for (int i = 0; i < argc; i++) { ds.args_ += argv[i]; args_ += argv[i]; if (i < argc - 1) { if (i < argc - 1) { ds.args_ += " "; args_ += " "; } } } } ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, ""); // Reset next index used by getopt so this can be called multiple times, for eg, in tests. if (!ds.extra_options_.empty()) { optind = 1; return ret; } // TODO: Move away from system properties when we have binder. void Dumpstate::SetOptionsFromProperties() { extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, ""); if (!extra_options_.empty()) { // Framework uses a system property to override some command-line args. // Framework uses a system property to override some command-line args. // Currently, it contains the type of the requested bugreport. // Currently, it contains the type of the requested bugreport. if (ds.extra_options_ == "bugreportplus") { if (extra_options_ == "bugreportplus") { // Currently, the dumpstate binder is only used by Shell to update progress. // Currently, the dumpstate binder is only used by Shell to update progress. do_start_service = true; options_.do_start_service = true; ds.update_progress_ = true; update_progress_ = true; do_fb = 0; options_.do_fb = false; } else if (ds.extra_options_ == "bugreportremote") { } else if (extra_options_ == "bugreportremote") { do_vibrate = 0; options_.do_vibrate = false; is_remote_mode = 1; options_.is_remote_mode = true; do_fb = 0; options_.do_fb = false; } else if (ds.extra_options_ == "bugreportwear") { } else if (extra_options_ == "bugreportwear") { do_start_service = true; options_.do_start_service = true; ds.update_progress_ = true; update_progress_ = true; do_zip_file = 1; options_.do_zip_file = true; } else if (ds.extra_options_ == "bugreporttelephony") { } else if (extra_options_ == "bugreporttelephony") { telephony_only = true; options_.telephony_only = true; } else if (ds.extra_options_ == "bugreportwifi") { } else if (extra_options_ == "bugreportwifi") { wifi_only = true; options_.wifi_only = true; do_zip_file = 1; options_.do_zip_file = true; } else { } else { MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str()); MYLOGE("Unknown extra option: %s\n", extra_options_.c_str()); } } // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, ""); android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, ""); } } ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, ""); notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, ""); if (!ds.notification_title.empty()) { if (!notification_title.empty()) { // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_TITLE, ""); android::base::SetProperty(PROPERTY_EXTRA_TITLE, ""); ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); if (!ds.notification_description.empty()) { if (!notification_description.empty()) { // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); } } MYLOGD("notification (title: %s, description: %s)\n", MYLOGD("notification (title: %s, description: %s)\n", notification_title.c_str(), ds.notification_title.c_str(), ds.notification_description.c_str()); notification_description.c_str()); } } } if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) { bool Dumpstate::ValidateOptions() { ExitOnInvalidArgs(); if ((options_.do_zip_file || options_.do_add_date || ds.update_progress_ || options_.do_broadcast) && options_.use_outfile.empty()) { return false; } } if (use_control_socket && !do_zip_file) { if (options_.use_control_socket && !options_.do_zip_file) { ExitOnInvalidArgs(); return false; } } if (ds.update_progress_ && !do_broadcast) { if (ds.update_progress_ && !options_.do_broadcast) { ExitOnInvalidArgs(); return false; } if (options_.is_remote_mode && (ds.update_progress_ || !options_.do_broadcast || !options_.do_zip_file || !options_.do_add_date)) { return false; } return true; } } if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) { /* Main entry point for dumpstate. */ int run_main(int argc, char* argv[]) { /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we"); if (oom_adj) { fputs("-1000", oom_adj); fclose(oom_adj); } else { /* fallback to kernels <= 2.6.35 */ oom_adj = fopen("/proc/self/oom_adj", "we"); if (oom_adj) { fputs("-17", oom_adj); fclose(oom_adj); } } int status = ds.ParseCommandlineOptions(argc, argv); if (status != -1) { ShowUsageAndExit(status); } ds.SetOptionsFromProperties(); if (!ds.ValidateOptions()) { ExitOnInvalidArgs(); ExitOnInvalidArgs(); } } Loading @@ -1855,17 +1864,20 @@ int run_main(int argc, char* argv[]) { exit(1); exit(1); } } if (show_header_only) { // TODO: make const reference, but first avoid setting do_zip_file below. Dumpstate::DumpOptions& options = ds.options_; if (options.show_header_only) { ds.PrintHeader(); ds.PrintHeader(); exit(0); exit(0); } } /* redirect output if needed */ // Redirect output if needed bool is_redirecting = !use_socket && use_outfile; bool is_redirecting = !options.use_socket && !options.use_outfile.empty(); // TODO: temporarily set progress until it's part of the Dumpstate constructor // TODO: temporarily set progress until it's part of the Dumpstate constructor std::string stats_path = std::string stats_path = is_redirecting is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile)) ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(options.use_outfile.c_str())) : ""; : ""; ds.progress_.reset(new Progress(stats_path)); ds.progress_.reset(new Progress(stats_path)); Loading @@ -1878,7 +1890,7 @@ int run_main(int argc, char* argv[]) { register_sig_handler(); register_sig_handler(); if (do_start_service) { if (options.do_start_service) { MYLOGI("Starting 'dumpstate' service\n"); MYLOGI("Starting 'dumpstate' service\n"); android::status_t ret; android::status_t ret; if ((ret = android::os::DumpstateService::Start()) != android::OK) { if ((ret = android::os::DumpstateService::Start()) != android::OK) { Loading @@ -1899,23 +1911,24 @@ int run_main(int argc, char* argv[]) { // If we are going to use a socket, do it as early as possible // If we are going to use a socket, do it as early as possible // to avoid timeouts from bugreport. // to avoid timeouts from bugreport. if (use_socket) { if (options.use_socket) { redirect_to_socket(stdout, "dumpstate"); redirect_to_socket(stdout, "dumpstate"); } } if (use_control_socket) { if (options.use_control_socket) { MYLOGD("Opening control socket\n"); MYLOGD("Opening control socket\n"); ds.control_socket_fd_ = open_socket("dumpstate"); ds.control_socket_fd_ = open_socket("dumpstate"); ds.update_progress_ = 1; ds.update_progress_ = 1; } } if (is_redirecting) { if (is_redirecting) { ds.bugreport_dir_ = dirname(use_outfile); ds.bugreport_dir_ = dirname(options.use_outfile.c_str()); std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD"); std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD"); std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE"); std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE"); ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile), ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(options.use_outfile.c_str()), device_name.c_str(), build_id.c_str()); device_name.c_str(), build_id.c_str()); if (do_add_date) { if (options.do_add_date) { char date[80]; char date[80]; strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_)); strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_)); ds.name_ = date; ds.name_ = date; Loading @@ -1923,13 +1936,13 @@ int run_main(int argc, char* argv[]) { ds.name_ = "undated"; ds.name_ = "undated"; } } if (telephony_only) { if (options.telephony_only) { ds.base_name_ += "-telephony"; ds.base_name_ += "-telephony"; } else if (wifi_only) { } else if (options.wifi_only) { ds.base_name_ += "-wifi"; ds.base_name_ += "-wifi"; } } if (do_fb) { if (options.do_fb) { ds.screenshot_path_ = ds.GetPath(".png"); ds.screenshot_path_ = ds.GetPath(".png"); } } ds.tmp_path_ = ds.GetPath(".tmp"); ds.tmp_path_ = ds.GetPath(".tmp"); Loading @@ -1945,14 +1958,14 @@ int run_main(int argc, char* argv[]) { ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str()); ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str()); if (do_zip_file) { if (options.do_zip_file) { ds.path_ = ds.GetPath(".zip"); ds.path_ = ds.GetPath(".zip"); MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str()); MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str()); create_parent_dirs(ds.path_.c_str()); create_parent_dirs(ds.path_.c_str()); ds.zip_file.reset(fopen(ds.path_.c_str(), "wb")); ds.zip_file.reset(fopen(ds.path_.c_str(), "wb")); if (ds.zip_file == nullptr) { if (ds.zip_file == nullptr) { MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno)); MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno)); do_zip_file = 0; options.do_zip_file = false; } else { } else { ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get())); ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get())); } } Loading @@ -1960,7 +1973,7 @@ int run_main(int argc, char* argv[]) { } } if (ds.update_progress_) { if (ds.update_progress_) { if (do_broadcast) { if (options.do_broadcast) { // clang-format off // clang-format off std::vector<std::string> am_args = { std::vector<std::string> am_args = { Loading @@ -1973,7 +1986,7 @@ int run_main(int argc, char* argv[]) { // clang-format on // clang-format on SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args); SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args); } } if (use_control_socket) { if (options.use_control_socket) { dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str()); dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str()); } } } } Loading @@ -1986,11 +1999,11 @@ int run_main(int argc, char* argv[]) { fclose(cmdline); fclose(cmdline); } } if (do_vibrate) { if (options.do_vibrate) { Vibrate(150); Vibrate(150); } } if (do_fb && ds.do_early_screenshot_) { if (options.do_fb && ds.do_early_screenshot_) { if (ds.screenshot_path_.empty()) { if (ds.screenshot_path_.empty()) { // should not have happened // should not have happened MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n"); MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n"); Loading @@ -2000,13 +2013,15 @@ int run_main(int argc, char* argv[]) { } } } } if (do_zip_file) { if (options.do_zip_file) { if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) { if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) { MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(), MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(), strerror(errno)); strerror(errno)); } } } } int dup_stdout_fd; int dup_stderr_fd; if (is_redirecting) { if (is_redirecting) { TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr))); TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr))); redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str())); redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str())); Loading @@ -2033,10 +2048,10 @@ int run_main(int argc, char* argv[]) { // duration is logged into MYLOG instead. // duration is logged into MYLOG instead. ds.PrintHeader(); ds.PrintHeader(); if (telephony_only) { if (options.telephony_only) { DumpstateTelephonyOnly(); DumpstateTelephonyOnly(); ds.DumpstateBoard(); ds.DumpstateBoard(); } else if (wifi_only) { } else if (options.wifi_only) { DumpstateWifiOnly(); DumpstateWifiOnly(); } else { } else { // Dumps systrace right away, otherwise it will be filled with unnecessary events. // Dumps systrace right away, otherwise it will be filled with unnecessary events. Loading Loading @@ -2092,8 +2107,7 @@ int run_main(int argc, char* argv[]) { } } /* rename or zip the (now complete) .tmp file to its final location */ /* rename or zip the (now complete) .tmp file to its final location */ if (use_outfile) { if (!options.use_outfile.empty()) { /* check if user changed the suffix using system properties */ /* check if user changed the suffix using system properties */ std::string name = android::base::GetProperty( std::string name = android::base::GetProperty( android::base::StringPrintf("dumpstate.%d.name", ds.pid_), ""); android::base::StringPrintf("dumpstate.%d.name", ds.pid_), ""); Loading Loading @@ -2122,7 +2136,7 @@ int run_main(int argc, char* argv[]) { } } bool do_text_file = true; bool do_text_file = true; if (do_zip_file) { if (options.do_zip_file) { if (!ds.FinishZipFile()) { if (!ds.FinishZipFile()) { MYLOGE("Failed to finish zip file; sending text bugreport instead\n"); MYLOGE("Failed to finish zip file; sending text bugreport instead\n"); do_text_file = true; do_text_file = true; Loading Loading @@ -2151,7 +2165,7 @@ int run_main(int argc, char* argv[]) { ds.path_.clear(); ds.path_.clear(); } } } } if (use_control_socket) { if (options.use_control_socket) { if (do_text_file) { if (do_text_file) { dprintf(ds.control_socket_fd_, dprintf(ds.control_socket_fd_, "FAIL:could not create zip file, check %s " "FAIL:could not create zip file, check %s " Loading @@ -2164,7 +2178,7 @@ int run_main(int argc, char* argv[]) { } } /* vibrate a few but shortly times to let user know it's finished */ /* vibrate a few but shortly times to let user know it's finished */ if (do_vibrate) { if (options.do_vibrate) { for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) { Vibrate(75); Vibrate(75); usleep((75 + 50) * 1000); usleep((75 + 50) * 1000); Loading @@ -2172,7 +2186,7 @@ int run_main(int argc, char* argv[]) { } } /* tell activity manager we're done */ /* tell activity manager we're done */ if (do_broadcast) { if (options.do_broadcast) { if (!ds.path_.empty()) { if (!ds.path_.empty()) { MYLOGI("Final bugreport path: %s\n", ds.path_.c_str()); MYLOGI("Final bugreport path: %s\n", ds.path_.c_str()); // clang-format off // clang-format off Loading @@ -2186,7 +2200,7 @@ int run_main(int argc, char* argv[]) { "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_ "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_ }; }; // clang-format on // clang-format on if (do_fb) { if (options.do_fb) { am_args.push_back("--es"); am_args.push_back("--es"); am_args.push_back("android.intent.extra.SCREENSHOT"); am_args.push_back("android.intent.extra.SCREENSHOT"); am_args.push_back(ds.screenshot_path_); am_args.push_back(ds.screenshot_path_); Loading @@ -2201,7 +2215,7 @@ int run_main(int argc, char* argv[]) { am_args.push_back(ds.notification_description); am_args.push_back(ds.notification_description); } } } } if (is_remote_mode) { if (options.is_remote_mode) { am_args.push_back("--es"); am_args.push_back("--es"); am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH"); am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH"); am_args.push_back(SHA256_file_hash(ds.path_)); am_args.push_back(SHA256_file_hash(ds.path_)); Loading @@ -2224,7 +2238,7 @@ int run_main(int argc, char* argv[]) { TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr))); TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr))); } } if (use_control_socket && ds.control_socket_fd_ != -1) { if (options.use_control_socket && ds.control_socket_fd_ != -1) { MYLOGD("Closing control socket\n"); MYLOGD("Closing control socket\n"); close(ds.control_socket_fd_); close(ds.control_socket_fd_); } } Loading cmds/dumpstate/dumpstate.h +38 −1 Original line number Original line Diff line number Diff line Loading @@ -290,14 +290,51 @@ class Dumpstate { /* Returns true if the current version supports priority dump feature. */ /* Returns true if the current version supports priority dump feature. */ bool CurrentVersionSupportsPriorityDumps() const; bool CurrentVersionSupportsPriorityDumps() const; // TODO: initialize fields on constructor // TODO: revisit the return values later. /* * Parses commandline arguments and sets runtime options accordingly. * * Returns 0 or positive number if the caller should exit with returned value as * exit code, or returns -1 if caller should proceed with execution. */ int ParseCommandlineOptions(int argc, char* argv[]); /* Sets runtime options from the system properties. */ void SetOptionsFromProperties(); /* Returns true if the options set so far are consistent. */ bool ValidateOptions(); // TODO: add update_progress_ & other options from DumpState. /* * Structure to hold options that determine the behavior of dumpstate. */ struct DumpOptions { bool do_add_date = false; bool do_zip_file = false; bool do_vibrate = true; bool use_socket = false; bool use_control_socket = false; bool do_fb = false; bool do_broadcast = false; bool is_remote_mode = false; bool show_header_only = false; bool do_start_service = false; bool telephony_only = false; bool wifi_only = false; std::string use_outfile; }; // TODO: initialize fields on constructor // dumpstate id - unique after each device reboot. // dumpstate id - unique after each device reboot. uint32_t id_; uint32_t id_; // dumpstate pid // dumpstate pid pid_t pid_; pid_t pid_; // Runtime options. DumpOptions options_; // Whether progress updates should be published. // Whether progress updates should be published. bool update_progress_ = false; bool update_progress_ = false; Loading cmds/dumpstate/tests/dumpstate_test.cpp +154 −0 Original line number Original line Diff line number Diff line Loading @@ -54,6 +54,8 @@ using ::testing::internal::CaptureStdout; using ::testing::internal::GetCapturedStderr; using ::testing::internal::GetCapturedStderr; using ::testing::internal::GetCapturedStdout; using ::testing::internal::GetCapturedStdout; #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) class DumpstateListenerMock : public IDumpstateListener { class DumpstateListenerMock : public IDumpstateListener { public: public: MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress)); MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress)); Loading Loading @@ -144,6 +146,7 @@ class DumpstateTest : public DumpstateBaseTest { ds.progress_.reset(new Progress()); ds.progress_.reset(new Progress()); ds.update_progress_ = false; ds.update_progress_ = false; ds.update_progress_threshold_ = 0; ds.update_progress_threshold_ = 0; ds.options_ = Dumpstate::DumpOptions(); } } // Runs a command and capture `stdout` and `stderr`. // Runs a command and capture `stdout` and `stderr`. Loading Loading @@ -201,6 +204,157 @@ class DumpstateTest : public DumpstateBaseTest { Dumpstate& ds = Dumpstate::GetInstance(); Dumpstate& ds = Dumpstate::GetInstance(); }; }; TEST_F(DumpstateTest, ParseCommandlineOptionsNone) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate") }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_FALSE(ds.options_.do_add_date); EXPECT_FALSE(ds.options_.do_zip_file); EXPECT_EQ("", ds.options_.use_outfile); EXPECT_FALSE(ds.options_.use_socket); EXPECT_FALSE(ds.options_.use_control_socket); EXPECT_FALSE(ds.options_.show_header_only); EXPECT_TRUE(ds.options_.do_vibrate); EXPECT_FALSE(ds.options_.do_fb); EXPECT_FALSE(ds.update_progress_); EXPECT_FALSE(ds.options_.is_remote_mode); EXPECT_FALSE(ds.options_.do_broadcast); } TEST_F(DumpstateTest, ParseCommandlineOptionsPartial1) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-d"), const_cast<char*>("-z"), const_cast<char*>("-o abc"), const_cast<char*>("-s"), const_cast<char*>("-S"), }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_TRUE(ds.options_.do_add_date); EXPECT_TRUE(ds.options_.do_zip_file); // TODO: Maybe we should trim the filename EXPECT_EQ(" abc", std::string(ds.options_.use_outfile)); EXPECT_TRUE(ds.options_.use_socket); EXPECT_TRUE(ds.options_.use_control_socket); // Other options retain default values EXPECT_FALSE(ds.options_.show_header_only); EXPECT_TRUE(ds.options_.do_vibrate); EXPECT_FALSE(ds.options_.do_fb); EXPECT_FALSE(ds.update_progress_); EXPECT_FALSE(ds.options_.is_remote_mode); EXPECT_FALSE(ds.options_.do_broadcast); } TEST_F(DumpstateTest, ParseCommandlineOptionsPartial2) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-v"), const_cast<char*>("-q"), const_cast<char*>("-p"), const_cast<char*>("-P"), const_cast<char*>("-R"), const_cast<char*>("-B"), }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_TRUE(ds.options_.show_header_only); EXPECT_FALSE(ds.options_.do_vibrate); EXPECT_TRUE(ds.options_.do_fb); EXPECT_TRUE(ds.update_progress_); EXPECT_TRUE(ds.options_.is_remote_mode); EXPECT_TRUE(ds.options_.do_broadcast); // Other options retain default values EXPECT_FALSE(ds.options_.do_add_date); EXPECT_FALSE(ds.options_.do_zip_file); EXPECT_EQ("", ds.options_.use_outfile); EXPECT_FALSE(ds.options_.use_socket); EXPECT_FALSE(ds.options_.use_control_socket); } TEST_F(DumpstateTest, ParseCommandlineOptionsHelp) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-h") }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); // -h is for help. Caller exit with code = 0 after printing usage, so expect return = 0. EXPECT_EQ(0, ret); } TEST_F(DumpstateTest, ParseCommandlineOptionsUnknown) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-u") // unknown flag }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); // -u is unknown. Caller exit with code = 1 to show execution failure, after printing usage, // so expect return = 1. EXPECT_EQ(1, ret); } TEST_F(DumpstateTest, ValidateOptionsNeedOutfile1) { ds.options_.do_zip_file = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.use_outfile = "a/b/c"; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsNeedOutfile2) { ds.options_.do_broadcast = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.use_outfile = "a/b/c"; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsNeedZipfile) { ds.options_.use_control_socket = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_zip_file = true; ds.options_.use_outfile = "a/b/c"; // do_zip_file needs outfile EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsUpdateProgressNeedsBroadcast) { ds.update_progress_ = true; ds.options_.use_outfile = "a/b/c"; // update_progress_ needs outfile EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_broadcast = true; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsRemoteMode) { ds.options_.is_remote_mode = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_broadcast = true; ds.options_.do_zip_file = true; ds.options_.do_add_date = true; ds.options_.use_outfile = "a/b/c"; // do_broadcast needs outfile EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, RunCommandNoArgs) { TEST_F(DumpstateTest, RunCommandNoArgs) { EXPECT_EQ(-1, RunCommand("", {})); EXPECT_EQ(-1, RunCommand("", {})); } } Loading Loading
cmds/dumpstate/Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -133,6 +133,7 @@ cc_test { name: "dumpstate_test", name: "dumpstate_test", defaults: ["dumpstate_defaults"], defaults: ["dumpstate_defaults"], srcs: [ srcs: [ "dumpstate.cpp", "tests/dumpstate_test.cpp", "tests/dumpstate_test.cpp", ], ], static_libs: ["libgmock"], static_libs: ["libgmock"], Loading
cmds/dumpstate/dumpstate.cpp +133 −119 Original line number Original line Diff line number Diff line Loading @@ -1716,131 +1716,140 @@ static void Vibrate(int duration_ms) { // clang-format on // clang-format on } } /** Main entry point for dumpstate. */ int Dumpstate::ParseCommandlineOptions(int argc, char* argv[]) { int run_main(int argc, char* argv[]) { int ret = -1; // success int do_add_date = 0; int do_zip_file = 0; int do_vibrate = 1; char* use_outfile = nullptr; int use_socket = 0; int use_control_socket = 0; int do_fb = 0; int do_broadcast = 0; int is_remote_mode = 0; bool show_header_only = false; bool do_start_service = false; bool telephony_only = false; bool wifi_only = false; int dup_stdout_fd; int dup_stderr_fd; /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we"); if (oom_adj) { fputs("-1000", oom_adj); fclose(oom_adj); } else { /* fallback to kernels <= 2.6.35 */ oom_adj = fopen("/proc/self/oom_adj", "we"); if (oom_adj) { fputs("-17", oom_adj); fclose(oom_adj); } } /* parse arguments */ int c; int c; while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) { switch (c) { switch (c) { // clang-format off // clang-format off case 'd': do_add_date = 1; break; case 'd': options_.do_add_date = true; break; case 'z': do_zip_file = 1; break; case 'z': options_.do_zip_file = true; break; case 'o': use_outfile = optarg; break; case 'o': options_.use_outfile = optarg; break; case 's': use_socket = 1; break; case 's': options_.use_socket = true; break; case 'S': use_control_socket = 1; break; case 'S': options_.use_control_socket = true; break; case 'v': show_header_only = true; break; case 'v': options_.show_header_only = true; break; case 'q': do_vibrate = 0; break; case 'q': options_.do_vibrate = false; break; case 'p': do_fb = 1; break; case 'p': options_.do_fb = true; break; case 'P': ds.update_progress_ = true; break; case 'P': update_progress_ = true; break; case 'R': is_remote_mode = 1; break; case 'R': options_.is_remote_mode = true; break; case 'B': do_broadcast = 1; break; case 'B': options_.do_broadcast = true; break; case 'V': break; // compatibility no-op case 'V': break; // compatibility no-op case 'h': case 'h': ShowUsageAndExit(0); ret = 0; break; break; default: default: fprintf(stderr, "Invalid option: %c\n", c); fprintf(stderr, "Invalid option: %c\n", c); ShowUsageAndExit(); ret = 1; break; // clang-format on // clang-format on } } } } // TODO: use helper function to convert argv into a string // TODO: use helper function to convert argv into a string for (int i = 0; i < argc; i++) { for (int i = 0; i < argc; i++) { ds.args_ += argv[i]; args_ += argv[i]; if (i < argc - 1) { if (i < argc - 1) { ds.args_ += " "; args_ += " "; } } } } ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, ""); // Reset next index used by getopt so this can be called multiple times, for eg, in tests. if (!ds.extra_options_.empty()) { optind = 1; return ret; } // TODO: Move away from system properties when we have binder. void Dumpstate::SetOptionsFromProperties() { extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, ""); if (!extra_options_.empty()) { // Framework uses a system property to override some command-line args. // Framework uses a system property to override some command-line args. // Currently, it contains the type of the requested bugreport. // Currently, it contains the type of the requested bugreport. if (ds.extra_options_ == "bugreportplus") { if (extra_options_ == "bugreportplus") { // Currently, the dumpstate binder is only used by Shell to update progress. // Currently, the dumpstate binder is only used by Shell to update progress. do_start_service = true; options_.do_start_service = true; ds.update_progress_ = true; update_progress_ = true; do_fb = 0; options_.do_fb = false; } else if (ds.extra_options_ == "bugreportremote") { } else if (extra_options_ == "bugreportremote") { do_vibrate = 0; options_.do_vibrate = false; is_remote_mode = 1; options_.is_remote_mode = true; do_fb = 0; options_.do_fb = false; } else if (ds.extra_options_ == "bugreportwear") { } else if (extra_options_ == "bugreportwear") { do_start_service = true; options_.do_start_service = true; ds.update_progress_ = true; update_progress_ = true; do_zip_file = 1; options_.do_zip_file = true; } else if (ds.extra_options_ == "bugreporttelephony") { } else if (extra_options_ == "bugreporttelephony") { telephony_only = true; options_.telephony_only = true; } else if (ds.extra_options_ == "bugreportwifi") { } else if (extra_options_ == "bugreportwifi") { wifi_only = true; options_.wifi_only = true; do_zip_file = 1; options_.do_zip_file = true; } else { } else { MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str()); MYLOGE("Unknown extra option: %s\n", extra_options_.c_str()); } } // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, ""); android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, ""); } } ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, ""); notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, ""); if (!ds.notification_title.empty()) { if (!notification_title.empty()) { // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_TITLE, ""); android::base::SetProperty(PROPERTY_EXTRA_TITLE, ""); ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); if (!ds.notification_description.empty()) { if (!notification_description.empty()) { // Reset the property // Reset the property android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, ""); } } MYLOGD("notification (title: %s, description: %s)\n", MYLOGD("notification (title: %s, description: %s)\n", notification_title.c_str(), ds.notification_title.c_str(), ds.notification_description.c_str()); notification_description.c_str()); } } } if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) { bool Dumpstate::ValidateOptions() { ExitOnInvalidArgs(); if ((options_.do_zip_file || options_.do_add_date || ds.update_progress_ || options_.do_broadcast) && options_.use_outfile.empty()) { return false; } } if (use_control_socket && !do_zip_file) { if (options_.use_control_socket && !options_.do_zip_file) { ExitOnInvalidArgs(); return false; } } if (ds.update_progress_ && !do_broadcast) { if (ds.update_progress_ && !options_.do_broadcast) { ExitOnInvalidArgs(); return false; } if (options_.is_remote_mode && (ds.update_progress_ || !options_.do_broadcast || !options_.do_zip_file || !options_.do_add_date)) { return false; } return true; } } if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) { /* Main entry point for dumpstate. */ int run_main(int argc, char* argv[]) { /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we"); if (oom_adj) { fputs("-1000", oom_adj); fclose(oom_adj); } else { /* fallback to kernels <= 2.6.35 */ oom_adj = fopen("/proc/self/oom_adj", "we"); if (oom_adj) { fputs("-17", oom_adj); fclose(oom_adj); } } int status = ds.ParseCommandlineOptions(argc, argv); if (status != -1) { ShowUsageAndExit(status); } ds.SetOptionsFromProperties(); if (!ds.ValidateOptions()) { ExitOnInvalidArgs(); ExitOnInvalidArgs(); } } Loading @@ -1855,17 +1864,20 @@ int run_main(int argc, char* argv[]) { exit(1); exit(1); } } if (show_header_only) { // TODO: make const reference, but first avoid setting do_zip_file below. Dumpstate::DumpOptions& options = ds.options_; if (options.show_header_only) { ds.PrintHeader(); ds.PrintHeader(); exit(0); exit(0); } } /* redirect output if needed */ // Redirect output if needed bool is_redirecting = !use_socket && use_outfile; bool is_redirecting = !options.use_socket && !options.use_outfile.empty(); // TODO: temporarily set progress until it's part of the Dumpstate constructor // TODO: temporarily set progress until it's part of the Dumpstate constructor std::string stats_path = std::string stats_path = is_redirecting is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile)) ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(options.use_outfile.c_str())) : ""; : ""; ds.progress_.reset(new Progress(stats_path)); ds.progress_.reset(new Progress(stats_path)); Loading @@ -1878,7 +1890,7 @@ int run_main(int argc, char* argv[]) { register_sig_handler(); register_sig_handler(); if (do_start_service) { if (options.do_start_service) { MYLOGI("Starting 'dumpstate' service\n"); MYLOGI("Starting 'dumpstate' service\n"); android::status_t ret; android::status_t ret; if ((ret = android::os::DumpstateService::Start()) != android::OK) { if ((ret = android::os::DumpstateService::Start()) != android::OK) { Loading @@ -1899,23 +1911,24 @@ int run_main(int argc, char* argv[]) { // If we are going to use a socket, do it as early as possible // If we are going to use a socket, do it as early as possible // to avoid timeouts from bugreport. // to avoid timeouts from bugreport. if (use_socket) { if (options.use_socket) { redirect_to_socket(stdout, "dumpstate"); redirect_to_socket(stdout, "dumpstate"); } } if (use_control_socket) { if (options.use_control_socket) { MYLOGD("Opening control socket\n"); MYLOGD("Opening control socket\n"); ds.control_socket_fd_ = open_socket("dumpstate"); ds.control_socket_fd_ = open_socket("dumpstate"); ds.update_progress_ = 1; ds.update_progress_ = 1; } } if (is_redirecting) { if (is_redirecting) { ds.bugreport_dir_ = dirname(use_outfile); ds.bugreport_dir_ = dirname(options.use_outfile.c_str()); std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD"); std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD"); std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE"); std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE"); ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile), ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(options.use_outfile.c_str()), device_name.c_str(), build_id.c_str()); device_name.c_str(), build_id.c_str()); if (do_add_date) { if (options.do_add_date) { char date[80]; char date[80]; strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_)); strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_)); ds.name_ = date; ds.name_ = date; Loading @@ -1923,13 +1936,13 @@ int run_main(int argc, char* argv[]) { ds.name_ = "undated"; ds.name_ = "undated"; } } if (telephony_only) { if (options.telephony_only) { ds.base_name_ += "-telephony"; ds.base_name_ += "-telephony"; } else if (wifi_only) { } else if (options.wifi_only) { ds.base_name_ += "-wifi"; ds.base_name_ += "-wifi"; } } if (do_fb) { if (options.do_fb) { ds.screenshot_path_ = ds.GetPath(".png"); ds.screenshot_path_ = ds.GetPath(".png"); } } ds.tmp_path_ = ds.GetPath(".tmp"); ds.tmp_path_ = ds.GetPath(".tmp"); Loading @@ -1945,14 +1958,14 @@ int run_main(int argc, char* argv[]) { ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str()); ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str()); if (do_zip_file) { if (options.do_zip_file) { ds.path_ = ds.GetPath(".zip"); ds.path_ = ds.GetPath(".zip"); MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str()); MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str()); create_parent_dirs(ds.path_.c_str()); create_parent_dirs(ds.path_.c_str()); ds.zip_file.reset(fopen(ds.path_.c_str(), "wb")); ds.zip_file.reset(fopen(ds.path_.c_str(), "wb")); if (ds.zip_file == nullptr) { if (ds.zip_file == nullptr) { MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno)); MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno)); do_zip_file = 0; options.do_zip_file = false; } else { } else { ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get())); ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get())); } } Loading @@ -1960,7 +1973,7 @@ int run_main(int argc, char* argv[]) { } } if (ds.update_progress_) { if (ds.update_progress_) { if (do_broadcast) { if (options.do_broadcast) { // clang-format off // clang-format off std::vector<std::string> am_args = { std::vector<std::string> am_args = { Loading @@ -1973,7 +1986,7 @@ int run_main(int argc, char* argv[]) { // clang-format on // clang-format on SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args); SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args); } } if (use_control_socket) { if (options.use_control_socket) { dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str()); dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str()); } } } } Loading @@ -1986,11 +1999,11 @@ int run_main(int argc, char* argv[]) { fclose(cmdline); fclose(cmdline); } } if (do_vibrate) { if (options.do_vibrate) { Vibrate(150); Vibrate(150); } } if (do_fb && ds.do_early_screenshot_) { if (options.do_fb && ds.do_early_screenshot_) { if (ds.screenshot_path_.empty()) { if (ds.screenshot_path_.empty()) { // should not have happened // should not have happened MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n"); MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n"); Loading @@ -2000,13 +2013,15 @@ int run_main(int argc, char* argv[]) { } } } } if (do_zip_file) { if (options.do_zip_file) { if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) { if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) { MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(), MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(), strerror(errno)); strerror(errno)); } } } } int dup_stdout_fd; int dup_stderr_fd; if (is_redirecting) { if (is_redirecting) { TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr))); TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr))); redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str())); redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str())); Loading @@ -2033,10 +2048,10 @@ int run_main(int argc, char* argv[]) { // duration is logged into MYLOG instead. // duration is logged into MYLOG instead. ds.PrintHeader(); ds.PrintHeader(); if (telephony_only) { if (options.telephony_only) { DumpstateTelephonyOnly(); DumpstateTelephonyOnly(); ds.DumpstateBoard(); ds.DumpstateBoard(); } else if (wifi_only) { } else if (options.wifi_only) { DumpstateWifiOnly(); DumpstateWifiOnly(); } else { } else { // Dumps systrace right away, otherwise it will be filled with unnecessary events. // Dumps systrace right away, otherwise it will be filled with unnecessary events. Loading Loading @@ -2092,8 +2107,7 @@ int run_main(int argc, char* argv[]) { } } /* rename or zip the (now complete) .tmp file to its final location */ /* rename or zip the (now complete) .tmp file to its final location */ if (use_outfile) { if (!options.use_outfile.empty()) { /* check if user changed the suffix using system properties */ /* check if user changed the suffix using system properties */ std::string name = android::base::GetProperty( std::string name = android::base::GetProperty( android::base::StringPrintf("dumpstate.%d.name", ds.pid_), ""); android::base::StringPrintf("dumpstate.%d.name", ds.pid_), ""); Loading Loading @@ -2122,7 +2136,7 @@ int run_main(int argc, char* argv[]) { } } bool do_text_file = true; bool do_text_file = true; if (do_zip_file) { if (options.do_zip_file) { if (!ds.FinishZipFile()) { if (!ds.FinishZipFile()) { MYLOGE("Failed to finish zip file; sending text bugreport instead\n"); MYLOGE("Failed to finish zip file; sending text bugreport instead\n"); do_text_file = true; do_text_file = true; Loading Loading @@ -2151,7 +2165,7 @@ int run_main(int argc, char* argv[]) { ds.path_.clear(); ds.path_.clear(); } } } } if (use_control_socket) { if (options.use_control_socket) { if (do_text_file) { if (do_text_file) { dprintf(ds.control_socket_fd_, dprintf(ds.control_socket_fd_, "FAIL:could not create zip file, check %s " "FAIL:could not create zip file, check %s " Loading @@ -2164,7 +2178,7 @@ int run_main(int argc, char* argv[]) { } } /* vibrate a few but shortly times to let user know it's finished */ /* vibrate a few but shortly times to let user know it's finished */ if (do_vibrate) { if (options.do_vibrate) { for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) { Vibrate(75); Vibrate(75); usleep((75 + 50) * 1000); usleep((75 + 50) * 1000); Loading @@ -2172,7 +2186,7 @@ int run_main(int argc, char* argv[]) { } } /* tell activity manager we're done */ /* tell activity manager we're done */ if (do_broadcast) { if (options.do_broadcast) { if (!ds.path_.empty()) { if (!ds.path_.empty()) { MYLOGI("Final bugreport path: %s\n", ds.path_.c_str()); MYLOGI("Final bugreport path: %s\n", ds.path_.c_str()); // clang-format off // clang-format off Loading @@ -2186,7 +2200,7 @@ int run_main(int argc, char* argv[]) { "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_ "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_ }; }; // clang-format on // clang-format on if (do_fb) { if (options.do_fb) { am_args.push_back("--es"); am_args.push_back("--es"); am_args.push_back("android.intent.extra.SCREENSHOT"); am_args.push_back("android.intent.extra.SCREENSHOT"); am_args.push_back(ds.screenshot_path_); am_args.push_back(ds.screenshot_path_); Loading @@ -2201,7 +2215,7 @@ int run_main(int argc, char* argv[]) { am_args.push_back(ds.notification_description); am_args.push_back(ds.notification_description); } } } } if (is_remote_mode) { if (options.is_remote_mode) { am_args.push_back("--es"); am_args.push_back("--es"); am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH"); am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH"); am_args.push_back(SHA256_file_hash(ds.path_)); am_args.push_back(SHA256_file_hash(ds.path_)); Loading @@ -2224,7 +2238,7 @@ int run_main(int argc, char* argv[]) { TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr))); TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr))); } } if (use_control_socket && ds.control_socket_fd_ != -1) { if (options.use_control_socket && ds.control_socket_fd_ != -1) { MYLOGD("Closing control socket\n"); MYLOGD("Closing control socket\n"); close(ds.control_socket_fd_); close(ds.control_socket_fd_); } } Loading
cmds/dumpstate/dumpstate.h +38 −1 Original line number Original line Diff line number Diff line Loading @@ -290,14 +290,51 @@ class Dumpstate { /* Returns true if the current version supports priority dump feature. */ /* Returns true if the current version supports priority dump feature. */ bool CurrentVersionSupportsPriorityDumps() const; bool CurrentVersionSupportsPriorityDumps() const; // TODO: initialize fields on constructor // TODO: revisit the return values later. /* * Parses commandline arguments and sets runtime options accordingly. * * Returns 0 or positive number if the caller should exit with returned value as * exit code, or returns -1 if caller should proceed with execution. */ int ParseCommandlineOptions(int argc, char* argv[]); /* Sets runtime options from the system properties. */ void SetOptionsFromProperties(); /* Returns true if the options set so far are consistent. */ bool ValidateOptions(); // TODO: add update_progress_ & other options from DumpState. /* * Structure to hold options that determine the behavior of dumpstate. */ struct DumpOptions { bool do_add_date = false; bool do_zip_file = false; bool do_vibrate = true; bool use_socket = false; bool use_control_socket = false; bool do_fb = false; bool do_broadcast = false; bool is_remote_mode = false; bool show_header_only = false; bool do_start_service = false; bool telephony_only = false; bool wifi_only = false; std::string use_outfile; }; // TODO: initialize fields on constructor // dumpstate id - unique after each device reboot. // dumpstate id - unique after each device reboot. uint32_t id_; uint32_t id_; // dumpstate pid // dumpstate pid pid_t pid_; pid_t pid_; // Runtime options. DumpOptions options_; // Whether progress updates should be published. // Whether progress updates should be published. bool update_progress_ = false; bool update_progress_ = false; Loading
cmds/dumpstate/tests/dumpstate_test.cpp +154 −0 Original line number Original line Diff line number Diff line Loading @@ -54,6 +54,8 @@ using ::testing::internal::CaptureStdout; using ::testing::internal::GetCapturedStderr; using ::testing::internal::GetCapturedStderr; using ::testing::internal::GetCapturedStdout; using ::testing::internal::GetCapturedStdout; #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) class DumpstateListenerMock : public IDumpstateListener { class DumpstateListenerMock : public IDumpstateListener { public: public: MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress)); MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress)); Loading Loading @@ -144,6 +146,7 @@ class DumpstateTest : public DumpstateBaseTest { ds.progress_.reset(new Progress()); ds.progress_.reset(new Progress()); ds.update_progress_ = false; ds.update_progress_ = false; ds.update_progress_threshold_ = 0; ds.update_progress_threshold_ = 0; ds.options_ = Dumpstate::DumpOptions(); } } // Runs a command and capture `stdout` and `stderr`. // Runs a command and capture `stdout` and `stderr`. Loading Loading @@ -201,6 +204,157 @@ class DumpstateTest : public DumpstateBaseTest { Dumpstate& ds = Dumpstate::GetInstance(); Dumpstate& ds = Dumpstate::GetInstance(); }; }; TEST_F(DumpstateTest, ParseCommandlineOptionsNone) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate") }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_FALSE(ds.options_.do_add_date); EXPECT_FALSE(ds.options_.do_zip_file); EXPECT_EQ("", ds.options_.use_outfile); EXPECT_FALSE(ds.options_.use_socket); EXPECT_FALSE(ds.options_.use_control_socket); EXPECT_FALSE(ds.options_.show_header_only); EXPECT_TRUE(ds.options_.do_vibrate); EXPECT_FALSE(ds.options_.do_fb); EXPECT_FALSE(ds.update_progress_); EXPECT_FALSE(ds.options_.is_remote_mode); EXPECT_FALSE(ds.options_.do_broadcast); } TEST_F(DumpstateTest, ParseCommandlineOptionsPartial1) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-d"), const_cast<char*>("-z"), const_cast<char*>("-o abc"), const_cast<char*>("-s"), const_cast<char*>("-S"), }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_TRUE(ds.options_.do_add_date); EXPECT_TRUE(ds.options_.do_zip_file); // TODO: Maybe we should trim the filename EXPECT_EQ(" abc", std::string(ds.options_.use_outfile)); EXPECT_TRUE(ds.options_.use_socket); EXPECT_TRUE(ds.options_.use_control_socket); // Other options retain default values EXPECT_FALSE(ds.options_.show_header_only); EXPECT_TRUE(ds.options_.do_vibrate); EXPECT_FALSE(ds.options_.do_fb); EXPECT_FALSE(ds.update_progress_); EXPECT_FALSE(ds.options_.is_remote_mode); EXPECT_FALSE(ds.options_.do_broadcast); } TEST_F(DumpstateTest, ParseCommandlineOptionsPartial2) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-v"), const_cast<char*>("-q"), const_cast<char*>("-p"), const_cast<char*>("-P"), const_cast<char*>("-R"), const_cast<char*>("-B"), }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); EXPECT_EQ(-1, ret); EXPECT_TRUE(ds.options_.show_header_only); EXPECT_FALSE(ds.options_.do_vibrate); EXPECT_TRUE(ds.options_.do_fb); EXPECT_TRUE(ds.update_progress_); EXPECT_TRUE(ds.options_.is_remote_mode); EXPECT_TRUE(ds.options_.do_broadcast); // Other options retain default values EXPECT_FALSE(ds.options_.do_add_date); EXPECT_FALSE(ds.options_.do_zip_file); EXPECT_EQ("", ds.options_.use_outfile); EXPECT_FALSE(ds.options_.use_socket); EXPECT_FALSE(ds.options_.use_control_socket); } TEST_F(DumpstateTest, ParseCommandlineOptionsHelp) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-h") }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); // -h is for help. Caller exit with code = 0 after printing usage, so expect return = 0. EXPECT_EQ(0, ret); } TEST_F(DumpstateTest, ParseCommandlineOptionsUnknown) { // clang-format off char* argv[] = { const_cast<char*>("dumpstate"), const_cast<char*>("-u") // unknown flag }; // clang-format on int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv); // -u is unknown. Caller exit with code = 1 to show execution failure, after printing usage, // so expect return = 1. EXPECT_EQ(1, ret); } TEST_F(DumpstateTest, ValidateOptionsNeedOutfile1) { ds.options_.do_zip_file = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.use_outfile = "a/b/c"; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsNeedOutfile2) { ds.options_.do_broadcast = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.use_outfile = "a/b/c"; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsNeedZipfile) { ds.options_.use_control_socket = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_zip_file = true; ds.options_.use_outfile = "a/b/c"; // do_zip_file needs outfile EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsUpdateProgressNeedsBroadcast) { ds.update_progress_ = true; ds.options_.use_outfile = "a/b/c"; // update_progress_ needs outfile EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_broadcast = true; EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, ValidateOptionsRemoteMode) { ds.options_.is_remote_mode = true; EXPECT_FALSE(ds.ValidateOptions()); ds.options_.do_broadcast = true; ds.options_.do_zip_file = true; ds.options_.do_add_date = true; ds.options_.use_outfile = "a/b/c"; // do_broadcast needs outfile EXPECT_TRUE(ds.ValidateOptions()); } TEST_F(DumpstateTest, RunCommandNoArgs) { TEST_F(DumpstateTest, RunCommandNoArgs) { EXPECT_EQ(-1, RunCommand("", {})); EXPECT_EQ(-1, RunCommand("", {})); } } Loading