OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include <errno.h> |
| 16 #include <getopt.h> |
| 17 #include <libgen.h> |
| 18 #include <stdio.h> |
| 19 #include <stdlib.h> |
| 20 #include <string.h> |
| 21 #include <sys/types.h> |
| 22 #include <time.h> |
| 23 |
| 24 #include <string> |
| 25 #include <vector> |
| 26 |
| 27 #include "base/basictypes.h" |
| 28 #include "base/logging.h" |
| 29 #include "base/files/file_path.h" |
| 30 #include "base/memory/scoped_ptr.h" |
| 31 #include "base/numerics/safe_conversions.h" |
| 32 #include "client/crash_report_database.h" |
| 33 #include "client/settings.h" |
| 34 #include "tools/tool_support.h" |
| 35 #include "util/misc/uuid.h" |
| 36 |
| 37 namespace crashpad { |
| 38 namespace { |
| 39 |
| 40 void Usage(const std::string& me) { |
| 41 fprintf(stderr, |
| 42 "Usage: %s [OPTION]... PID\n" |
| 43 "Operate on Crashpad crash report databases.\n" |
| 44 "\n" |
| 45 " -d, --database=PATH operate on the crash report database at PATH\
n" |
| 46 " --show-client-id show the client ID\n" |
| 47 " --show-uploads-enabled show whether uploads are enabled\n" |
| 48 " --show-last-upload-attempt-time\n" |
| 49 " show the last-upload-attempt time\n" |
| 50 " --show-pending-reports show reports eligible for upload\n" |
| 51 " --show-completed-reports show reports not eligible for upload\n" |
| 52 " --show-all-report-info with --show-*-reports, show more information\
n" |
| 53 " --show-report=UUID show report stored under UUID\n" |
| 54 " --set-uploads-enabled=BOOL enable or disable uploads\n" |
| 55 " --set-last-upload-attempt-time=TIME\n" |
| 56 " set the last-upload-attempt time to TIME\n" |
| 57 " --utc show and set UTC times instead of local\n" |
| 58 " --help display this help and exit\n" |
| 59 " --version output version information and exit\n", |
| 60 me.c_str()); |
| 61 ToolSupport::UsageTail(me); |
| 62 } |
| 63 |
| 64 struct Options { |
| 65 std::vector<UUID> show_reports; |
| 66 const char* database; |
| 67 const char* set_last_upload_attempt_time_string; |
| 68 time_t set_last_upload_attempt_time; |
| 69 bool show_client_id; |
| 70 bool show_uploads_enabled; |
| 71 bool show_last_upload_attempt_time; |
| 72 bool show_pending_reports; |
| 73 bool show_completed_reports; |
| 74 bool show_all_report_info; |
| 75 bool set_uploads_enabled; |
| 76 bool has_set_uploads_enabled; |
| 77 bool utc; |
| 78 }; |
| 79 |
| 80 // Converts |string| to |boolean|, returning true if a conversion could be |
| 81 // performed, and false without setting |boolean| if no conversion could be |
| 82 // performed. Various string representations of a boolean are recognized |
| 83 // case-insensitively. |
| 84 bool StringToBool(const char* string, bool* boolean) { |
| 85 const char* const kFalseWords[] = { |
| 86 "0", |
| 87 "false", |
| 88 "no", |
| 89 "off", |
| 90 "disabled", |
| 91 "clear", |
| 92 }; |
| 93 const char* const kTrueWords[] = { |
| 94 "1", |
| 95 "true", |
| 96 "yes", |
| 97 "on", |
| 98 "enabled", |
| 99 "set", |
| 100 }; |
| 101 |
| 102 for (size_t index = 0; index < arraysize(kFalseWords); ++index) { |
| 103 if (strcasecmp(string, kFalseWords[index]) == 0) { |
| 104 *boolean = false; |
| 105 return true; |
| 106 } |
| 107 } |
| 108 |
| 109 for (size_t index = 0; index < arraysize(kTrueWords); ++index) { |
| 110 if (strcasecmp(string, kTrueWords[index]) == 0) { |
| 111 *boolean = true; |
| 112 return true; |
| 113 } |
| 114 } |
| 115 |
| 116 return false; |
| 117 } |
| 118 |
| 119 // Converts |boolean| to a string, either "true" or "false". |
| 120 std::string BoolToString(bool boolean) { |
| 121 return std::string(boolean ? "true" : "false"); |
| 122 } |
| 123 |
| 124 // Converts |string| to |time|, returning true if a conversion could be |
| 125 // performed, and false without setting |boolean| if no conversion could be |
| 126 // performed. Various time formats are recognized, including several string |
| 127 // representations and a numeric time_t representation. The special string |
| 128 // "never" is recognized as |string| and converts to a |time| value of 0. |utc|, |
| 129 // when true, causes |string| to be interpreted as a UTC time rather than a |
| 130 // local time when the time zone is ambiguous. |
| 131 bool StringToTime(const char* string, time_t* time, bool utc) { |
| 132 if (strcasecmp(string, "never") == 0) { |
| 133 *time = 0; |
| 134 return true; |
| 135 } |
| 136 |
| 137 const char* end = string + strlen(string); |
| 138 |
| 139 const char* const kFormats[] = { |
| 140 "%Y-%m-%d %H:%M:%S %Z", |
| 141 "%Y-%m-%d %H:%M:%S", |
| 142 "%+", |
| 143 }; |
| 144 |
| 145 for (size_t index = 0; index < arraysize(kFormats); ++index) { |
| 146 tm time_tm; |
| 147 const char* strptime_result = strptime(string, kFormats[index], &time_tm); |
| 148 if (strptime_result == end) { |
| 149 if (utc) { |
| 150 *time = timegm(&time_tm); |
| 151 } else { |
| 152 *time = timelocal(&time_tm); |
| 153 } |
| 154 |
| 155 return true; |
| 156 } |
| 157 } |
| 158 |
| 159 char* end_result; |
| 160 errno = 0; |
| 161 long long strtoll_result = strtoll(string, &end_result, 0); |
| 162 if (end_result == end && errno == 0 && |
| 163 base::IsValueInRangeForNumericType<time_t>(strtoll_result)) { |
| 164 *time = strtoll_result; |
| 165 return true; |
| 166 } |
| 167 |
| 168 return false; |
| 169 } |
| 170 |
| 171 // Converst |time_tt| to a string, and returns it. |utc| determines whether the |
| 172 // converted time will reference local time or UTC. If |time_tt| is 0, the |
| 173 // string "never" will be returned as a special case. |
| 174 std::string TimeToString(time_t time_tt, bool utc) { |
| 175 if (time_tt == 0) { |
| 176 return std::string("never"); |
| 177 } |
| 178 |
| 179 tm time_tm; |
| 180 if (utc) { |
| 181 gmtime_r(&time_tt, &time_tm); |
| 182 } else { |
| 183 localtime_r(&time_tt, &time_tm); |
| 184 } |
| 185 |
| 186 char string[64]; |
| 187 CHECK_NE( |
| 188 strftime(string, arraysize(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm), |
| 189 0u); |
| 190 |
| 191 return std::string(string); |
| 192 } |
| 193 |
| 194 // Shows information about a single |report|. |space_count| is the number of |
| 195 // spaces to print before each line that is printed. |utc| determines whether |
| 196 // times should be shown in UTC or the local time zone. |
| 197 void ShowReport(const CrashReportDatabase::Report& report, |
| 198 size_t space_count, |
| 199 bool utc) { |
| 200 std::string spaces(space_count, ' '); |
| 201 |
| 202 printf("%sPath: %s\n", spaces.c_str(), report.file_path.value().c_str()); |
| 203 if (!report.id.empty()) { |
| 204 printf("%sRemote ID: %s\n", spaces.c_str(), report.id.c_str()); |
| 205 } |
| 206 printf("%sCreation time: %s\n", |
| 207 spaces.c_str(), |
| 208 TimeToString(report.creation_time, utc).c_str()); |
| 209 printf("%sUploaded: %s\n", |
| 210 spaces.c_str(), |
| 211 BoolToString(report.uploaded).c_str()); |
| 212 printf("%sLast upload attempt time: %s\n", |
| 213 spaces.c_str(), |
| 214 TimeToString(report.last_upload_attempt_time, utc).c_str()); |
| 215 printf("%sUpload attempts: %d\n", spaces.c_str(), report.upload_attempts); |
| 216 } |
| 217 |
| 218 // Shows information about a vector of |reports|. |space_count| is the number of |
| 219 // spaces to print before each line that is printed. |options| will be consulted |
| 220 // to determine whether to show expanded information |
| 221 // (options.show_all_report_info) and what time zone to use when showing |
| 222 // expanded information (options.utc). |
| 223 void ShowReports(const std::vector<CrashReportDatabase::Report>& reports, |
| 224 size_t space_count, |
| 225 const Options& options) { |
| 226 std::string spaces(space_count, ' '); |
| 227 const char* colon = options.show_all_report_info ? ":" : ""; |
| 228 |
| 229 for (const CrashReportDatabase::Report& report : reports) { |
| 230 printf("%s%s%s\n", spaces.c_str(), report.uuid.ToString().c_str(), colon); |
| 231 if (options.show_all_report_info) { |
| 232 ShowReport(report, space_count + 2, options.utc); |
| 233 } |
| 234 } |
| 235 } |
| 236 |
| 237 int DatabaseUtilMain(int argc, char* argv[]) { |
| 238 const std::string me(basename(argv[0])); |
| 239 |
| 240 enum OptionFlags { |
| 241 // “Short” (single-character) options. |
| 242 kOptionDatabase = 'd', |
| 243 |
| 244 // Long options without short equivalents. |
| 245 kOptionLastChar = 255, |
| 246 kOptionShowClientID, |
| 247 kOptionShowUploadsEnabled, |
| 248 kOptionShowLastUploadAttemptTime, |
| 249 kOptionShowPendingReports, |
| 250 kOptionShowCompletedReports, |
| 251 kOptionShowAllReportInfo, |
| 252 kOptionShowReport, |
| 253 kOptionSetUploadsEnabled, |
| 254 kOptionSetLastUploadAttemptTime, |
| 255 kOptionUTC, |
| 256 |
| 257 // Standard options. |
| 258 kOptionHelp = -2, |
| 259 kOptionVersion = -3, |
| 260 }; |
| 261 |
| 262 const option long_options[] = { |
| 263 {"database", required_argument, nullptr, kOptionDatabase}, |
| 264 {"show-client-id", no_argument, nullptr, kOptionShowClientID}, |
| 265 {"show-uploads-enabled", no_argument, nullptr, kOptionShowUploadsEnabled}, |
| 266 {"show-last-upload-attempt-time", |
| 267 no_argument, |
| 268 nullptr, |
| 269 kOptionShowLastUploadAttemptTime}, |
| 270 {"show-pending-reports", no_argument, nullptr, kOptionShowPendingReports}, |
| 271 {"show-completed-reports", |
| 272 no_argument, |
| 273 nullptr, |
| 274 kOptionShowCompletedReports}, |
| 275 {"show-all-report-info", no_argument, nullptr, kOptionShowAllReportInfo}, |
| 276 {"show-report", required_argument, nullptr, kOptionShowReport}, |
| 277 {"set-uploads-enabled", |
| 278 required_argument, |
| 279 nullptr, |
| 280 kOptionSetUploadsEnabled}, |
| 281 {"set-last-upload-attempt-time", |
| 282 required_argument, |
| 283 nullptr, |
| 284 kOptionSetLastUploadAttemptTime}, |
| 285 {"utc", no_argument, nullptr, kOptionUTC}, |
| 286 {"help", no_argument, nullptr, kOptionHelp}, |
| 287 {"version", no_argument, nullptr, kOptionVersion}, |
| 288 {nullptr, 0, nullptr, 0}, |
| 289 }; |
| 290 |
| 291 Options options = {}; |
| 292 |
| 293 int opt; |
| 294 while ((opt = getopt_long(argc, argv, "d:", long_options, nullptr)) != -1) { |
| 295 switch (opt) { |
| 296 case kOptionDatabase: { |
| 297 options.database = optarg; |
| 298 break; |
| 299 } |
| 300 case kOptionShowClientID: { |
| 301 options.show_client_id = true; |
| 302 break; |
| 303 } |
| 304 case kOptionShowUploadsEnabled: { |
| 305 options.show_uploads_enabled = true; |
| 306 break; |
| 307 } |
| 308 case kOptionShowLastUploadAttemptTime: { |
| 309 options.show_last_upload_attempt_time = true; |
| 310 break; |
| 311 } |
| 312 case kOptionShowPendingReports: { |
| 313 options.show_pending_reports = true; |
| 314 break; |
| 315 } |
| 316 case kOptionShowCompletedReports: { |
| 317 options.show_completed_reports = true; |
| 318 break; |
| 319 } |
| 320 case kOptionShowAllReportInfo: { |
| 321 options.show_all_report_info = true; |
| 322 break; |
| 323 } |
| 324 case kOptionShowReport: { |
| 325 UUID uuid; |
| 326 if (!uuid.InitializeFromString(optarg)) { |
| 327 ToolSupport::UsageHint(me, "--show-report requires a UUID"); |
| 328 return EXIT_FAILURE; |
| 329 } |
| 330 options.show_reports.push_back(uuid); |
| 331 break; |
| 332 } |
| 333 case kOptionSetUploadsEnabled: { |
| 334 if (!StringToBool(optarg, &options.set_uploads_enabled)) { |
| 335 ToolSupport::UsageHint(me, "--set-uploads-enabled requires a BOOL"); |
| 336 return EXIT_FAILURE; |
| 337 } |
| 338 options.has_set_uploads_enabled = true; |
| 339 break; |
| 340 } |
| 341 case kOptionSetLastUploadAttemptTime: { |
| 342 options.set_last_upload_attempt_time_string = optarg; |
| 343 break; |
| 344 } |
| 345 case kOptionUTC: { |
| 346 options.utc = true; |
| 347 break; |
| 348 } |
| 349 case kOptionHelp: { |
| 350 Usage(me); |
| 351 return EXIT_SUCCESS; |
| 352 } |
| 353 case kOptionVersion: { |
| 354 ToolSupport::Version(me); |
| 355 return EXIT_SUCCESS; |
| 356 } |
| 357 default: { |
| 358 ToolSupport::UsageHint(me, nullptr); |
| 359 return EXIT_FAILURE; |
| 360 } |
| 361 } |
| 362 } |
| 363 argc -= optind; |
| 364 argv += optind; |
| 365 |
| 366 if (!options.database) { |
| 367 ToolSupport::UsageHint(me, "--database is required"); |
| 368 return EXIT_FAILURE; |
| 369 } |
| 370 |
| 371 // This conversion couldn’t happen in the option-processing loop above because |
| 372 // it depends on options.utc, which may have been set after |
| 373 // options.set_last_upload_attempt_time_string. |
| 374 if (options.set_last_upload_attempt_time_string) { |
| 375 if (!StringToTime(options.set_last_upload_attempt_time_string, |
| 376 &options.set_last_upload_attempt_time, |
| 377 options.utc)) { |
| 378 ToolSupport::UsageHint(me, |
| 379 "--set-last-upload-attempt-time requires a TIME"); |
| 380 return EXIT_FAILURE; |
| 381 } |
| 382 } |
| 383 |
| 384 const size_t show_operations = options.show_client_id + |
| 385 options.show_uploads_enabled + |
| 386 options.show_last_upload_attempt_time + |
| 387 options.show_pending_reports + |
| 388 options.show_completed_reports + |
| 389 options.show_reports.size(); |
| 390 const size_t set_operations = |
| 391 options.has_set_uploads_enabled + |
| 392 (options.set_last_upload_attempt_time_string != nullptr); |
| 393 |
| 394 if (show_operations + set_operations == 0) { |
| 395 ToolSupport::UsageHint(me, "nothing to do"); |
| 396 return EXIT_FAILURE; |
| 397 } |
| 398 |
| 399 scoped_ptr<CrashReportDatabase> database( |
| 400 CrashReportDatabase::Initialize(base::FilePath(options.database))); |
| 401 if (!database) { |
| 402 return EXIT_FAILURE; |
| 403 } |
| 404 |
| 405 Settings* settings = database->GetSettings(); |
| 406 |
| 407 // Handle the “show” options before the “set” options so that when they’re |
| 408 // specified together, the “show” option reflects the initial state. |
| 409 |
| 410 if (options.show_client_id) { |
| 411 UUID client_id; |
| 412 if (!settings->GetClientID(&client_id)) { |
| 413 return EXIT_FAILURE; |
| 414 } |
| 415 |
| 416 const char* prefix = (show_operations > 1) ? "Client ID: " : ""; |
| 417 |
| 418 printf("%s%s\n", prefix, client_id.ToString().c_str()); |
| 419 } |
| 420 |
| 421 if (options.show_uploads_enabled) { |
| 422 bool uploads_enabled; |
| 423 if (!settings->GetUploadsEnabled(&uploads_enabled)) { |
| 424 return EXIT_FAILURE; |
| 425 } |
| 426 |
| 427 const char* prefix = (show_operations > 1) ? "Uploads enabled: " : ""; |
| 428 |
| 429 printf("%s%s\n", prefix, BoolToString(uploads_enabled).c_str()); |
| 430 } |
| 431 |
| 432 if (options.show_last_upload_attempt_time) { |
| 433 time_t last_upload_attempt_time; |
| 434 if (!settings->GetLastUploadAttemptTime(&last_upload_attempt_time)) { |
| 435 return EXIT_FAILURE; |
| 436 } |
| 437 |
| 438 const char* prefix = |
| 439 (show_operations > 1) ? "Last upload attempt time: " : ""; |
| 440 |
| 441 printf("%s%s (%ld)\n", |
| 442 prefix, |
| 443 TimeToString(last_upload_attempt_time, options.utc).c_str(), |
| 444 implicit_cast<long>(last_upload_attempt_time)); |
| 445 } |
| 446 |
| 447 if (options.show_pending_reports) { |
| 448 std::vector<CrashReportDatabase::Report> pending_reports; |
| 449 if (database->GetPendingReports(&pending_reports) != |
| 450 CrashReportDatabase::kNoError) { |
| 451 return EXIT_FAILURE; |
| 452 } |
| 453 |
| 454 if (show_operations > 1) { |
| 455 printf("Pending reports:\n"); |
| 456 } |
| 457 |
| 458 ShowReports(pending_reports, show_operations > 1 ? 2 : 0, options); |
| 459 } |
| 460 |
| 461 if (options.show_completed_reports) { |
| 462 std::vector<CrashReportDatabase::Report> completed_reports; |
| 463 if (database->GetCompletedReports(&completed_reports) != |
| 464 CrashReportDatabase::kNoError) { |
| 465 return EXIT_FAILURE; |
| 466 } |
| 467 |
| 468 if (show_operations > 1) { |
| 469 printf("Completed reports:\n"); |
| 470 } |
| 471 |
| 472 ShowReports(completed_reports, show_operations > 1 ? 2 : 0, options); |
| 473 } |
| 474 |
| 475 for (const UUID& uuid : options.show_reports) { |
| 476 CrashReportDatabase::Report report; |
| 477 CrashReportDatabase::OperationStatus status = |
| 478 database->LookUpCrashReport(uuid, &report); |
| 479 if (status == CrashReportDatabase::kNoError) { |
| 480 if (show_operations > 1) { |
| 481 printf("Report %s:\n", uuid.ToString().c_str()); |
| 482 } |
| 483 ShowReport(report, show_operations > 1 ? 2 : 0, options.utc); |
| 484 } else if (status == CrashReportDatabase::kReportNotFound) { |
| 485 // If only asked to do one thing, a failure to find the single requested |
| 486 // report should result in a failure exit status. |
| 487 if (show_operations + set_operations == 1) { |
| 488 fprintf(stderr, "%s: Report not found\n", me.c_str()); |
| 489 return EXIT_FAILURE; |
| 490 } |
| 491 printf("Report %s not found\n", uuid.ToString().c_str()); |
| 492 } else { |
| 493 return EXIT_FAILURE; |
| 494 } |
| 495 } |
| 496 |
| 497 if (options.has_set_uploads_enabled && |
| 498 !settings->SetUploadsEnabled(options.set_uploads_enabled)) { |
| 499 return EXIT_FAILURE; |
| 500 } |
| 501 |
| 502 if (options.set_last_upload_attempt_time_string && |
| 503 !settings->SetLastUploadAttemptTime( |
| 504 options.set_last_upload_attempt_time)) { |
| 505 return EXIT_FAILURE; |
| 506 } |
| 507 |
| 508 return EXIT_SUCCESS; |
| 509 } |
| 510 |
| 511 } // namespace |
| 512 } // namespace crashpad |
| 513 |
| 514 int main(int argc, char* argv[]) { |
| 515 return crashpad::DatabaseUtilMain(argc, argv); |
| 516 } |
OLD | NEW |