summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2014-03-04 22:55:41 +0100
committerGravatar Martin Szulecki2014-03-04 22:55:41 +0100
commit5838f6d67a46370d82dd2f19588c06562c9fe0de (patch)
tree2c767d51bfbc8082243ad84a291f454382b79298
parentb574a78a99577e88419b8dfe67f863d15ebbcd79 (diff)
downloadlibimobiledevice-5838f6d67a46370d82dd2f19588c06562c9fe0de.tar.gz
libimobiledevice-5838f6d67a46370d82dd2f19588c06562c9fe0de.tar.bz2
Add new "idevicecrashreport" tool to retrieve crash reports/logs from a device
-rw-r--r--tools/Makefile.am8
-rw-r--r--tools/idevicecrashreport.c456
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
74idevicediagnostics_CFLAGS = $(AM_CFLAGS) 74idevicediagnostics_CFLAGS = $(AM_CFLAGS)
75idevicediagnostics_LDFLAGS = $(AM_LDFLAGS) 75idevicediagnostics_LDFLAGS = $(AM_LDFLAGS)
76idevicediagnostics_LDADD = ../src/libimobiledevice.la 76idevicediagnostics_LDADD = ../src/libimobiledevice.la
77
78if !WIN32
79bin_PROGRAMS += idevicecrashreport
80idevicecrashreport_SOURCES = idevicecrashreport.c
81idevicecrashreport_CFLAGS = $(AM_CFLAGS)
82idevicecrashreport_LDFLAGS = $(top_srcdir)/common/libinternalcommon.la $(AM_LDFLAGS)
83idevicecrashreport_LDADD = ../src/libimobiledevice.la
84endif
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
39const char* target_directory = NULL;
40static int extract_raw_crash_reports = 0;
41static int keep_crash_reports = 0;
42
43static int file_exists(const char* path)
44{
45 struct stat tst;
46 return (lstat(path, &tst) == 0);
47}
48
49static 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
87static 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
281static 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
296int 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}