summaryrefslogtreecommitdiffstats
path: root/tools/idevicecrashreport.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/idevicecrashreport.c')
-rw-r--r--tools/idevicecrashreport.c529
1 files changed, 529 insertions, 0 deletions
diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c
new file mode 100644
index 0000000..09bd537
--- /dev/null
+++ b/tools/idevicecrashreport.c
@@ -0,0 +1,529 @@
1/*
2 * idevicecrashreport.c
3 * Simple utility to move crash reports from a device to a local directory.
4 *
5 * Copyright (c) 2014 Martin Szulecki. All Rights Reserved.
6 * Copyright (c) 2014 Nikias Bassen. All Rights Reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
27#define TOOL_NAME "idevicecrashreport"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33#include <getopt.h>
34#ifndef WIN32
35#include <signal.h>
36#endif
37#include <libimobiledevice-glue/utils.h>
38
39#include <libimobiledevice/libimobiledevice.h>
40#include <libimobiledevice/lockdown.h>
41#include <libimobiledevice/service.h>
42#include <libimobiledevice/afc.h>
43#include <plist/plist.h>
44
45#ifdef WIN32
46#include <windows.h>
47#define S_IFLNK S_IFREG
48#define S_IFSOCK S_IFREG
49#endif
50
51#define CRASH_REPORT_MOVER_SERVICE "com.apple.crashreportmover"
52#define CRASH_REPORT_COPY_MOBILE_SERVICE "com.apple.crashreportcopymobile"
53
54const char* target_directory = NULL;
55static int extract_raw_crash_reports = 0;
56static int keep_crash_reports = 0;
57
58static int file_exists(const char* path)
59{
60 struct stat tst;
61#ifdef WIN32
62 return (stat(path, &tst) == 0);
63#else
64 return (lstat(path, &tst) == 0);
65#endif
66}
67
68static int extract_raw_crash_report(const char* filename)
69{
70 int res = 0;
71 plist_t report = NULL;
72 char* raw = NULL;
73 char* raw_filename = strdup(filename);
74
75 /* create filename with '.crash' extension */
76 char* p = strrchr(raw_filename, '.');
77 if ((p == NULL) || (strcmp(p, ".plist") != 0)) {
78 free(raw_filename);
79 return res;
80 }
81 strcpy(p, ".crash");
82
83 /* read plist crash report */
84 if (plist_read_from_file(filename, &report, NULL)) {
85 plist_t description_node = plist_dict_get_item(report, "description");
86 if (description_node && plist_get_node_type(description_node) == PLIST_STRING) {
87 plist_get_string_val(description_node, &raw);
88
89 if (raw != NULL) {
90 /* write file */
91 buffer_write_to_filename(raw_filename, raw, strlen(raw));
92 free(raw);
93 res = 1;
94 }
95 }
96 }
97
98 if (report)
99 plist_free(report);
100
101 if (raw_filename)
102 free(raw_filename);
103
104 return res;
105}
106
107static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory, const char* filename_filter)
108{
109 afc_error_t afc_error;
110 int k;
111 int res = -1;
112 int crash_report_count = 0;
113 uint64_t handle;
114 char source_filename[512];
115 char target_filename[512];
116
117 if (!afc)
118 return res;
119
120 char** list = NULL;
121 afc_error = afc_read_directory(afc, device_directory, &list);
122 if (afc_error != AFC_E_SUCCESS) {
123 fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory);
124 return res;
125 }
126
127 /* ensure we have a trailing slash */
128 strcpy(source_filename, device_directory);
129 if (source_filename[strlen(source_filename)-1] != '/') {
130 strcat(source_filename, "/");
131 }
132 int device_directory_length = strlen(source_filename);
133
134 /* ensure we have a trailing slash */
135 strcpy(target_filename, host_directory);
136 if (target_filename[strlen(target_filename)-1] != '/') {
137 strcat(target_filename, "/");
138 }
139 int host_directory_length = strlen(target_filename);
140
141 /* loop over file entries */
142 for (k = 0; list[k]; k++) {
143 if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) {
144 continue;
145 }
146
147 char **fileinfo = NULL;
148 struct stat stbuf;
149 memset(&stbuf, '\0', sizeof(struct stat));
150
151 /* assemble absolute source filename */
152 strcpy(((char*)source_filename) + device_directory_length, list[k]);
153
154 /* assemble absolute target filename */
155#ifdef WIN32
156 /* replace every ':' with '-' since ':' is an illegal character for file names in windows */
157 char* current_pos = strchr(list[k], ':');
158 while (current_pos) {
159 *current_pos = '-';
160 current_pos = strchr(current_pos, ':');
161 }
162#endif
163 char* p = strrchr(list[k], '.');
164 if (p != NULL && !strncmp(p, ".synced", 7)) {
165 /* make sure to strip ".synced" extension as seen on iOS 5 */
166 size_t newlen = p - list[k];
167 strncpy(((char*)target_filename) + host_directory_length, list[k], newlen);
168 target_filename[host_directory_length + newlen] = '\0';
169 } else {
170 strcpy(((char*)target_filename) + host_directory_length, list[k]);
171 }
172
173 /* get file information */
174 afc_get_file_info(afc, source_filename, &fileinfo);
175 if (!fileinfo) {
176 printf("Failed to read information for '%s'. Skipping...\n", source_filename);
177 continue;
178 }
179
180 /* parse file information */
181 int i;
182 for (i = 0; fileinfo[i]; i+=2) {
183 if (!strcmp(fileinfo[i], "st_size")) {
184 stbuf.st_size = atoll(fileinfo[i+1]);
185 } else if (!strcmp(fileinfo[i], "st_ifmt")) {
186 if (!strcmp(fileinfo[i+1], "S_IFREG")) {
187 stbuf.st_mode = S_IFREG;
188 } else if (!strcmp(fileinfo[i+1], "S_IFDIR")) {
189 stbuf.st_mode = S_IFDIR;
190 } else if (!strcmp(fileinfo[i+1], "S_IFLNK")) {
191 stbuf.st_mode = S_IFLNK;
192 } else if (!strcmp(fileinfo[i+1], "S_IFBLK")) {
193 stbuf.st_mode = S_IFBLK;
194 } else if (!strcmp(fileinfo[i+1], "S_IFCHR")) {
195 stbuf.st_mode = S_IFCHR;
196 } else if (!strcmp(fileinfo[i+1], "S_IFIFO")) {
197 stbuf.st_mode = S_IFIFO;
198 } else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) {
199 stbuf.st_mode = S_IFSOCK;
200 }
201 } else if (!strcmp(fileinfo[i], "st_nlink")) {
202 stbuf.st_nlink = atoi(fileinfo[i+1]);
203 } else if (!strcmp(fileinfo[i], "st_mtime")) {
204 stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000);
205 } else if (!strcmp(fileinfo[i], "LinkTarget")) {
206 /* report latest crash report filename */
207 printf("Link: %s\n", (char*)target_filename + strlen(target_directory));
208
209 /* remove any previous symlink */
210 if (file_exists(target_filename)) {
211 remove(target_filename);
212 }
213
214#ifndef WIN32
215 /* use relative filename */
216 char* b = strrchr(fileinfo[i+1], '/');
217 if (b == NULL) {
218 b = fileinfo[i+1];
219 } else {
220 b++;
221 }
222
223 /* create a symlink pointing to latest log */
224 if (symlink(b, target_filename) < 0) {
225 fprintf(stderr, "Can't create symlink to %s\n", b);
226 }
227#endif
228
229 if (!keep_crash_reports)
230 afc_remove_path(afc, source_filename);
231
232 res = 0;
233 }
234 }
235
236 /* free file information */
237 afc_dictionary_free(fileinfo);
238
239 /* recurse into child directories */
240 if (S_ISDIR(stbuf.st_mode)) {
241#ifdef WIN32
242 mkdir(target_filename);
243#else
244 mkdir(target_filename, 0755);
245#endif
246 res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename, filename_filter);
247
248 /* remove directory from device */
249 if (!keep_crash_reports)
250 afc_remove_path(afc, source_filename);
251 } else if (S_ISREG(stbuf.st_mode)) {
252 if (filename_filter != NULL && strstr(source_filename, filename_filter) == NULL) {
253 continue;
254 }
255
256 /* copy file to host */
257 afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle);
258 if(afc_error != AFC_E_SUCCESS) {
259 if (afc_error == AFC_E_OBJECT_NOT_FOUND) {
260 continue;
261 }
262 fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error);
263 continue;
264 }
265
266 FILE* output = fopen(target_filename, "wb");
267 if(output == NULL) {
268 fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename);
269 afc_file_close(afc, handle);
270 continue;
271 }
272
273 printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory));
274
275 uint32_t bytes_read = 0;
276 uint32_t bytes_total = 0;
277 unsigned char data[0x1000];
278
279 afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
280 while(afc_error == AFC_E_SUCCESS && bytes_read > 0) {
281 fwrite(data, 1, bytes_read, output);
282 bytes_total += bytes_read;
283 afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
284 }
285 afc_file_close(afc, handle);
286 fclose(output);
287
288 if ((uint32_t)stbuf.st_size != bytes_total) {
289 fprintf(stderr, "File size mismatch. Skipping...\n");
290 continue;
291 }
292
293 /* remove file from device */
294 if (!keep_crash_reports) {
295 afc_remove_path(afc, source_filename);
296 }
297
298 /* extract raw crash information into separate '.crash' file */
299 if (extract_raw_crash_reports) {
300 extract_raw_crash_report(target_filename);
301 }
302
303 crash_report_count++;
304
305 res = 0;
306 }
307 }
308 afc_dictionary_free(list);
309
310 /* no reports, no error */
311 if (crash_report_count == 0)
312 res = 0;
313
314 return res;
315}
316
317static void print_usage(int argc, char **argv, int is_error)
318{
319 char *name = strrchr(argv[0], '/');
320 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0]));
321 fprintf(is_error ? stderr : stdout,
322 "\n"
323 "Move crash reports from device to a local DIRECTORY.\n"
324 "\n"
325 "OPTIONS:\n"
326 " -u, --udid UDID target specific device by UDID\n"
327 " -n, --network connect to network device\n"
328 " -e, --extract extract raw crash report into separate '.crash' file\n"
329 " -k, --keep copy but do not remove crash reports from device\n"
330 " -d, --debug enable communication debugging\n"
331 " -f, --filter NAME filter crash reports by NAME (case sensitive)\n"
332 " -h, --help prints usage information\n"
333 " -v, --version prints version information\n"
334 "\n"
335 "Homepage: <" PACKAGE_URL ">\n"
336 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
337 );
338}
339
340int main(int argc, char* argv[])
341{
342 idevice_t device = NULL;
343 lockdownd_client_t lockdownd = NULL;
344 afc_client_t afc = NULL;
345
346 idevice_error_t device_error = IDEVICE_E_SUCCESS;
347 lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS;
348 afc_error_t afc_error = AFC_E_SUCCESS;
349
350 const char* udid = NULL;
351 int use_network = 0;
352 const char* filename_filter = NULL;
353
354 int c = 0;
355 const struct option longopts[] = {
356 { "debug", no_argument, NULL, 'd' },
357 { "help", no_argument, NULL, 'h' },
358 { "udid", required_argument, NULL, 'u' },
359 { "network", no_argument, NULL, 'n' },
360 { "version", no_argument, NULL, 'v' },
361 { "filter", required_argument, NULL, 'f' },
362 { "extract", no_argument, NULL, 'e' },
363 { "keep", no_argument, NULL, 'k' },
364 { NULL, 0, NULL, 0}
365 };
366
367#ifndef WIN32
368 signal(SIGPIPE, SIG_IGN);
369#endif
370
371 /* parse cmdline args */
372 while ((c = getopt_long(argc, argv, "dhu:nvf:ek", longopts, NULL)) != -1) {
373 switch (c) {
374 case 'd':
375 idevice_set_debug_level(1);
376 break;
377 case 'u':
378 if (!*optarg) {
379 fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
380 print_usage(argc, argv, 1);
381 return 2;
382 }
383 udid = optarg;
384 break;
385 case 'n':
386 use_network = 1;
387 break;
388 case 'h':
389 print_usage(argc, argv, 0);
390 return 0;
391 case 'v':
392 printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
393 return 0;
394 case 'f':
395 if (!*optarg) {
396 fprintf(stderr, "ERROR: filter argument must not be empty!\n");
397 print_usage(argc, argv, 1);
398 return 2;
399 }
400 filename_filter = optarg;
401 break;
402 case 'e':
403 extract_raw_crash_reports = 1;
404 break;
405 case 'k':
406 keep_crash_reports = 1;
407 break;
408 default:
409 print_usage(argc, argv, 1);
410 return 2;
411 }
412 }
413 argc -= optind;
414 argv += optind;
415
416 /* ensure a target directory was supplied */
417 if (!argv[0]) {
418 fprintf(stderr, "ERROR: missing target directory.\n");
419 print_usage(argc+optind, argv-optind, 1);
420 return 2;
421 }
422 target_directory = argv[0];
423
424 /* check if target directory exists */
425 if (!file_exists(target_directory)) {
426 fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory);
427 return 1;
428 }
429
430 device_error = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
431 if (device_error != IDEVICE_E_SUCCESS) {
432 if (udid) {
433 printf("No device found with udid %s.\n", udid);
434 } else {
435 printf("No device found.\n");
436 }
437 return -1;
438 }
439
440 lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, TOOL_NAME);
441 if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
442 fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lockdownd_error);
443 idevice_free(device);
444 return -1;
445 }
446
447 /* start crash log mover service */
448 lockdownd_service_descriptor_t service = NULL;
449 lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_MOVER_SERVICE, &service);
450 if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
451 fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_MOVER_SERVICE, lockdownd_strerror(lockdownd_error));
452 lockdownd_client_free(lockdownd);
453 idevice_free(device);
454 return -1;
455 }
456
457 /* trigger move operation on device */
458 service_client_t svcmove = NULL;
459 service_error_t service_error = service_client_new(device, service, &svcmove);
460 lockdownd_service_descriptor_free(service);
461 service = NULL;
462 if (service_error != SERVICE_E_SUCCESS) {
463 lockdownd_client_free(lockdownd);
464 idevice_free(device);
465 return -1;
466 }
467
468 /* read "ping" message which indicates the crash logs have been moved to a safe harbor */
469 char *ping = malloc(4);
470 memset(ping, '\0', 4);
471 int attempts = 0;
472 while ((strncmp(ping, "ping", 4) != 0) && (attempts < 10)) {
473 uint32_t bytes = 0;
474 service_error = service_receive_with_timeout(svcmove, ping, 4, &bytes, 2000);
475 if (service_error == SERVICE_E_SUCCESS || service_error == SERVICE_E_TIMEOUT) {
476 attempts++;
477 continue;
478 }
479
480 fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted (%d).\n", service_error);
481 break;
482 }
483 service_client_free(svcmove);
484 free(ping);
485
486 if (device_error != IDEVICE_E_SUCCESS || attempts > 10) {
487 fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n");
488 lockdownd_client_free(lockdownd);
489 idevice_free(device);
490 return -1;
491 }
492
493 lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_COPY_MOBILE_SERVICE, &service);
494 if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
495 fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_COPY_MOBILE_SERVICE, lockdownd_strerror(lockdownd_error));
496 lockdownd_client_free(lockdownd);
497 idevice_free(device);
498 return -1;
499 }
500 lockdownd_client_free(lockdownd);
501
502 afc = NULL;
503 afc_error = afc_client_new(device, service, &afc);
504 if(afc_error != AFC_E_SUCCESS) {
505 lockdownd_client_free(lockdownd);
506 idevice_free(device);
507 return -1;
508 }
509
510 if (service) {
511 lockdownd_service_descriptor_free(service);
512 service = NULL;
513 }
514
515 /* recursively copy crash reports from the device to a local directory */
516 if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory, filename_filter) < 0) {
517 fprintf(stderr, "ERROR: Failed to get crash reports from device.\n");
518 afc_client_free(afc);
519 idevice_free(device);
520 return -1;
521 }
522
523 printf("Done.\n");
524
525 afc_client_free(afc);
526 idevice_free(device);
527
528 return 0;
529}