diff options
| author | 2014-03-04 22:55:41 +0100 | |
|---|---|---|
| committer | 2014-03-04 22:55:41 +0100 | |
| commit | 5838f6d67a46370d82dd2f19588c06562c9fe0de (patch) | |
| tree | 2c767d51bfbc8082243ad84a291f454382b79298 | |
| parent | b574a78a99577e88419b8dfe67f863d15ebbcd79 (diff) | |
| download | libimobiledevice-5838f6d67a46370d82dd2f19588c06562c9fe0de.tar.gz libimobiledevice-5838f6d67a46370d82dd2f19588c06562c9fe0de.tar.bz2 | |
Add new "idevicecrashreport" tool to retrieve crash reports/logs from a device
| -rw-r--r-- | tools/Makefile.am | 8 | ||||
| -rw-r--r-- | tools/idevicecrashreport.c | 456 |
2 files changed, 464 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am index c280715..6184fcd 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am | |||
| @@ -74,3 +74,11 @@ idevicediagnostics_SOURCES = idevicediagnostics.c | |||
| 74 | idevicediagnostics_CFLAGS = $(AM_CFLAGS) | 74 | idevicediagnostics_CFLAGS = $(AM_CFLAGS) |
| 75 | idevicediagnostics_LDFLAGS = $(AM_LDFLAGS) | 75 | idevicediagnostics_LDFLAGS = $(AM_LDFLAGS) |
| 76 | idevicediagnostics_LDADD = ../src/libimobiledevice.la | 76 | idevicediagnostics_LDADD = ../src/libimobiledevice.la |
| 77 | |||
| 78 | if !WIN32 | ||
| 79 | bin_PROGRAMS += idevicecrashreport | ||
| 80 | idevicecrashreport_SOURCES = idevicecrashreport.c | ||
| 81 | idevicecrashreport_CFLAGS = $(AM_CFLAGS) | ||
| 82 | idevicecrashreport_LDFLAGS = $(top_srcdir)/common/libinternalcommon.la $(AM_LDFLAGS) | ||
| 83 | idevicecrashreport_LDADD = ../src/libimobiledevice.la | ||
| 84 | endif | ||
diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c new file mode 100644 index 0000000..5b4b186 --- /dev/null +++ b/tools/idevicecrashreport.c | |||
| @@ -0,0 +1,456 @@ | |||
| 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 | #include <stdio.h> | ||
| 24 | #include <stdlib.h> | ||
| 25 | #include <string.h> | ||
| 26 | #include <unistd.h> | ||
| 27 | #include "common/utils.h" | ||
| 28 | |||
| 29 | #include <libimobiledevice/afc.h> | ||
| 30 | #include <libimobiledevice/lockdown.h> | ||
| 31 | #include <libimobiledevice/libimobiledevice.h> | ||
| 32 | #include <plist/plist.h> | ||
| 33 | |||
| 34 | #ifdef WIN32 | ||
| 35 | #define S_IFLNK S_IFREG | ||
| 36 | #define S_IFSOCK S_IFREG | ||
| 37 | #endif | ||
| 38 | |||
| 39 | const char* target_directory = NULL; | ||
| 40 | static int extract_raw_crash_reports = 0; | ||
| 41 | static int keep_crash_reports = 0; | ||
| 42 | |||
| 43 | static int file_exists(const char* path) | ||
| 44 | { | ||
| 45 | struct stat tst; | ||
| 46 | return (lstat(path, &tst) == 0); | ||
| 47 | } | ||
| 48 | |||
| 49 | static int extract_raw_crash_report(const char* filename) { | ||
| 50 | int res = 0; | ||
| 51 | plist_t report = NULL; | ||
| 52 | char* raw = NULL; | ||
| 53 | char* raw_filename = strdup(filename); | ||
| 54 | |||
| 55 | /* create filename with '.crash' extension */ | ||
| 56 | char* p = strrchr(raw_filename, '.'); | ||
| 57 | if ((p == NULL) || (strcmp(p, ".plist") != 0)) { | ||
| 58 | free(raw_filename); | ||
| 59 | return res; | ||
| 60 | } | ||
| 61 | strcpy(p, ".crash"); | ||
| 62 | |||
| 63 | /* read plist crash report */ | ||
| 64 | if (plist_read_from_filename(&report, filename)) { | ||
| 65 | plist_t description_node = plist_dict_get_item(report, "description"); | ||
| 66 | if (description_node && plist_get_node_type(description_node) == PLIST_STRING) { | ||
| 67 | plist_get_string_val(description_node, &raw); | ||
| 68 | |||
| 69 | if (raw != NULL) { | ||
| 70 | /* write file */ | ||
| 71 | buffer_write_to_filename(raw_filename, raw, strlen(raw)); | ||
| 72 | free(raw); | ||
| 73 | res = 1; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | if (report) | ||
| 79 | plist_free(report); | ||
| 80 | |||
| 81 | if (raw_filename) | ||
| 82 | free(raw_filename); | ||
| 83 | |||
| 84 | return res; | ||
| 85 | } | ||
| 86 | |||
| 87 | static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory) | ||
| 88 | { | ||
| 89 | afc_error_t afc_error; | ||
| 90 | int k; | ||
| 91 | int res = -1; | ||
| 92 | int crash_report_count = 0; | ||
| 93 | uint64_t handle; | ||
| 94 | char source_filename[512]; | ||
| 95 | char target_filename[512]; | ||
| 96 | |||
| 97 | if (!afc) | ||
| 98 | return res; | ||
| 99 | |||
| 100 | char** list = NULL; | ||
| 101 | afc_error = afc_read_directory(afc, device_directory, &list); | ||
| 102 | if (afc_error != AFC_E_SUCCESS) { | ||
| 103 | fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory); | ||
| 104 | return res; | ||
| 105 | } | ||
| 106 | |||
| 107 | /* ensure we have a trailing slash */ | ||
| 108 | strcpy(source_filename, device_directory); | ||
| 109 | if (source_filename[strlen(source_filename)-1] != '/') { | ||
| 110 | strcat(source_filename, "/"); | ||
| 111 | } | ||
| 112 | int device_directory_length = strlen(source_filename); | ||
| 113 | |||
| 114 | /* ensure we have a trailing slash */ | ||
| 115 | strcpy(target_filename, host_directory); | ||
| 116 | if (target_filename[strlen(target_filename)-1] != '/') { | ||
| 117 | strcat(target_filename, "/"); | ||
| 118 | } | ||
| 119 | int host_directory_length = strlen(target_filename); | ||
| 120 | |||
| 121 | /* loop over file entries */ | ||
| 122 | for (k = 0; list[k]; k++) { | ||
| 123 | if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) { | ||
| 124 | continue; | ||
| 125 | } | ||
| 126 | |||
| 127 | char **fileinfo = NULL; | ||
| 128 | struct stat stbuf; | ||
| 129 | stbuf.st_size = 0; | ||
| 130 | |||
| 131 | /* assemble absolute source filename */ | ||
| 132 | strcpy(((char*)source_filename) + device_directory_length, list[k]); | ||
| 133 | |||
| 134 | /* assemble absolute target filename */ | ||
| 135 | char* p = strrchr(list[k], '.'); | ||
| 136 | if (p != NULL && !strncmp(p, ".synced", 7)) { | ||
| 137 | /* make sure to strip ".synced" extension as seen on iOS 5 */ | ||
| 138 | strncpy(((char*)target_filename) + host_directory_length, list[k], strlen(list[k]) - 7); | ||
| 139 | } else { | ||
| 140 | strcpy(((char*)target_filename) + host_directory_length, list[k]); | ||
| 141 | } | ||
| 142 | |||
| 143 | /* get file information */ | ||
| 144 | afc_get_file_info(afc, source_filename, &fileinfo); | ||
| 145 | if (!fileinfo) { | ||
| 146 | printf("Failed to read information for '%s'. Skipping...\n", source_filename); | ||
| 147 | continue; | ||
| 148 | } | ||
| 149 | |||
| 150 | /* parse file information */ | ||
| 151 | int i; | ||
| 152 | for (i = 0; fileinfo[i]; i+=2) { | ||
| 153 | if (!strcmp(fileinfo[i], "st_size")) { | ||
| 154 | stbuf.st_size = atoll(fileinfo[i+1]); | ||
| 155 | } else if (!strcmp(fileinfo[i], "st_ifmt")) { | ||
| 156 | if (!strcmp(fileinfo[i+1], "S_IFREG")) { | ||
| 157 | stbuf.st_mode = S_IFREG; | ||
| 158 | } else if (!strcmp(fileinfo[i+1], "S_IFDIR")) { | ||
| 159 | stbuf.st_mode = S_IFDIR; | ||
| 160 | } else if (!strcmp(fileinfo[i+1], "S_IFLNK")) { | ||
| 161 | stbuf.st_mode = S_IFLNK; | ||
| 162 | } else if (!strcmp(fileinfo[i+1], "S_IFBLK")) { | ||
| 163 | stbuf.st_mode = S_IFBLK; | ||
| 164 | } else if (!strcmp(fileinfo[i+1], "S_IFCHR")) { | ||
| 165 | stbuf.st_mode = S_IFCHR; | ||
| 166 | } else if (!strcmp(fileinfo[i+1], "S_IFIFO")) { | ||
| 167 | stbuf.st_mode = S_IFIFO; | ||
| 168 | } else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) { | ||
| 169 | stbuf.st_mode = S_IFSOCK; | ||
| 170 | } | ||
| 171 | } else if (!strcmp(fileinfo[i], "st_nlink")) { | ||
| 172 | stbuf.st_nlink = atoi(fileinfo[i+1]); | ||
| 173 | } else if (!strcmp(fileinfo[i], "st_mtime")) { | ||
| 174 | stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000); | ||
| 175 | } else if (!strcmp(fileinfo[i], "LinkTarget")) { | ||
| 176 | /* report latest crash report filename */ | ||
| 177 | printf("Link: %s\n", (char*)target_filename + strlen(target_directory)); | ||
| 178 | |||
| 179 | /* remove any previous symlink */ | ||
| 180 | if (file_exists(target_filename)) { | ||
| 181 | remove(target_filename); | ||
| 182 | } | ||
| 183 | |||
| 184 | #ifndef WIN32 | ||
| 185 | /* use relative filename */ | ||
| 186 | char* b = strrchr(fileinfo[i+1], '/'); | ||
| 187 | if (b == NULL) { | ||
| 188 | b = fileinfo[i+1]; | ||
| 189 | } else { | ||
| 190 | b++; | ||
| 191 | } | ||
| 192 | |||
| 193 | /* create a symlink pointing to latest log */ | ||
| 194 | symlink(b, target_filename); | ||
| 195 | #endif | ||
| 196 | |||
| 197 | if (!keep_crash_reports) | ||
| 198 | afc_remove_path(afc, source_filename); | ||
| 199 | |||
| 200 | res = 0; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | /* free file information */ | ||
| 205 | afc_dictionary_free(fileinfo); | ||
| 206 | |||
| 207 | /* recurse into child directories */ | ||
| 208 | if (S_ISDIR(stbuf.st_mode)) { | ||
| 209 | #ifdef WIN32 | ||
| 210 | mkdir(target_filename); | ||
| 211 | #else | ||
| 212 | mkdir(target_filename, 0755); | ||
| 213 | #endif | ||
| 214 | res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename); | ||
| 215 | |||
| 216 | /* remove directory from device */ | ||
| 217 | if (!keep_crash_reports) | ||
| 218 | afc_remove_path(afc, source_filename); | ||
| 219 | } else if (S_ISREG(stbuf.st_mode)) { | ||
| 220 | /* copy file to host */ | ||
| 221 | afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle); | ||
| 222 | if(afc_error != AFC_E_SUCCESS) { | ||
| 223 | if (afc_error == AFC_E_OBJECT_NOT_FOUND) { | ||
| 224 | continue; | ||
| 225 | } | ||
| 226 | fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error); | ||
| 227 | continue; | ||
| 228 | } | ||
| 229 | |||
| 230 | FILE* output = fopen(target_filename, "wb"); | ||
| 231 | if(output == NULL) { | ||
| 232 | fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename); | ||
| 233 | afc_file_close(afc, handle); | ||
| 234 | continue; | ||
| 235 | } | ||
| 236 | |||
| 237 | printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory)); | ||
| 238 | |||
| 239 | uint32_t bytes_read = 0; | ||
| 240 | uint32_t bytes_total = 0; | ||
| 241 | unsigned char data[0x1000]; | ||
| 242 | |||
| 243 | afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); | ||
| 244 | while(afc_error == AFC_E_SUCCESS && bytes_read > 0) { | ||
| 245 | fwrite(data, 1, bytes_read, output); | ||
| 246 | bytes_total += bytes_read; | ||
| 247 | afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); | ||
| 248 | } | ||
| 249 | afc_file_close(afc, handle); | ||
| 250 | fclose(output); | ||
| 251 | |||
| 252 | if ((uint32_t)stbuf.st_size != bytes_total) { | ||
| 253 | fprintf(stderr, "File size mismatch. Skipping...\n"); | ||
| 254 | continue; | ||
| 255 | } | ||
| 256 | |||
| 257 | /* remove file from device */ | ||
| 258 | if (!keep_crash_reports) { | ||
| 259 | afc_remove_path(afc, source_filename); | ||
| 260 | } | ||
| 261 | |||
| 262 | /* extract raw crash information into separate '.crash' file */ | ||
| 263 | if (extract_raw_crash_reports) { | ||
| 264 | extract_raw_crash_report(target_filename); | ||
| 265 | } | ||
| 266 | |||
| 267 | crash_report_count++; | ||
| 268 | |||
| 269 | res = 0; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | afc_dictionary_free(list); | ||
| 273 | |||
| 274 | /* no reports, no error */ | ||
| 275 | if (crash_report_count == 0) | ||
| 276 | res = 0; | ||
| 277 | |||
| 278 | return res; | ||
| 279 | } | ||
| 280 | |||
| 281 | static void print_usage(int argc, char **argv) | ||
| 282 | { | ||
| 283 | char *name = NULL; | ||
| 284 | |||
| 285 | name = strrchr(argv[0], '/'); | ||
| 286 | printf("Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0])); | ||
| 287 | printf("Move crash reports from device to a local DIRECTORY.\n\n"); | ||
| 288 | printf(" -e, --extract\t\textract raw crash report into separate '.crash' file\n"); | ||
| 289 | printf(" -k, --keep\t\tcopy but do not remove crash reports from device\n"); | ||
| 290 | printf(" -d, --debug\t\tenable communication debugging\n"); | ||
| 291 | printf(" -u, --udid UDID\ttarget specific device by its 40-digit device UDID\n"); | ||
| 292 | printf(" -h, --help\t\tprints usage information\n"); | ||
| 293 | printf("\n"); | ||
| 294 | } | ||
| 295 | |||
| 296 | int main(int argc, char* argv[]) { | ||
| 297 | idevice_t device = NULL; | ||
| 298 | lockdownd_client_t lockdownd = NULL; | ||
| 299 | afc_client_t afc = NULL; | ||
| 300 | |||
| 301 | idevice_error_t device_error = IDEVICE_E_SUCCESS; | ||
| 302 | lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS; | ||
| 303 | afc_error_t afc_error = AFC_E_SUCCESS; | ||
| 304 | |||
| 305 | int i; | ||
| 306 | const char* udid = NULL; | ||
| 307 | |||
| 308 | /* parse cmdline args */ | ||
| 309 | for (i = 1; i < argc; i++) { | ||
| 310 | if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { | ||
| 311 | idevice_set_debug_level(1); | ||
| 312 | continue; | ||
| 313 | } | ||
| 314 | else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--udid")) { | ||
| 315 | i++; | ||
| 316 | if (!argv[i] || (strlen(argv[i]) != 40)) { | ||
| 317 | print_usage(argc, argv); | ||
| 318 | return 0; | ||
| 319 | } | ||
| 320 | udid = argv[i]; | ||
| 321 | continue; | ||
| 322 | } | ||
| 323 | else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { | ||
| 324 | print_usage(argc, argv); | ||
| 325 | return 0; | ||
| 326 | } | ||
| 327 | else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--extract")) { | ||
| 328 | extract_raw_crash_reports = 1; | ||
| 329 | continue; | ||
| 330 | } | ||
| 331 | else if (!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keep")) { | ||
| 332 | keep_crash_reports = 1; | ||
| 333 | continue; | ||
| 334 | } | ||
| 335 | else if (target_directory == NULL) { | ||
| 336 | target_directory = argv[i]; | ||
| 337 | continue; | ||
| 338 | } | ||
| 339 | else { | ||
| 340 | print_usage(argc, argv); | ||
| 341 | return 0; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | /* ensure a target directory was supplied */ | ||
| 346 | if (!target_directory) { | ||
| 347 | print_usage(argc, argv); | ||
| 348 | return 0; | ||
| 349 | } | ||
| 350 | |||
| 351 | /* check if target directory exists */ | ||
| 352 | if (!file_exists(target_directory)) { | ||
| 353 | fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory); | ||
| 354 | print_usage(argc, argv); | ||
| 355 | return 0; | ||
| 356 | } | ||
| 357 | |||
| 358 | device_error = idevice_new(&device, udid); | ||
| 359 | if (device_error != IDEVICE_E_SUCCESS) { | ||
| 360 | if (udid) { | ||
| 361 | printf("No device found with udid %s, is it plugged in?\n", udid); | ||
| 362 | } else { | ||
| 363 | printf("No device found, is it plugged in?\n"); | ||
| 364 | } | ||
| 365 | return -1; | ||
| 366 | } | ||
| 367 | |||
| 368 | lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, "idevicecrashreport"); | ||
| 369 | if (lockdownd_error != LOCKDOWN_E_SUCCESS) { | ||
| 370 | idevice_free(device); | ||
| 371 | return -1; | ||
| 372 | } | ||
| 373 | |||
| 374 | /* start crash log mover service */ | ||
| 375 | lockdownd_service_descriptor_t service = NULL; | ||
| 376 | lockdownd_error = lockdownd_start_service(lockdownd, "com.apple.crashreportmover", &service); | ||
| 377 | if (lockdownd_error != LOCKDOWN_E_SUCCESS) { | ||
| 378 | lockdownd_client_free(lockdownd); | ||
| 379 | idevice_free(device); | ||
| 380 | return -1; | ||
| 381 | } | ||
| 382 | |||
| 383 | /* trigger move operation on device */ | ||
| 384 | idevice_connection_t connection = NULL; | ||
| 385 | device_error = idevice_connect(device, service->port, &connection); | ||
| 386 | if(device_error != IDEVICE_E_SUCCESS) { | ||
| 387 | lockdownd_client_free(lockdownd); | ||
| 388 | idevice_free(device); | ||
| 389 | return -1; | ||
| 390 | } | ||
| 391 | |||
| 392 | /* read "ping" message which indicates the crash logs have been moved to a safe harbor */ | ||
| 393 | char *ping = malloc(4); | ||
| 394 | int attempts = 0; | ||
| 395 | while ((strncmp(ping, "ping", 4) != 0) && (attempts > 10)) { | ||
| 396 | uint32_t bytes = 0; | ||
| 397 | device_error = idevice_connection_receive_timeout(connection, ping, 4, &bytes, 2000); | ||
| 398 | if ((bytes == 0) && (device_error == IDEVICE_E_SUCCESS)) { | ||
| 399 | attempts++; | ||
| 400 | continue; | ||
| 401 | } else if (device_error < 0) { | ||
| 402 | fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted.\n"); | ||
| 403 | break; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | idevice_disconnect(connection); | ||
| 407 | free(ping); | ||
| 408 | |||
| 409 | if (service) { | ||
| 410 | lockdownd_service_descriptor_free(service); | ||
| 411 | service = NULL; | ||
| 412 | } | ||
| 413 | |||
| 414 | if (device_error != IDEVICE_E_SUCCESS || attempts > 10) { | ||
| 415 | fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n"); | ||
| 416 | lockdownd_client_free(lockdownd); | ||
| 417 | idevice_free(device); | ||
| 418 | return -1; | ||
| 419 | } | ||
| 420 | |||
| 421 | lockdownd_error = lockdownd_start_service(lockdownd, "com.apple.crashreportcopymobile", &service); | ||
| 422 | if (lockdownd_error != LOCKDOWN_E_SUCCESS) { | ||
| 423 | lockdownd_client_free(lockdownd); | ||
| 424 | idevice_free(device); | ||
| 425 | return -1; | ||
| 426 | } | ||
| 427 | lockdownd_client_free(lockdownd); | ||
| 428 | |||
| 429 | afc = NULL; | ||
| 430 | afc_error = afc_client_new(device, service, &afc); | ||
| 431 | if(afc_error != AFC_E_SUCCESS) { | ||
| 432 | lockdownd_client_free(lockdownd); | ||
| 433 | idevice_free(device); | ||
| 434 | return -1; | ||
| 435 | } | ||
| 436 | |||
| 437 | if (service) { | ||
| 438 | lockdownd_service_descriptor_free(service); | ||
| 439 | service = NULL; | ||
| 440 | } | ||
| 441 | |||
| 442 | /* recursively copy crash reports from the device to a local directory */ | ||
| 443 | if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory) < 0) { | ||
| 444 | fprintf(stderr, "ERROR: Failed to get crash reports from device.\n"); | ||
| 445 | afc_client_free(afc); | ||
| 446 | idevice_free(device); | ||
| 447 | return -1; | ||
| 448 | } | ||
| 449 | |||
| 450 | printf("Done.\n"); | ||
| 451 | |||
| 452 | afc_client_free(afc); | ||
| 453 | idevice_free(device); | ||
| 454 | |||
| 455 | return 0; | ||
| 456 | } | ||
