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) { | |
Robert Sesek
2015/03/19 21:00:58
It would be nice to have tests for these functions
Mark Mentovai
2015/03/19 22:03:01
Robert Sesek wrote:
| |
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 struct option long_options[] = { | |
Robert Sesek
2015/03/19 21:00:58
You don't use |struct| for other C ones, so don't
Mark Mentovai
2015/03/19 22:03:01
Robert Sesek wrote:
| |
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 |