| // Copyright 2015 The Crashpad Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <sys/types.h> |
| #include <time.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "client/crash_report_database.h" |
| #include "client/settings.h" |
| #include "tools/tool_support.h" |
| #include "util/file/file_io.h" |
| #include "util/file/file_reader.h" |
| #include "util/misc/uuid.h" |
| #include "util/stdlib/string_number_conversion.h" |
| |
| namespace crashpad { |
| namespace { |
| |
| void Usage(const base::FilePath& me) { |
| fprintf(stderr, |
| "Usage: %" PRFilePath " [OPTION]... PID\n" |
| "Operate on Crashpad crash report databases.\n" |
| "\n" |
| " --create allow database at PATH to be created\n" |
| " -d, --database=PATH operate on the crash report database at PATH\n" |
| " --show-client-id show the client ID\n" |
| " --show-uploads-enabled show whether uploads are enabled\n" |
| " --show-last-upload-attempt-time\n" |
| " show the last-upload-attempt time\n" |
| " --show-pending-reports show reports eligible for upload\n" |
| " --show-completed-reports show reports not eligible for upload\n" |
| " --show-all-report-info with --show-*-reports, show more information\n" |
| " --show-report=UUID show report stored under UUID\n" |
| " --set-uploads-enabled=BOOL enable or disable uploads\n" |
| " --set-last-upload-attempt-time=TIME\n" |
| " set the last-upload-attempt time to TIME\n" |
| " --new-report=PATH submit a new report at PATH, or - for stdin\n" |
| " --utc show and set UTC times instead of local\n" |
| " --help display this help and exit\n" |
| " --version output version information and exit\n", |
| me.value().c_str()); |
| ToolSupport::UsageTail(me); |
| } |
| |
| struct Options { |
| std::vector<UUID> show_reports; |
| std::vector<base::FilePath> new_report_paths; |
| const char* database; |
| const char* set_last_upload_attempt_time_string; |
| time_t set_last_upload_attempt_time; |
| bool create; |
| bool show_client_id; |
| bool show_uploads_enabled; |
| bool show_last_upload_attempt_time; |
| bool show_pending_reports; |
| bool show_completed_reports; |
| bool show_all_report_info; |
| bool set_uploads_enabled; |
| bool has_set_uploads_enabled; |
| bool utc; |
| }; |
| |
| // Converts |string| to |boolean|, returning true if a conversion could be |
| // performed, and false without setting |boolean| if no conversion could be |
| // performed. Various string representations of a boolean are recognized |
| // case-insensitively. |
| bool StringToBool(const char* string, bool* boolean) { |
| static constexpr const char* kFalseWords[] = { |
| "0", |
| "false", |
| "no", |
| "off", |
| "disabled", |
| "clear", |
| }; |
| static constexpr const char* kTrueWords[] = { |
| "1", |
| "true", |
| "yes", |
| "on", |
| "enabled", |
| "set", |
| }; |
| |
| for (size_t index = 0; index < base::size(kFalseWords); ++index) { |
| if (strcasecmp(string, kFalseWords[index]) == 0) { |
| *boolean = false; |
| return true; |
| } |
| } |
| |
| for (size_t index = 0; index < base::size(kTrueWords); ++index) { |
| if (strcasecmp(string, kTrueWords[index]) == 0) { |
| *boolean = true; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Converts |boolean| to a string, either "true" or "false". |
| std::string BoolToString(bool boolean) { |
| return std::string(boolean ? "true" : "false"); |
| } |
| |
| // Converts |string| to |out_time|, returning true if a conversion could be |
| // performed, and false without setting |boolean| if no conversion could be |
| // performed. Various time formats are recognized, including several string |
| // representations and a numeric time_t representation. The special |string| |
| // "never" is recognized as converted to a |out_time| value of 0; "now" is |
| // converted to the current time. |utc|, when true, causes |string| to be |
| // interpreted as a UTC time rather than a local time when the time zone is |
| // ambiguous. |
| bool StringToTime(const char* string, time_t* out_time, bool utc) { |
| if (strcasecmp(string, "never") == 0) { |
| *out_time = 0; |
| return true; |
| } |
| |
| if (strcasecmp(string, "now") == 0) { |
| errno = 0; |
| PCHECK(time(out_time) != -1 || errno == 0); |
| return true; |
| } |
| |
| const char* end = string + strlen(string); |
| |
| static constexpr const char* kFormats[] = { |
| "%Y-%m-%d %H:%M:%S %Z", |
| "%Y-%m-%d %H:%M:%S", |
| "%+", |
| }; |
| |
| for (size_t index = 0; index < base::size(kFormats); ++index) { |
| tm time_tm; |
| const char* strptime_result = strptime(string, kFormats[index], &time_tm); |
| if (strptime_result == end) { |
| time_t test_out_time; |
| if (utc) { |
| test_out_time = timegm(&time_tm); |
| } else { |
| test_out_time = mktime(&time_tm); |
| } |
| |
| // mktime() is supposed to set errno in the event of an error, but support |
| // for this is spotty, so there’s no way to distinguish between a true |
| // time_t of -1 (1969-12-31 23:59:59 UTC) and an error. Assume error. |
| // |
| // See 10.11.5 Libc-1082.50.1/stdtime/FreeBSD/localtime.c and |
| // glibc-2.24/time/mktime.c, which don’t set errno or save and restore |
| // errno. Post-Android 7.1.0 Bionic is even more hopeless, setting errno |
| // whenever the time conversion returns -1, even for valid input. See |
| // libc/tzcode/localtime.c mktime(). Windows seems to get it right: see |
| // 10.0.14393 SDK Source/ucrt/time/mktime.cpp. |
| if (test_out_time != -1) { |
| *out_time = test_out_time; |
| return true; |
| } |
| } |
| } |
| |
| int64_t int64_result; |
| if (StringToNumber(string, &int64_result) && |
| base::IsValueInRangeForNumericType<time_t>(int64_result)) { |
| *out_time = int64_result; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Converts |out_time| to a string, and returns it. |utc| determines whether the |
| // converted time will reference local time or UTC. If |out_time| is 0, the |
| // string "never" will be returned as a special case. |
| std::string TimeToString(time_t out_time, bool utc) { |
| if (out_time == 0) { |
| return std::string("never"); |
| } |
| |
| tm time_tm; |
| if (utc) { |
| PCHECK(gmtime_r(&out_time, &time_tm)); |
| } else { |
| PCHECK(localtime_r(&out_time, &time_tm)); |
| } |
| |
| char string[64]; |
| CHECK_NE( |
| strftime(string, base::size(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm), |
| 0u); |
| |
| return std::string(string); |
| } |
| |
| // Shows information about a single |report|. |space_count| is the number of |
| // spaces to print before each line that is printed. |utc| determines whether |
| // times should be shown in UTC or the local time zone. |
| void ShowReport(const CrashReportDatabase::Report& report, |
| size_t space_count, |
| bool utc) { |
| std::string spaces(space_count, ' '); |
| |
| printf("%sPath: %" PRFilePath "\n", |
| spaces.c_str(), |
| report.file_path.value().c_str()); |
| if (!report.id.empty()) { |
| printf("%sRemote ID: %s\n", spaces.c_str(), report.id.c_str()); |
| } |
| printf("%sCreation time: %s\n", |
| spaces.c_str(), |
| TimeToString(report.creation_time, utc).c_str()); |
| printf("%sUploaded: %s\n", |
| spaces.c_str(), |
| BoolToString(report.uploaded).c_str()); |
| printf("%sLast upload attempt time: %s\n", |
| spaces.c_str(), |
| TimeToString(report.last_upload_attempt_time, utc).c_str()); |
| printf("%sUpload attempts: %d\n", spaces.c_str(), report.upload_attempts); |
| } |
| |
| // Shows information about a vector of |reports|. |space_count| is the number of |
| // spaces to print before each line that is printed. |options| will be consulted |
| // to determine whether to show expanded information |
| // (options.show_all_report_info) and what time zone to use when showing |
| // expanded information (options.utc). |
| void ShowReports(const std::vector<CrashReportDatabase::Report>& reports, |
| size_t space_count, |
| const Options& options) { |
| std::string spaces(space_count, ' '); |
| const char* colon = options.show_all_report_info ? ":" : ""; |
| |
| for (const CrashReportDatabase::Report& report : reports) { |
| printf("%s%s%s\n", spaces.c_str(), report.uuid.ToString().c_str(), colon); |
| if (options.show_all_report_info) { |
| ShowReport(report, space_count + 2, options.utc); |
| } |
| } |
| } |
| |
| int DatabaseUtilMain(int argc, char* argv[]) { |
| const base::FilePath argv0( |
| ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); |
| const base::FilePath me(argv0.BaseName()); |
| |
| enum OptionFlags { |
| // “Short” (single-character) options. |
| kOptionDatabase = 'd', |
| |
| // Long options without short equivalents. |
| kOptionLastChar = 255, |
| kOptionCreate, |
| kOptionShowClientID, |
| kOptionShowUploadsEnabled, |
| kOptionShowLastUploadAttemptTime, |
| kOptionShowPendingReports, |
| kOptionShowCompletedReports, |
| kOptionShowAllReportInfo, |
| kOptionShowReport, |
| kOptionSetUploadsEnabled, |
| kOptionSetLastUploadAttemptTime, |
| kOptionNewReport, |
| kOptionUTC, |
| |
| // Standard options. |
| kOptionHelp = -2, |
| kOptionVersion = -3, |
| }; |
| |
| static constexpr option long_options[] = { |
| {"create", no_argument, nullptr, kOptionCreate}, |
| {"database", required_argument, nullptr, kOptionDatabase}, |
| {"show-client-id", no_argument, nullptr, kOptionShowClientID}, |
| {"show-uploads-enabled", no_argument, nullptr, kOptionShowUploadsEnabled}, |
| {"show-last-upload-attempt-time", |
| no_argument, |
| nullptr, |
| kOptionShowLastUploadAttemptTime}, |
| {"show-pending-reports", no_argument, nullptr, kOptionShowPendingReports}, |
| {"show-completed-reports", |
| no_argument, |
| nullptr, |
| kOptionShowCompletedReports}, |
| {"show-all-report-info", no_argument, nullptr, kOptionShowAllReportInfo}, |
| {"show-report", required_argument, nullptr, kOptionShowReport}, |
| {"set-uploads-enabled", |
| required_argument, |
| nullptr, |
| kOptionSetUploadsEnabled}, |
| {"set-last-upload-attempt-time", |
| required_argument, |
| nullptr, |
| kOptionSetLastUploadAttemptTime}, |
| {"new-report", required_argument, nullptr, kOptionNewReport}, |
| {"utc", no_argument, nullptr, kOptionUTC}, |
| {"help", no_argument, nullptr, kOptionHelp}, |
| {"version", no_argument, nullptr, kOptionVersion}, |
| {nullptr, 0, nullptr, 0}, |
| }; |
| |
| Options options = {}; |
| |
| int opt; |
| while ((opt = getopt_long(argc, argv, "d:", long_options, nullptr)) != -1) { |
| switch (opt) { |
| case kOptionCreate: { |
| options.create = true; |
| break; |
| } |
| case kOptionDatabase: { |
| options.database = optarg; |
| break; |
| } |
| case kOptionShowClientID: { |
| options.show_client_id = true; |
| break; |
| } |
| case kOptionShowUploadsEnabled: { |
| options.show_uploads_enabled = true; |
| break; |
| } |
| case kOptionShowLastUploadAttemptTime: { |
| options.show_last_upload_attempt_time = true; |
| break; |
| } |
| case kOptionShowPendingReports: { |
| options.show_pending_reports = true; |
| break; |
| } |
| case kOptionShowCompletedReports: { |
| options.show_completed_reports = true; |
| break; |
| } |
| case kOptionShowAllReportInfo: { |
| options.show_all_report_info = true; |
| break; |
| } |
| case kOptionShowReport: { |
| UUID uuid; |
| if (!uuid.InitializeFromString(optarg)) { |
| ToolSupport::UsageHint(me, "--show-report requires a UUID"); |
| return EXIT_FAILURE; |
| } |
| options.show_reports.push_back(uuid); |
| break; |
| } |
| case kOptionSetUploadsEnabled: { |
| if (!StringToBool(optarg, &options.set_uploads_enabled)) { |
| ToolSupport::UsageHint(me, "--set-uploads-enabled requires a BOOL"); |
| return EXIT_FAILURE; |
| } |
| options.has_set_uploads_enabled = true; |
| break; |
| } |
| case kOptionSetLastUploadAttemptTime: { |
| options.set_last_upload_attempt_time_string = optarg; |
| break; |
| } |
| case kOptionNewReport: { |
| options.new_report_paths.push_back(base::FilePath( |
| ToolSupport::CommandLineArgumentToFilePathStringType(optarg))); |
| break; |
| } |
| case kOptionUTC: { |
| options.utc = true; |
| break; |
| } |
| case kOptionHelp: { |
| Usage(me); |
| return EXIT_SUCCESS; |
| } |
| case kOptionVersion: { |
| ToolSupport::Version(me); |
| return EXIT_SUCCESS; |
| } |
| default: { |
| ToolSupport::UsageHint(me, nullptr); |
| return EXIT_FAILURE; |
| } |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (!options.database) { |
| ToolSupport::UsageHint(me, "--database is required"); |
| return EXIT_FAILURE; |
| } |
| |
| // This conversion couldn’t happen in the option-processing loop above because |
| // it depends on options.utc, which may have been set after |
| // options.set_last_upload_attempt_time_string. |
| if (options.set_last_upload_attempt_time_string) { |
| if (!StringToTime(options.set_last_upload_attempt_time_string, |
| &options.set_last_upload_attempt_time, |
| options.utc)) { |
| ToolSupport::UsageHint(me, |
| "--set-last-upload-attempt-time requires a TIME"); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| // --new-report is treated as a show operation because it produces output. |
| const size_t show_operations = options.show_client_id + |
| options.show_uploads_enabled + |
| options.show_last_upload_attempt_time + |
| options.show_pending_reports + |
| options.show_completed_reports + |
| options.show_reports.size() + |
| options.new_report_paths.size(); |
| const size_t set_operations = |
| options.has_set_uploads_enabled + |
| (options.set_last_upload_attempt_time_string != nullptr); |
| |
| if ((options.create ? 1 : 0) + show_operations + set_operations == 0) { |
| ToolSupport::UsageHint(me, "nothing to do"); |
| return EXIT_FAILURE; |
| } |
| |
| std::unique_ptr<CrashReportDatabase> database; |
| base::FilePath database_path = base::FilePath( |
| ToolSupport::CommandLineArgumentToFilePathStringType(options.database)); |
| if (options.create) { |
| database = CrashReportDatabase::Initialize(database_path); |
| } else { |
| database = CrashReportDatabase::InitializeWithoutCreating(database_path); |
| } |
| if (!database) { |
| return EXIT_FAILURE; |
| } |
| |
| Settings* settings = database->GetSettings(); |
| |
| // Handle the “show” options before the “set” options so that when they’re |
| // specified together, the “show” option reflects the initial state. |
| |
| if (options.show_client_id) { |
| UUID client_id; |
| if (!settings->GetClientID(&client_id)) { |
| return EXIT_FAILURE; |
| } |
| |
| const char* prefix = (show_operations > 1) ? "Client ID: " : ""; |
| |
| printf("%s%s\n", prefix, client_id.ToString().c_str()); |
| } |
| |
| if (options.show_uploads_enabled) { |
| bool uploads_enabled; |
| if (!settings->GetUploadsEnabled(&uploads_enabled)) { |
| return EXIT_FAILURE; |
| } |
| |
| const char* prefix = (show_operations > 1) ? "Uploads enabled: " : ""; |
| |
| printf("%s%s\n", prefix, BoolToString(uploads_enabled).c_str()); |
| } |
| |
| if (options.show_last_upload_attempt_time) { |
| time_t last_upload_attempt_time; |
| if (!settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) { |
| return EXIT_FAILURE; |
| } |
| |
| const char* prefix = |
| (show_operations > 1) ? "Last upload attempt time: " : ""; |
| |
| printf("%s%s (%ld)\n", |
| prefix, |
| TimeToString(last_upload_attempt_time, options.utc).c_str(), |
| static_cast<long>(last_upload_attempt_time)); |
| } |
| |
| if (options.show_pending_reports) { |
| std::vector<CrashReportDatabase::Report> pending_reports; |
| if (database->GetPendingReports(&pending_reports) != |
| CrashReportDatabase::kNoError) { |
| return EXIT_FAILURE; |
| } |
| |
| if (show_operations > 1) { |
| printf("Pending reports:\n"); |
| } |
| |
| ShowReports(pending_reports, show_operations > 1 ? 2 : 0, options); |
| } |
| |
| if (options.show_completed_reports) { |
| std::vector<CrashReportDatabase::Report> completed_reports; |
| if (database->GetCompletedReports(&completed_reports) != |
| CrashReportDatabase::kNoError) { |
| return EXIT_FAILURE; |
| } |
| |
| if (show_operations > 1) { |
| printf("Completed reports:\n"); |
| } |
| |
| ShowReports(completed_reports, show_operations > 1 ? 2 : 0, options); |
| } |
| |
| for (const UUID& uuid : options.show_reports) { |
| CrashReportDatabase::Report report; |
| CrashReportDatabase::OperationStatus status = |
| database->LookUpCrashReport(uuid, &report); |
| if (status == CrashReportDatabase::kNoError) { |
| if (show_operations > 1) { |
| printf("Report %s:\n", uuid.ToString().c_str()); |
| } |
| ShowReport(report, show_operations > 1 ? 2 : 0, options.utc); |
| } else if (status == CrashReportDatabase::kReportNotFound) { |
| // If only asked to do one thing, a failure to find the single requested |
| // report should result in a failure exit status. |
| if (show_operations + set_operations == 1) { |
| fprintf( |
| stderr, "%" PRFilePath ": Report not found\n", me.value().c_str()); |
| return EXIT_FAILURE; |
| } |
| printf("Report %s not found\n", uuid.ToString().c_str()); |
| } else { |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (options.has_set_uploads_enabled && |
| !settings->SetUploadsEnabled(options.set_uploads_enabled)) { |
| return EXIT_FAILURE; |
| } |
| |
| if (options.set_last_upload_attempt_time_string && |
| !settings->SetLastUploadAttemptTime( |
| options.set_last_upload_attempt_time)) { |
| return EXIT_FAILURE; |
| } |
| |
| bool used_stdin = false; |
| for (const base::FilePath& new_report_path : options.new_report_paths) { |
| std::unique_ptr<FileReaderInterface> file_reader; |
| |
| if (new_report_path.value() == FILE_PATH_LITERAL("-")) { |
| if (used_stdin) { |
| fprintf(stderr, |
| "%" PRFilePath |
| ": Only one --new-report may be read from standard input\n", |
| me.value().c_str()); |
| return EXIT_FAILURE; |
| } |
| used_stdin = true; |
| file_reader.reset(new WeakFileHandleFileReader( |
| StdioFileHandle(StdioStream::kStandardInput))); |
| } else { |
| std::unique_ptr<FileReader> file_path_reader(new FileReader()); |
| if (!file_path_reader->Open(new_report_path)) { |
| return EXIT_FAILURE; |
| } |
| |
| file_reader = std::move(file_path_reader); |
| } |
| |
| std::unique_ptr<CrashReportDatabase::NewReport> new_report; |
| CrashReportDatabase::OperationStatus status = |
| database->PrepareNewCrashReport(&new_report); |
| if (status != CrashReportDatabase::kNoError) { |
| return EXIT_FAILURE; |
| } |
| |
| char buf[4096]; |
| FileOperationResult read_result; |
| do { |
| read_result = file_reader->Read(buf, sizeof(buf)); |
| if (read_result < 0) { |
| return EXIT_FAILURE; |
| } |
| if (read_result > 0 && !new_report->Writer()->Write(buf, read_result)) { |
| return EXIT_FAILURE; |
| } |
| } while (read_result > 0); |
| |
| UUID uuid; |
| status = database->FinishedWritingCrashReport(std::move(new_report), &uuid); |
| if (status != CrashReportDatabase::kNoError) { |
| return EXIT_FAILURE; |
| } |
| |
| const char* prefix = (show_operations > 1) ? "New report ID: " : ""; |
| printf("%s%s\n", prefix, uuid.ToString().c_str()); |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| } // namespace |
| } // namespace crashpad |
| |
| #if defined(OS_POSIX) |
| int main(int argc, char* argv[]) { |
| return crashpad::DatabaseUtilMain(argc, argv); |
| } |
| #elif defined(OS_WIN) |
| int wmain(int argc, wchar_t* argv[]) { |
| return crashpad::ToolSupport::Wmain(argc, argv, crashpad::DatabaseUtilMain); |
| } |
| #endif // OS_POSIX |