Loading cmds/dumpstate/Android.bp +2 −0 Original line number Original line Diff line number Diff line Loading @@ -89,6 +89,7 @@ cc_defaults { "android.hardware.dumpstate@1.0", "android.hardware.dumpstate@1.0", "android.hardware.dumpstate@1.1", "android.hardware.dumpstate@1.1", "android.hardware.dumpstate-V1-ndk", "android.hardware.dumpstate-V1-ndk", "android.os.flags-aconfig-cc-host", "libziparchive", "libziparchive", "libbase", "libbase", "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries Loading @@ -99,6 +100,7 @@ cc_defaults { "libdumpstateaidl", "libdumpstateaidl", "libdumpstateutil", "libdumpstateutil", "libdumputils", "libdumputils", "libgui", "libhardware_legacy", "libhardware_legacy", "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries "liblog", "liblog", Loading cmds/dumpstate/binder/android/os/IDumpstate.aidl +3 −0 Original line number Original line Diff line number Diff line Loading @@ -61,6 +61,9 @@ interface IDumpstate { // Keep bugreport stored after retrieval. // Keep bugreport stored after retrieval. const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4; const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4; // Capture multi-display screenshots. const int BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT = 0x8; /** /** * Speculatively pre-dumps UI data for a bugreport request that might come later. * Speculatively pre-dumps UI data for a bugreport request that might come later. * * Loading cmds/dumpstate/dumpstate.cpp +101 −18 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/os/IIncidentCompanion.h> #include <android/os/IIncidentCompanion.h> #include <android_app_admin_flags.h> #include <android_app_admin_flags.h> #include <android_os.h> #include <android_tracing.h> #include <android_tracing.h> #include <binder/IServiceManager.h> #include <binder/IServiceManager.h> #include <cutils/multiuser.h> #include <cutils/multiuser.h> Loading @@ -48,6 +49,7 @@ #include <dumputils/dump_utils.h> #include <dumputils/dump_utils.h> #include <errno.h> #include <errno.h> #include <fcntl.h> #include <fcntl.h> #include <gui/SurfaceComposerClient.h> #include <hardware_legacy/power.h> #include <hardware_legacy/power.h> #include <hidl/ServiceManagement.h> #include <hidl/ServiceManagement.h> #include <inttypes.h> #include <inttypes.h> Loading Loading @@ -113,10 +115,12 @@ using android::Dumpsys; using android::INVALID_OPERATION; using android::INVALID_OPERATION; using android::IServiceManager; using android::IServiceManager; using android::OK; using android::OK; using android::PhysicalDisplayId; using android::sp; using android::sp; using android::status_t; using android::status_t; using android::String16; using android::String16; using android::String8; using android::String8; using android::SurfaceComposerClient; using android::TIMED_OUT; using android::TIMED_OUT; using android::UNKNOWN_ERROR; using android::UNKNOWN_ERROR; using android::Vector; using android::Vector; Loading Loading @@ -798,6 +802,9 @@ android::binder::Status Dumpstate::ConsentCallback::onReportApproved() { ds.options_->screenshot_fd.get()); ds.options_->screenshot_fd.get()); if (copy_succeeded) { if (copy_succeeded) { android::os::UnlinkAndLogOnError(ds.screenshot_path_); android::os::UnlinkAndLogOnError(ds.screenshot_path_); for (auto& [_, path] : ds.screenshot_path_by_display_id_) { android::os::UnlinkAndLogOnError(path); } } else { } else { MYLOGE("Failed to copy screenshot to a permanent file.\n"); MYLOGE("Failed to copy screenshot to a permanent file.\n"); copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH, copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH, Loading Loading @@ -883,6 +890,12 @@ static const std::set<std::string> PROBLEMATIC_FILE_EXTENSIONS = { status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, std::chrono::milliseconds timeout = 0ms) { std::chrono::milliseconds timeout = 0ms) { return AddZipEntryFromFd(zip_writer_, entry_name, fd, timeout); } status_t Dumpstate::AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, int fd, std::chrono::milliseconds timeout = 0ms) { std::string valid_name = entry_name; std::string valid_name = entry_name; // Rename extension if necessary. // Rename extension if necessary. Loading @@ -899,21 +912,20 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, // Logging statement below is useful to time how long each entry takes, but it's too verbose. // Logging statement below is useful to time how long each entry takes, but it's too verbose. // MYLOGD("Adding zip entry %s\n", entry_name.c_str()); // MYLOGD("Adding zip entry %s\n", entry_name.c_str()); size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; int32_t err = zip_writer_->StartEntryWithTime(valid_name.c_str(), flags, int32_t err = zip_writer->StartEntryWithTime(valid_name.c_str(), flags, get_mtime(fd, ds.now_)); get_mtime(fd, ds.now_)); if (err != 0) { if (err != 0) { MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", valid_name.c_str(), MYLOGE("zip_writer->StartEntryWithTime(%s): %s\n", valid_name.c_str(), ZipWriter::ErrorCodeString(err)); ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } bool finished_entry = false; bool finished_entry = false; auto finish_entry = [this, &finished_entry] { auto finish_entry = [&zip_writer, &finished_entry] { if (!finished_entry) { if (!finished_entry) { // This should only be called when we're going to return an earlier error, // This should only be called when we're going to return an earlier error, // which would've been logged. This may imply the file is already corrupt // which would've been logged. This may imply the file is already corrupt // and any further logging from FinishEntry is more likely to mislead than // and any further logging from FinishEntry is more likely to mislead than // not. // not. this->zip_writer_->FinishEntry(); zip_writer->FinishEntry(); } } }; }; auto scope_guard = android::base::make_scope_guard(finish_entry); auto scope_guard = android::base::make_scope_guard(finish_entry); Loading Loading @@ -950,17 +962,17 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno)); MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno)); return -errno; return -errno; } } err = zip_writer_->WriteBytes(buffer.data(), bytes_read); err = zip_writer->WriteBytes(buffer.data(), bytes_read); if (err) { if (err) { MYLOGE("zip_writer_->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err)); MYLOGE("zip_writer->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } } } err = zip_writer_->FinishEntry(); err = zip_writer->FinishEntry(); finished_entry = true; finished_entry = true; if (err != 0) { if (err != 0) { MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); MYLOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } Loading @@ -968,6 +980,11 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, } } bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) { bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) { return AddZipEntry(zip_writer_, entry_name, entry_path); } bool Dumpstate::AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, const std::string& entry_path) { android::base::unique_fd fd( android::base::unique_fd fd( TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC))); TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC))); if (fd == -1) { if (fd == -1) { Loading @@ -975,7 +992,7 @@ bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& en return false; return false; } } return (AddZipEntryFromFd(entry_name, fd.get()) == OK); return (AddZipEntryFromFd(zip_writer, entry_name, fd.get()) == OK); } } /* adds a file to the existing zipped bugreport */ /* adds a file to the existing zipped bugreport */ Loading Loading @@ -2765,13 +2782,14 @@ void Dumpstate::DumpstateBoard(int out_fd) { static void ShowUsage() { static void ShowUsage() { fprintf(stderr, fprintf(stderr, "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] " "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] [-m] " "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n" "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n" " -h: display this help message\n" " -h: display this help message\n" " -b: play sound file instead of vibrate, at beginning of job\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" " -e: play sound file instead of vibrate, at end of job\n" " -o: write to custom directory (only in limited mode)\n" " -o: write to custom directory (only in limited mode)\n" " -p: capture screenshot to filename.png\n" " -p: capture screenshot to filename.png\n" " -m: capture screenshots of all displays to filename.zip\n" " -s: write zipped file to control socket (for init)\n" " -s: write zipped file to control socket (for init)\n" " -S: write file location to control socket (for init)\n" " -S: write file location to control socket (for init)\n" " -q: disable vibrate\n" " -q: disable vibrate\n" Loading Loading @@ -2899,8 +2917,21 @@ static bool PrepareToWriteToFile() { } } if (ds.options_->do_screenshot) { if (ds.options_->do_screenshot) { if (android::os::bugreport_multi_display_screenshot_enabled() && ds.options_->multi_display_screenshot) { ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-screenshots-zip.tmp" : "-screenshots.zip"); std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); for (PhysicalDisplayId display_id : ids) { std::string id = android::to_string(display_id); std::string path = ds.GetPath("-" + id + ".png"); ds.screenshot_path_by_display_id_.insert(std::make_pair(id, path)); } } else { ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-png.tmp" : ".png"); ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-png.tmp" : ".png"); } } } ds.tmp_path_ = ds.GetPath(".tmp"); ds.tmp_path_ = ds.GetPath(".tmp"); ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt"); ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt"); Loading Loading @@ -3034,15 +3065,14 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt static void LogDumpOptions(const Dumpstate::DumpOptions& options) { static void LogDumpOptions(const Dumpstate::DumpOptions& options) { MYLOGI( MYLOGI( "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d " "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d " "is_remote_mode: %d show_header_only: %d telephony_only: %d " "multi_display_screenshot: %d is_remote_mode: %d show_header_only: %d telephony_only: %d " "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s " "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s " "limited_only: %d args: %s\n", "limited_only: %d args: %s\n", options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket, options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket, options.do_screenshot, options.is_remote_mode, options.show_header_only, options.do_screenshot, options.multi_display_screenshot, options.is_remote_mode, options.telephony_only, options.wifi_only, options.show_header_only, options.telephony_only, options.wifi_only, options.do_progress_updates, options.bugreport_fd.get(), options.do_progress_updates, options.bugreport_fd.get(), options.bugreport_mode_string.c_str(), options.bugreport_mode_string.c_str(), options.limited_only, options.args.c_str()); options.limited_only, options.args.c_str()); } } void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, Loading @@ -3053,6 +3083,8 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, bool skip_user_consent) { bool skip_user_consent) { this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; this->multi_display_screenshot = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT; this->skip_user_consent = skip_user_consent; this->skip_user_consent = skip_user_consent; // Duplicate the fds because the passed in fds don't outlive the binder transaction. // Duplicate the fds because the passed in fds don't outlive the binder transaction. bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); Loading @@ -3064,7 +3096,7 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) { Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) { RunStatus status = RunStatus::OK; RunStatus status = RunStatus::OK; int c; int c; while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpmLPBRSV:w")) != -1) { switch (c) { switch (c) { // clang-format off // clang-format off case 'o': out_dir = optarg; break; case 'o': out_dir = optarg; break; Loading @@ -3073,6 +3105,7 @@ Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) case 'v': show_header_only = true; break; case 'v': show_header_only = true; break; case 'q': do_vibrate = false; break; case 'q': do_vibrate = false; break; case 'p': do_screenshot = true; break; case 'p': do_screenshot = true; break; case 'm': multi_display_screenshot = true; break; case 'P': do_progress_updates = true; break; case 'P': do_progress_updates = true; break; case 'R': is_remote_mode = true; break; case 'R': is_remote_mode = true; break; case 'L': limited_only = true; break; case 'L': limited_only = true; break; Loading Loading @@ -3717,6 +3750,9 @@ bool Dumpstate::CalledByApi() const { void Dumpstate::CleanupTmpFiles() { void Dumpstate::CleanupTmpFiles() { android::os::UnlinkAndLogOnError(tmp_path_); android::os::UnlinkAndLogOnError(tmp_path_); android::os::UnlinkAndLogOnError(screenshot_path_); android::os::UnlinkAndLogOnError(screenshot_path_); for (auto& [_, path] : screenshot_path_by_display_id_) { android::os::UnlinkAndLogOnError(path); } android::os::UnlinkAndLogOnError(path_); android::os::UnlinkAndLogOnError(path_); if (dump_traces_path != nullptr) { if (dump_traces_path != nullptr) { android::os::UnlinkAndLogOnError(dump_traces_path); android::os::UnlinkAndLogOnError(dump_traces_path); Loading Loading @@ -4712,6 +4748,15 @@ void Dumpstate::UpdateProgress(int32_t delta_sec) { } } void Dumpstate::TakeScreenshot(const std::string& path) { void Dumpstate::TakeScreenshot(const std::string& path) { if (android::os::bugreport_multi_display_screenshot_enabled() && ds.options_->multi_display_screenshot) { TakeMultiDisplayScreenshots(path); } else { TakeSingleDisplayScreenshot(path); } } void Dumpstate::TakeSingleDisplayScreenshot(const std::string& path) { const std::string& real_path = path.empty() ? screenshot_path_ : path; const std::string& real_path = path.empty() ? screenshot_path_ : path; int status = int status = RunCommand("", {"screencap", "-p", real_path}, RunCommand("", {"screencap", "-p", real_path}, Loading @@ -4728,6 +4773,44 @@ void Dumpstate::TakeScreenshot(const std::string& path) { } } } } void Dumpstate::TakeMultiDisplayScreenshots(const std::string& path) { const std::string& real_path = path.empty() ? screenshot_path_ : path; FILE* zipFile = fopen(real_path.c_str(), "wb"); std::unique_ptr<ZipWriter> zip_writer = std::make_unique<ZipWriter>(zipFile); bool success = false; for (auto& [display_id, screenshot_path] : ds.screenshot_path_by_display_id_) { int status = RunCommand( "", {"/system/bin/screencap", "-p", screenshot_path, "-d", display_id}, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); if (status == 0) { MYLOGD("Screenshot for display id %s saved on %s\n", display_id.c_str(), screenshot_path.c_str()); size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; std::filesystem::path p(screenshot_path); std::string filename = p.filename().string(); AddZipEntry(zip_writer, filename, screenshot_path); success = true; } else { MYLOGE("Failed to take screenshot for display id %s on %s\n", display_id.c_str(), screenshot_path.c_str()); } } zip_writer->Finish(); fclose(zipFile); if (listener_ != nullptr) { // Show a visual indication to indicate screenshot is taken via // IDumpstateListener.onScreenshotTaken() listener_->onScreenshotTaken(success); } } bool is_dir(const char* pathname) { bool is_dir(const char* pathname) { struct stat info; struct stat info; if (stat(pathname, &info) == -1) { if (stat(pathname, &info) == -1) { Loading cmds/dumpstate/dumpstate.h +19 −5 Original line number Original line Diff line number Diff line Loading @@ -209,10 +209,11 @@ class Dumpstate { enum BugreportFlag { enum BugreportFlag { BUGREPORT_USE_PREDUMPED_UI_DATA = BUGREPORT_USE_PREDUMPED_UI_DATA = android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, BUGREPORT_FLAG_DEFER_CONSENT = BUGREPORT_FLAG_DEFER_CONSENT = android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT, android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT, BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL, BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT = android::os::IDumpstate::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT }; }; static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; Loading Loading @@ -410,6 +411,7 @@ class Dumpstate { // Writes generation progress updates to a socket. // Writes generation progress updates to a socket. bool progress_updates_to_socket = false; bool progress_updates_to_socket = false; bool do_screenshot = false; bool do_screenshot = false; bool multi_display_screenshot = false; bool is_screenshot_copied = false; bool is_screenshot_copied = false; bool is_consent_deferred = false; bool is_consent_deferred = false; bool skip_user_consent = false; bool skip_user_consent = false; Loading Loading @@ -513,6 +515,9 @@ class Dumpstate { // Full path of the file containing the screenshot (when requested). // Full path of the file containing the screenshot (when requested). std::string screenshot_path_; std::string screenshot_path_; // Full paths of the files containing the screenshots for multi displays (when requested). std::map<std::string, std::string> screenshot_path_by_display_id_; // Pointer to the zipped file. // Pointer to the zipped file. std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose}; std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose}; Loading Loading @@ -572,6 +577,15 @@ class Dumpstate { RunStatus DumpstateDefaultAfterCritical(); RunStatus DumpstateDefaultAfterCritical(); RunStatus dumpstate(); RunStatus dumpstate(); bool AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, const std::string& entry_path); android::status_t AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, int fd, std::chrono::milliseconds timeout); void TakeSingleDisplayScreenshot(const std::string& path = ""); void TakeMultiDisplayScreenshots(const std::string& path = ""); void MaybeTakeEarlyScreenshot(); void MaybeTakeEarlyScreenshot(); void MaybeSavePlaceholderScreenshot(); void MaybeSavePlaceholderScreenshot(); std::future<std::string> MaybeSnapshotSystemTraceAsync(); std::future<std::string> MaybeSnapshotSystemTraceAsync(); Loading cmds/dumpstate/tests/dumpstate_test.cpp +40 −0 Original line number Original line Diff line number Diff line Loading @@ -1096,6 +1096,7 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileCreated) { std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-png.tmp" : ".png"); std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-png.tmp" : ".png"); EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; android::base::RemoveFileIfExists(screenshot); } } TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { Loading @@ -1114,6 +1115,45 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { EXPECT_FALSE(std::filesystem::exists(screenshot)) << screenshot << " was created."; EXPECT_FALSE(std::filesystem::exists(screenshot)) << screenshot << " was created."; } } TEST_F(ZippedBugReportStreamTest, ScreenShotZipFileCreated) { std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip"; android::base::unique_fd out_fd; CreateFd(out_path, &out_fd); ds_.options_->limited_only = true; ds_.options_->stream_to_socket = true; ds_.options_->do_screenshot = false; ds_.options_->multi_display_screenshot = true; RedirectOutputToFd(out_fd); GenerateBugreport(); std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip"); EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; android::base::RemoveFileIfExists(screenshot); } TEST_F(ZippedBugReportStreamTest, ScreenShotsAreIncludedInScreenShotZipFile) { std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip"; android::base::unique_fd out_fd; CreateFd(out_path, &out_fd); ds_.options_->limited_only = true; ds_.options_->stream_to_socket = true; ds_.options_->do_screenshot = false; ds_.options_->multi_display_screenshot = true; RedirectOutputToFd(out_fd); GenerateBugreport(); std::string screenshot_path = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip"); OpenArchive(screenshot_path.c_str(), &handle_); ZipEntry entry; for (auto& [_, path] : ds_.screenshot_path_by_display_id_) { std::filesystem::path p(path); VerifyEntry(handle_, p.filename().string(), &entry); } android::base::RemoveFileIfExists(screenshot_path); } class ProgressTest : public DumpstateBaseTest { class ProgressTest : public DumpstateBaseTest { public: public: Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { Loading Loading
cmds/dumpstate/Android.bp +2 −0 Original line number Original line Diff line number Diff line Loading @@ -89,6 +89,7 @@ cc_defaults { "android.hardware.dumpstate@1.0", "android.hardware.dumpstate@1.0", "android.hardware.dumpstate@1.1", "android.hardware.dumpstate@1.1", "android.hardware.dumpstate-V1-ndk", "android.hardware.dumpstate-V1-ndk", "android.os.flags-aconfig-cc-host", "libziparchive", "libziparchive", "libbase", "libbase", "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries Loading @@ -99,6 +100,7 @@ cc_defaults { "libdumpstateaidl", "libdumpstateaidl", "libdumpstateutil", "libdumpstateutil", "libdumputils", "libdumputils", "libgui", "libhardware_legacy", "libhardware_legacy", "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries "liblog", "liblog", Loading
cmds/dumpstate/binder/android/os/IDumpstate.aidl +3 −0 Original line number Original line Diff line number Diff line Loading @@ -61,6 +61,9 @@ interface IDumpstate { // Keep bugreport stored after retrieval. // Keep bugreport stored after retrieval. const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4; const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4; // Capture multi-display screenshots. const int BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT = 0x8; /** /** * Speculatively pre-dumps UI data for a bugreport request that might come later. * Speculatively pre-dumps UI data for a bugreport request that might come later. * * Loading
cmds/dumpstate/dumpstate.cpp +101 −18 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/os/IIncidentCompanion.h> #include <android/os/IIncidentCompanion.h> #include <android_app_admin_flags.h> #include <android_app_admin_flags.h> #include <android_os.h> #include <android_tracing.h> #include <android_tracing.h> #include <binder/IServiceManager.h> #include <binder/IServiceManager.h> #include <cutils/multiuser.h> #include <cutils/multiuser.h> Loading @@ -48,6 +49,7 @@ #include <dumputils/dump_utils.h> #include <dumputils/dump_utils.h> #include <errno.h> #include <errno.h> #include <fcntl.h> #include <fcntl.h> #include <gui/SurfaceComposerClient.h> #include <hardware_legacy/power.h> #include <hardware_legacy/power.h> #include <hidl/ServiceManagement.h> #include <hidl/ServiceManagement.h> #include <inttypes.h> #include <inttypes.h> Loading Loading @@ -113,10 +115,12 @@ using android::Dumpsys; using android::INVALID_OPERATION; using android::INVALID_OPERATION; using android::IServiceManager; using android::IServiceManager; using android::OK; using android::OK; using android::PhysicalDisplayId; using android::sp; using android::sp; using android::status_t; using android::status_t; using android::String16; using android::String16; using android::String8; using android::String8; using android::SurfaceComposerClient; using android::TIMED_OUT; using android::TIMED_OUT; using android::UNKNOWN_ERROR; using android::UNKNOWN_ERROR; using android::Vector; using android::Vector; Loading Loading @@ -798,6 +802,9 @@ android::binder::Status Dumpstate::ConsentCallback::onReportApproved() { ds.options_->screenshot_fd.get()); ds.options_->screenshot_fd.get()); if (copy_succeeded) { if (copy_succeeded) { android::os::UnlinkAndLogOnError(ds.screenshot_path_); android::os::UnlinkAndLogOnError(ds.screenshot_path_); for (auto& [_, path] : ds.screenshot_path_by_display_id_) { android::os::UnlinkAndLogOnError(path); } } else { } else { MYLOGE("Failed to copy screenshot to a permanent file.\n"); MYLOGE("Failed to copy screenshot to a permanent file.\n"); copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH, copy_succeeded = android::os::CopyFileToFd(DEFAULT_SCREENSHOT_PATH, Loading Loading @@ -883,6 +890,12 @@ static const std::set<std::string> PROBLEMATIC_FILE_EXTENSIONS = { status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, std::chrono::milliseconds timeout = 0ms) { std::chrono::milliseconds timeout = 0ms) { return AddZipEntryFromFd(zip_writer_, entry_name, fd, timeout); } status_t Dumpstate::AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, int fd, std::chrono::milliseconds timeout = 0ms) { std::string valid_name = entry_name; std::string valid_name = entry_name; // Rename extension if necessary. // Rename extension if necessary. Loading @@ -899,21 +912,20 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, // Logging statement below is useful to time how long each entry takes, but it's too verbose. // Logging statement below is useful to time how long each entry takes, but it's too verbose. // MYLOGD("Adding zip entry %s\n", entry_name.c_str()); // MYLOGD("Adding zip entry %s\n", entry_name.c_str()); size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; int32_t err = zip_writer_->StartEntryWithTime(valid_name.c_str(), flags, int32_t err = zip_writer->StartEntryWithTime(valid_name.c_str(), flags, get_mtime(fd, ds.now_)); get_mtime(fd, ds.now_)); if (err != 0) { if (err != 0) { MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", valid_name.c_str(), MYLOGE("zip_writer->StartEntryWithTime(%s): %s\n", valid_name.c_str(), ZipWriter::ErrorCodeString(err)); ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } bool finished_entry = false; bool finished_entry = false; auto finish_entry = [this, &finished_entry] { auto finish_entry = [&zip_writer, &finished_entry] { if (!finished_entry) { if (!finished_entry) { // This should only be called when we're going to return an earlier error, // This should only be called when we're going to return an earlier error, // which would've been logged. This may imply the file is already corrupt // which would've been logged. This may imply the file is already corrupt // and any further logging from FinishEntry is more likely to mislead than // and any further logging from FinishEntry is more likely to mislead than // not. // not. this->zip_writer_->FinishEntry(); zip_writer->FinishEntry(); } } }; }; auto scope_guard = android::base::make_scope_guard(finish_entry); auto scope_guard = android::base::make_scope_guard(finish_entry); Loading Loading @@ -950,17 +962,17 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno)); MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno)); return -errno; return -errno; } } err = zip_writer_->WriteBytes(buffer.data(), bytes_read); err = zip_writer->WriteBytes(buffer.data(), bytes_read); if (err) { if (err) { MYLOGE("zip_writer_->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err)); MYLOGE("zip_writer->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } } } err = zip_writer_->FinishEntry(); err = zip_writer->FinishEntry(); finished_entry = true; finished_entry = true; if (err != 0) { if (err != 0) { MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); MYLOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); return UNKNOWN_ERROR; return UNKNOWN_ERROR; } } Loading @@ -968,6 +980,11 @@ status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd, } } bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) { bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) { return AddZipEntry(zip_writer_, entry_name, entry_path); } bool Dumpstate::AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, const std::string& entry_path) { android::base::unique_fd fd( android::base::unique_fd fd( TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC))); TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC))); if (fd == -1) { if (fd == -1) { Loading @@ -975,7 +992,7 @@ bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& en return false; return false; } } return (AddZipEntryFromFd(entry_name, fd.get()) == OK); return (AddZipEntryFromFd(zip_writer, entry_name, fd.get()) == OK); } } /* adds a file to the existing zipped bugreport */ /* adds a file to the existing zipped bugreport */ Loading Loading @@ -2765,13 +2782,14 @@ void Dumpstate::DumpstateBoard(int out_fd) { static void ShowUsage() { static void ShowUsage() { fprintf(stderr, fprintf(stderr, "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] " "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] [-m] " "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n" "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n" " -h: display this help message\n" " -h: display this help message\n" " -b: play sound file instead of vibrate, at beginning of job\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" " -e: play sound file instead of vibrate, at end of job\n" " -o: write to custom directory (only in limited mode)\n" " -o: write to custom directory (only in limited mode)\n" " -p: capture screenshot to filename.png\n" " -p: capture screenshot to filename.png\n" " -m: capture screenshots of all displays to filename.zip\n" " -s: write zipped file to control socket (for init)\n" " -s: write zipped file to control socket (for init)\n" " -S: write file location to control socket (for init)\n" " -S: write file location to control socket (for init)\n" " -q: disable vibrate\n" " -q: disable vibrate\n" Loading Loading @@ -2899,8 +2917,21 @@ static bool PrepareToWriteToFile() { } } if (ds.options_->do_screenshot) { if (ds.options_->do_screenshot) { if (android::os::bugreport_multi_display_screenshot_enabled() && ds.options_->multi_display_screenshot) { ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-screenshots-zip.tmp" : "-screenshots.zip"); std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds(); for (PhysicalDisplayId display_id : ids) { std::string id = android::to_string(display_id); std::string path = ds.GetPath("-" + id + ".png"); ds.screenshot_path_by_display_id_.insert(std::make_pair(id, path)); } } else { ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-png.tmp" : ".png"); ds.screenshot_path_ = ds.GetPath(ds.CalledByApi() ? "-png.tmp" : ".png"); } } } ds.tmp_path_ = ds.GetPath(".tmp"); ds.tmp_path_ = ds.GetPath(".tmp"); ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt"); ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt"); Loading Loading @@ -3034,15 +3065,14 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt static void LogDumpOptions(const Dumpstate::DumpOptions& options) { static void LogDumpOptions(const Dumpstate::DumpOptions& options) { MYLOGI( MYLOGI( "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d " "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d " "is_remote_mode: %d show_header_only: %d telephony_only: %d " "multi_display_screenshot: %d is_remote_mode: %d show_header_only: %d telephony_only: %d " "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s " "wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s " "limited_only: %d args: %s\n", "limited_only: %d args: %s\n", options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket, options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket, options.do_screenshot, options.is_remote_mode, options.show_header_only, options.do_screenshot, options.multi_display_screenshot, options.is_remote_mode, options.telephony_only, options.wifi_only, options.show_header_only, options.telephony_only, options.wifi_only, options.do_progress_updates, options.bugreport_fd.get(), options.do_progress_updates, options.bugreport_fd.get(), options.bugreport_mode_string.c_str(), options.bugreport_mode_string.c_str(), options.limited_only, options.args.c_str()); options.limited_only, options.args.c_str()); } } void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, Loading @@ -3053,6 +3083,8 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, bool skip_user_consent) { bool skip_user_consent) { this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; this->multi_display_screenshot = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT; this->skip_user_consent = skip_user_consent; this->skip_user_consent = skip_user_consent; // Duplicate the fds because the passed in fds don't outlive the binder transaction. // Duplicate the fds because the passed in fds don't outlive the binder transaction. bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); Loading @@ -3064,7 +3096,7 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) { Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) { RunStatus status = RunStatus::OK; RunStatus status = RunStatus::OK; int c; int c; while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpmLPBRSV:w")) != -1) { switch (c) { switch (c) { // clang-format off // clang-format off case 'o': out_dir = optarg; break; case 'o': out_dir = optarg; break; Loading @@ -3073,6 +3105,7 @@ Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) case 'v': show_header_only = true; break; case 'v': show_header_only = true; break; case 'q': do_vibrate = false; break; case 'q': do_vibrate = false; break; case 'p': do_screenshot = true; break; case 'p': do_screenshot = true; break; case 'm': multi_display_screenshot = true; break; case 'P': do_progress_updates = true; break; case 'P': do_progress_updates = true; break; case 'R': is_remote_mode = true; break; case 'R': is_remote_mode = true; break; case 'L': limited_only = true; break; case 'L': limited_only = true; break; Loading Loading @@ -3717,6 +3750,9 @@ bool Dumpstate::CalledByApi() const { void Dumpstate::CleanupTmpFiles() { void Dumpstate::CleanupTmpFiles() { android::os::UnlinkAndLogOnError(tmp_path_); android::os::UnlinkAndLogOnError(tmp_path_); android::os::UnlinkAndLogOnError(screenshot_path_); android::os::UnlinkAndLogOnError(screenshot_path_); for (auto& [_, path] : screenshot_path_by_display_id_) { android::os::UnlinkAndLogOnError(path); } android::os::UnlinkAndLogOnError(path_); android::os::UnlinkAndLogOnError(path_); if (dump_traces_path != nullptr) { if (dump_traces_path != nullptr) { android::os::UnlinkAndLogOnError(dump_traces_path); android::os::UnlinkAndLogOnError(dump_traces_path); Loading Loading @@ -4712,6 +4748,15 @@ void Dumpstate::UpdateProgress(int32_t delta_sec) { } } void Dumpstate::TakeScreenshot(const std::string& path) { void Dumpstate::TakeScreenshot(const std::string& path) { if (android::os::bugreport_multi_display_screenshot_enabled() && ds.options_->multi_display_screenshot) { TakeMultiDisplayScreenshots(path); } else { TakeSingleDisplayScreenshot(path); } } void Dumpstate::TakeSingleDisplayScreenshot(const std::string& path) { const std::string& real_path = path.empty() ? screenshot_path_ : path; const std::string& real_path = path.empty() ? screenshot_path_ : path; int status = int status = RunCommand("", {"screencap", "-p", real_path}, RunCommand("", {"screencap", "-p", real_path}, Loading @@ -4728,6 +4773,44 @@ void Dumpstate::TakeScreenshot(const std::string& path) { } } } } void Dumpstate::TakeMultiDisplayScreenshots(const std::string& path) { const std::string& real_path = path.empty() ? screenshot_path_ : path; FILE* zipFile = fopen(real_path.c_str(), "wb"); std::unique_ptr<ZipWriter> zip_writer = std::make_unique<ZipWriter>(zipFile); bool success = false; for (auto& [display_id, screenshot_path] : ds.screenshot_path_by_display_id_) { int status = RunCommand( "", {"/system/bin/screencap", "-p", screenshot_path, "-d", display_id}, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); if (status == 0) { MYLOGD("Screenshot for display id %s saved on %s\n", display_id.c_str(), screenshot_path.c_str()); size_t flags = ZipWriter::kCompress | ZipWriter::kDefaultCompression; std::filesystem::path p(screenshot_path); std::string filename = p.filename().string(); AddZipEntry(zip_writer, filename, screenshot_path); success = true; } else { MYLOGE("Failed to take screenshot for display id %s on %s\n", display_id.c_str(), screenshot_path.c_str()); } } zip_writer->Finish(); fclose(zipFile); if (listener_ != nullptr) { // Show a visual indication to indicate screenshot is taken via // IDumpstateListener.onScreenshotTaken() listener_->onScreenshotTaken(success); } } bool is_dir(const char* pathname) { bool is_dir(const char* pathname) { struct stat info; struct stat info; if (stat(pathname, &info) == -1) { if (stat(pathname, &info) == -1) { Loading
cmds/dumpstate/dumpstate.h +19 −5 Original line number Original line Diff line number Diff line Loading @@ -209,10 +209,11 @@ class Dumpstate { enum BugreportFlag { enum BugreportFlag { BUGREPORT_USE_PREDUMPED_UI_DATA = BUGREPORT_USE_PREDUMPED_UI_DATA = android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, BUGREPORT_FLAG_DEFER_CONSENT = BUGREPORT_FLAG_DEFER_CONSENT = android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT, android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT, BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL, BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT = android::os::IDumpstate::BUGREPORT_FLAG_CAPTURE_MULTI_DISPLAY_SCREENSHOT }; }; static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; Loading Loading @@ -410,6 +411,7 @@ class Dumpstate { // Writes generation progress updates to a socket. // Writes generation progress updates to a socket. bool progress_updates_to_socket = false; bool progress_updates_to_socket = false; bool do_screenshot = false; bool do_screenshot = false; bool multi_display_screenshot = false; bool is_screenshot_copied = false; bool is_screenshot_copied = false; bool is_consent_deferred = false; bool is_consent_deferred = false; bool skip_user_consent = false; bool skip_user_consent = false; Loading Loading @@ -513,6 +515,9 @@ class Dumpstate { // Full path of the file containing the screenshot (when requested). // Full path of the file containing the screenshot (when requested). std::string screenshot_path_; std::string screenshot_path_; // Full paths of the files containing the screenshots for multi displays (when requested). std::map<std::string, std::string> screenshot_path_by_display_id_; // Pointer to the zipped file. // Pointer to the zipped file. std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose}; std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose}; Loading Loading @@ -572,6 +577,15 @@ class Dumpstate { RunStatus DumpstateDefaultAfterCritical(); RunStatus DumpstateDefaultAfterCritical(); RunStatus dumpstate(); RunStatus dumpstate(); bool AddZipEntry(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, const std::string& entry_path); android::status_t AddZipEntryFromFd(const std::unique_ptr<ZipWriter>& zip_writer, const std::string& entry_name, int fd, std::chrono::milliseconds timeout); void TakeSingleDisplayScreenshot(const std::string& path = ""); void TakeMultiDisplayScreenshots(const std::string& path = ""); void MaybeTakeEarlyScreenshot(); void MaybeTakeEarlyScreenshot(); void MaybeSavePlaceholderScreenshot(); void MaybeSavePlaceholderScreenshot(); std::future<std::string> MaybeSnapshotSystemTraceAsync(); std::future<std::string> MaybeSnapshotSystemTraceAsync(); Loading
cmds/dumpstate/tests/dumpstate_test.cpp +40 −0 Original line number Original line Diff line number Diff line Loading @@ -1096,6 +1096,7 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileCreated) { std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-png.tmp" : ".png"); std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-png.tmp" : ".png"); EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; android::base::RemoveFileIfExists(screenshot); } } TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { Loading @@ -1114,6 +1115,45 @@ TEST_F(ZippedBugReportStreamTest, ScreenShotFileIsNotCreated) { EXPECT_FALSE(std::filesystem::exists(screenshot)) << screenshot << " was created."; EXPECT_FALSE(std::filesystem::exists(screenshot)) << screenshot << " was created."; } } TEST_F(ZippedBugReportStreamTest, ScreenShotZipFileCreated) { std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip"; android::base::unique_fd out_fd; CreateFd(out_path, &out_fd); ds_.options_->limited_only = true; ds_.options_->stream_to_socket = true; ds_.options_->do_screenshot = false; ds_.options_->multi_display_screenshot = true; RedirectOutputToFd(out_fd); GenerateBugreport(); std::string screenshot = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip"); EXPECT_TRUE(std::filesystem::exists(screenshot)) << screenshot << " was not created."; android::base::RemoveFileIfExists(screenshot); } TEST_F(ZippedBugReportStreamTest, ScreenShotsAreIncludedInScreenShotZipFile) { std::string out_path = kTestDataPath + "ScreenShotCapturedOut.zip"; android::base::unique_fd out_fd; CreateFd(out_path, &out_fd); ds_.options_->limited_only = true; ds_.options_->stream_to_socket = true; ds_.options_->do_screenshot = false; ds_.options_->multi_display_screenshot = true; RedirectOutputToFd(out_fd); GenerateBugreport(); std::string screenshot_path = ds_.GetPath(ds_.CalledByApi() ? "-screenshots-zip.tmp" : ".zip"); OpenArchive(screenshot_path.c_str(), &handle_); ZipEntry entry; for (auto& [_, path] : ds_.screenshot_path_by_display_id_) { std::filesystem::path p(path); VerifyEntry(handle_, p.filename().string(), &entry); } android::base::RemoveFileIfExists(screenshot_path); } class ProgressTest : public DumpstateBaseTest { class ProgressTest : public DumpstateBaseTest { public: public: Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { Loading