diff options
Diffstat (limited to 'tools')
| -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  idevicediagnostics_CFLAGS = $(AM_CFLAGS)  idevicediagnostics_LDFLAGS = $(AM_LDFLAGS)  idevicediagnostics_LDADD = ../src/libimobiledevice.la + +if !WIN32 +bin_PROGRAMS += idevicecrashreport +idevicecrashreport_SOURCES = idevicecrashreport.c +idevicecrashreport_CFLAGS = $(AM_CFLAGS) +idevicecrashreport_LDFLAGS = $(top_srcdir)/common/libinternalcommon.la $(AM_LDFLAGS) +idevicecrashreport_LDADD = ../src/libimobiledevice.la +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 @@ +/* + * idevicecrashreport.c + * Simple utility to move crash reports from a device to a local directory. + * + * Copyright (c) 2014 Martin Szulecki. All Rights Reserved. + * Copyright (c) 2014 Nikias Bassen. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + *  + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + *  + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA  + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "common/utils.h" + +#include <libimobiledevice/afc.h> +#include <libimobiledevice/lockdown.h> +#include <libimobiledevice/libimobiledevice.h> +#include <plist/plist.h> + +#ifdef WIN32 +#define S_IFLNK S_IFREG +#define S_IFSOCK S_IFREG +#endif + +const char* target_directory = NULL; +static int extract_raw_crash_reports = 0; +static int keep_crash_reports = 0; + +static int file_exists(const char* path) +{ +	struct stat tst; +	return (lstat(path, &tst) == 0); +} + +static int extract_raw_crash_report(const char* filename) { +	int res = 0; +	plist_t report = NULL; +	char* raw = NULL; +	char* raw_filename = strdup(filename); + +	/* create filename with '.crash' extension */ +	char* p = strrchr(raw_filename, '.'); +	if ((p == NULL) || (strcmp(p, ".plist") != 0)) { +		free(raw_filename); +		return res; +	} +	strcpy(p, ".crash"); + +	/* read plist crash report */ +	if (plist_read_from_filename(&report, filename)) { +		plist_t description_node = plist_dict_get_item(report, "description"); +		if (description_node && plist_get_node_type(description_node) == PLIST_STRING) { +			plist_get_string_val(description_node, &raw); + +			if (raw != NULL) { +				/* write file */ +				buffer_write_to_filename(raw_filename, raw, strlen(raw)); +				free(raw); +				res = 1; +			} +		} +	} + +	if (report) +		plist_free(report); + +	if (raw_filename) +		free(raw_filename); + +	return res; +} + +static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory) +{ +	afc_error_t afc_error; +	int k; +	int res = -1; +	int crash_report_count = 0; +	uint64_t handle; +	char source_filename[512]; +	char target_filename[512]; + +	if (!afc) +		return res; + +	char** list = NULL; +	afc_error = afc_read_directory(afc, device_directory, &list); +	if (afc_error != AFC_E_SUCCESS) { +		fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory); +		return res; +	} + +	/* ensure we have a trailing slash */ +	strcpy(source_filename, device_directory); +	if (source_filename[strlen(source_filename)-1] != '/') { +		strcat(source_filename, "/"); +	} +	int device_directory_length = strlen(source_filename); + +	/* ensure we have a trailing slash */ +	strcpy(target_filename, host_directory); +	if (target_filename[strlen(target_filename)-1] != '/') { +		strcat(target_filename, "/"); +	} +	int host_directory_length = strlen(target_filename); + +	/* loop over file entries */ +	for (k = 0; list[k]; k++) { +		if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) { +			continue; +		} + +		char **fileinfo = NULL; +		struct stat stbuf; +		stbuf.st_size = 0; + +		/* assemble absolute source filename */ +		strcpy(((char*)source_filename) + device_directory_length, list[k]); + +		/* assemble absolute target filename */ +		char* p = strrchr(list[k], '.'); +		if (p != NULL && !strncmp(p, ".synced", 7)) { +			/* make sure to strip ".synced" extension as seen on iOS 5 */ +			strncpy(((char*)target_filename) + host_directory_length, list[k], strlen(list[k]) - 7); +		} else { +			strcpy(((char*)target_filename) + host_directory_length, list[k]); +		} + +		/* get file information */ +		afc_get_file_info(afc, source_filename, &fileinfo); +		if (!fileinfo) { +			printf("Failed to read information for '%s'. Skipping...\n", source_filename); +			continue; +		} + +		/* parse file information */ +		int i; +		for (i = 0; fileinfo[i]; i+=2) { +			if (!strcmp(fileinfo[i], "st_size")) { +				stbuf.st_size = atoll(fileinfo[i+1]); +			} else if (!strcmp(fileinfo[i], "st_ifmt")) { +				if (!strcmp(fileinfo[i+1], "S_IFREG")) { +					stbuf.st_mode = S_IFREG; +				} else if (!strcmp(fileinfo[i+1], "S_IFDIR")) { +					stbuf.st_mode = S_IFDIR; +				} else if (!strcmp(fileinfo[i+1], "S_IFLNK")) { +					stbuf.st_mode = S_IFLNK; +				} else if (!strcmp(fileinfo[i+1], "S_IFBLK")) { +					stbuf.st_mode = S_IFBLK; +				} else if (!strcmp(fileinfo[i+1], "S_IFCHR")) { +					stbuf.st_mode = S_IFCHR; +				} else if (!strcmp(fileinfo[i+1], "S_IFIFO")) { +					stbuf.st_mode = S_IFIFO; +				} else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) { +					stbuf.st_mode = S_IFSOCK; +				} +			} else if (!strcmp(fileinfo[i], "st_nlink")) { +				stbuf.st_nlink = atoi(fileinfo[i+1]); +			} else if (!strcmp(fileinfo[i], "st_mtime")) { +				stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000); +			} else if (!strcmp(fileinfo[i], "LinkTarget")) { +				/* report latest crash report filename */ +				printf("Link: %s\n", (char*)target_filename + strlen(target_directory)); + +				/* remove any previous symlink */ +				if (file_exists(target_filename)) { +					remove(target_filename); +				} + +#ifndef WIN32 +				/* use relative filename */ +				char* b = strrchr(fileinfo[i+1], '/'); +				if (b == NULL) { +					b = fileinfo[i+1]; +				} else { +					b++; +				} + +				/* create a symlink pointing to latest log */ +				symlink(b, target_filename); +#endif + +				if (!keep_crash_reports) +					afc_remove_path(afc, source_filename); + +				res = 0; +			} +		} + +		/* free file information */ +		afc_dictionary_free(fileinfo); + +		/* recurse into child directories */ +		if (S_ISDIR(stbuf.st_mode)) { +#ifdef WIN32 +			mkdir(target_filename); +#else +			mkdir(target_filename, 0755); +#endif +			res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename); + +			/* remove directory from device */ +			if (!keep_crash_reports) +				afc_remove_path(afc, source_filename); +		} else if (S_ISREG(stbuf.st_mode)) { +			/* copy file to host */ +			afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle); +			if(afc_error != AFC_E_SUCCESS) { +				if (afc_error == AFC_E_OBJECT_NOT_FOUND) { +					continue; +				} +				fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error); +				continue; +			} + +			FILE* output = fopen(target_filename, "wb"); +			if(output == NULL) { +				fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename); +				afc_file_close(afc, handle); +				continue; +			} + +			printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory)); + +			uint32_t bytes_read = 0; +			uint32_t bytes_total = 0; +			unsigned char data[0x1000]; + +			afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); +			while(afc_error == AFC_E_SUCCESS && bytes_read > 0) { +				fwrite(data, 1, bytes_read, output); +				bytes_total += bytes_read; +				afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read); +			} +			afc_file_close(afc, handle); +			fclose(output); + +			if ((uint32_t)stbuf.st_size != bytes_total) { +				fprintf(stderr, "File size mismatch. Skipping...\n"); +				continue; +			} + +			/* remove file from device */ +			if (!keep_crash_reports) { +				afc_remove_path(afc, source_filename); +			} + +			/* extract raw crash information into separate '.crash' file */ +			if (extract_raw_crash_reports) { +				extract_raw_crash_report(target_filename); +			} + +			crash_report_count++; + +			res = 0; +		} +	} +	afc_dictionary_free(list); + +	/* no reports, no error */ +	if (crash_report_count == 0) +		res = 0; + +	return res; +} + +static void print_usage(int argc, char **argv) +{ +	char *name = NULL; + +	name = strrchr(argv[0], '/'); +	printf("Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0])); +	printf("Move crash reports from device to a local DIRECTORY.\n\n"); +	printf("  -e, --extract\t\textract raw crash report into separate '.crash' file\n"); +	printf("  -k, --keep\t\tcopy but do not remove crash reports from device\n"); +	printf("  -d, --debug\t\tenable communication debugging\n"); +	printf("  -u, --udid UDID\ttarget specific device by its 40-digit device UDID\n"); +	printf("  -h, --help\t\tprints usage information\n"); +	printf("\n"); +} + +int main(int argc, char* argv[]) { +	idevice_t device = NULL; +	lockdownd_client_t lockdownd = NULL; +	afc_client_t afc = NULL; + +	idevice_error_t device_error = IDEVICE_E_SUCCESS; +	lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS; +	afc_error_t afc_error = AFC_E_SUCCESS; + +	int i; +	const char* udid = NULL; + +	/* parse cmdline args */ +	for (i = 1; i < argc; i++) { +		if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { +			idevice_set_debug_level(1); +			continue; +		} +		else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--udid")) { +			i++; +			if (!argv[i] || (strlen(argv[i]) != 40)) { +				print_usage(argc, argv); +				return 0; +			} +			udid = argv[i]; +			continue; +		} +		else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { +			print_usage(argc, argv); +			return 0; +		} +		else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--extract")) { +			extract_raw_crash_reports = 1; +			continue; +		} +		else if (!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keep")) { +			keep_crash_reports = 1; +			continue; +		} +		else if (target_directory == NULL) { +			target_directory = argv[i]; +			continue; +		} +		else { +			print_usage(argc, argv); +			return 0; +		} +	} + +	/* ensure a target directory was supplied */ +	if (!target_directory) { +		print_usage(argc, argv); +		return 0; +	} + +	/* check if target directory exists */ +	if (!file_exists(target_directory)) { +		fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory); +		print_usage(argc, argv); +		return 0; +	} + +	device_error = idevice_new(&device, udid); +	if (device_error != IDEVICE_E_SUCCESS) { +		if (udid) { +			printf("No device found with udid %s, is it plugged in?\n", udid); +		} else { +			printf("No device found, is it plugged in?\n"); +		} +		return -1; +	} + +	lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, "idevicecrashreport"); +	if (lockdownd_error != LOCKDOWN_E_SUCCESS) { +		idevice_free(device); +		return -1; +	} + +	/* start crash log mover service */ +	lockdownd_service_descriptor_t service = NULL; +	lockdownd_error = lockdownd_start_service(lockdownd, "com.apple.crashreportmover", &service); +	if (lockdownd_error != LOCKDOWN_E_SUCCESS) { +		lockdownd_client_free(lockdownd); +		idevice_free(device); +		return -1; +	} + +	/* trigger move operation on device */ +	idevice_connection_t connection = NULL; +	device_error = idevice_connect(device, service->port, &connection); +	if(device_error != IDEVICE_E_SUCCESS) { +		lockdownd_client_free(lockdownd); +		idevice_free(device); +		return -1; +	} + +	/* read "ping" message which indicates the crash logs have been moved to a safe harbor */ +	char *ping = malloc(4); +	int attempts = 0; +	while ((strncmp(ping, "ping", 4) != 0) && (attempts > 10)) { +		uint32_t bytes = 0; +		device_error = idevice_connection_receive_timeout(connection, ping, 4, &bytes, 2000); +		if ((bytes == 0) && (device_error == IDEVICE_E_SUCCESS)) { +			attempts++; +			continue; +		} else if (device_error < 0) { +			fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted.\n"); +			break; +		} +	} +	idevice_disconnect(connection); +	free(ping); + +	if (service) { +		lockdownd_service_descriptor_free(service); +		service = NULL; +	} + +	if (device_error != IDEVICE_E_SUCCESS || attempts > 10) { +		fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n"); +		lockdownd_client_free(lockdownd); +		idevice_free(device); +		return -1; +	} + +	lockdownd_error = lockdownd_start_service(lockdownd, "com.apple.crashreportcopymobile", &service); +	if (lockdownd_error != LOCKDOWN_E_SUCCESS) { +		lockdownd_client_free(lockdownd); +		idevice_free(device); +		return -1; +	} +	lockdownd_client_free(lockdownd); + +	afc = NULL; +	afc_error = afc_client_new(device, service, &afc); +	if(afc_error != AFC_E_SUCCESS) { +		lockdownd_client_free(lockdownd); +		idevice_free(device); +		return -1; +	} + +	if (service) { +		lockdownd_service_descriptor_free(service); +		service = NULL; +	} + +	/* recursively copy crash reports from the device to a local directory */ +	if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory) < 0) { +		fprintf(stderr, "ERROR: Failed to get crash reports from device.\n"); +		afc_client_free(afc); +		idevice_free(device); +		return -1; +	} + +	printf("Done.\n"); + +	afc_client_free(afc); +	idevice_free(device); + +	return 0; +}  | 
