idevicecrashreport.c 13 KB

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