summaryrefslogtreecommitdiffstats
path: root/tools/afcclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/afcclient.c')
-rw-r--r--tools/afcclient.c1640
1 files changed, 1640 insertions, 0 deletions
diff --git a/tools/afcclient.c b/tools/afcclient.c
new file mode 100644
index 0000000..2667e52
--- /dev/null
+++ b/tools/afcclient.c
@@ -0,0 +1,1640 @@
1/*
2 * afcclient.c
3 * Utility to interact with AFC/HoustArrest service on the device
4 *
5 * Inspired by https://github.com/emonti/afcclient
6 * But entirely rewritten from scratch.
7 *
8 * Copyright (c) 2023 Nikias Bassen, All Rights Reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
29#define TOOL_NAME "afcclient"
30
31#include <stdio.h>
32#include <string.h>
33#include <errno.h>
34#include <stdlib.h>
35#include <getopt.h>
36#include <signal.h>
37#include <ctype.h>
38#include <unistd.h>
39#include <dirent.h>
40#include <time.h>
41#include <sys/stat.h>
42
43#ifdef _WIN32
44#include <windows.h>
45#include <sys/time.h>
46#include <conio.h>
47#define sleep(x) Sleep(x*1000)
48#define S_IFMT 0170000 /* [XSI] type of file mask */
49#define S_IFIFO 0010000 /* [XSI] named pipe (fifo) */
50#define S_IFCHR 0020000 /* [XSI] character special */
51#define S_IFBLK 0060000 /* [XSI] block special */
52#define S_IFLNK 0120000 /* [XSI] symbolic link */
53#define S_IFSOCK 0140000 /* [XSI] socket */
54#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */
55#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */
56#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */
57#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */
58#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
59#else
60#include <sys/time.h>
61#include <termios.h>
62#endif
63
64#ifdef HAVE_READLINE
65#include <readline/readline.h>
66#include <readline/history.h>
67#endif
68
69#include <libimobiledevice/libimobiledevice.h>
70#include <libimobiledevice/lockdown.h>
71#include <libimobiledevice/house_arrest.h>
72#include <libimobiledevice/afc.h>
73#include <plist/plist.h>
74
75#include <libimobiledevice-glue/termcolors.h>
76#include <libimobiledevice-glue/utils.h>
77
78#undef st_mtime
79#undef st_birthtime
80struct afc_file_stat {
81 uint16_t st_mode;
82 uint16_t st_nlink;
83 uint64_t st_size;
84 uint64_t st_mtime;
85 uint64_t st_birthtime;
86 uint32_t st_blocks;
87};
88
89static char* udid = NULL;
90static int connected = 0;
91static int use_network = 0;
92static idevice_subscription_context_t context = NULL;
93static char* curdir = NULL;
94static size_t curdir_len = 0;
95
96static int file_exists(const char* path)
97{
98 struct stat tst;
99#ifdef _WIN32
100 return (stat(path, &tst) == 0);
101#else
102 return (lstat(path, &tst) == 0);
103#endif
104}
105
106static int is_directory(const char* path)
107{
108 struct stat tst;
109#ifdef _WIN32
110 return (stat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
111#else
112 return (lstat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
113#endif
114}
115
116static void print_usage(int argc, char **argv, int is_error)
117{
118 char *name = strrchr(argv[0], '/');
119 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
120 fprintf(is_error ? stderr : stdout,
121 "\n"
122 "Interact with AFC/HouseArrest service on a connected device.\n"
123 "\n"
124 "OPTIONS:\n"
125 " -u, --udid UDID target specific device by UDID\n"
126 " -n, --network connect to network device (not recommended!)\n"
127 " --container <appid> Access container of given app\n"
128 " --documents <appid> Access Documents directory of given app\n"
129 " -h, --help prints usage information\n" \
130 " -d, --debug enable communication debugging\n" \
131 " -v, --version prints version information\n" \
132 "\n"
133 );
134 fprintf(is_error ? stderr : stdout,
135 "\n" \
136 "Homepage: <" PACKAGE_URL ">\n"
137 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
138 );
139}
140
141#ifndef HAVE_READLINE
142#ifdef _WIN32
143#define BS_CC '\b'
144#define getch _getch
145#else
146#define BS_CC 0x7f
147#define getch getchar
148#endif
149static void get_input(char *buf, int maxlen)
150{
151 int len = 0;
152 int c;
153
154 while ((c = getch())) {
155 if ((c == '\r') || (c == '\n')) {
156 break;
157 }
158 if (isprint(c)) {
159 if (len < maxlen-1) {
160 buf[len++] = c;
161#ifdef _WIN32
162 fputc(c, stdout);
163#endif
164 }
165 } else if (c == BS_CC) {
166 if (len > 0) {
167 fputs("\b \b", stdout);
168 len--;
169 } else {
170 fputc(0x07, stdout);
171 }
172 }
173 }
174 buf[len] = 0;
175}
176#endif
177
178#define OPT_DOCUMENTS 1
179#define OPT_CONTAINER 2
180
181int stop_requested = 0;
182
183static void handle_signal(int sig)
184{
185 stop_requested++;
186#ifdef _WIN32
187 GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
188#else
189 kill(getpid(), SIGINT);
190#endif
191}
192
193static void handle_help(afc_client_t afc, int argc, char** argv)
194{
195 printf("Available commands:\n");
196 printf("help - print list of available commands\n");
197 printf("devinfo - print device information\n");
198 printf("info PATH - print file attributes of file at PATH\n");
199 printf("ls [-l] PATH - print directory contents of PATH\n");
200 printf("mv OLD NEW - rename file OLD to NEW\n");
201 printf("mkdir PATH - create directory at PATH\n");
202 printf("ln [-s] FILE [LINK] - create a (symbolic) link to file named LINKNAME\n");
203 printf(" NOTE: This feature has been disabled in newer versions of iOS.\n");
204 printf("rm PATH - remove item at PATH\n");
205 printf("get [-rf] PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n");
206 printf("put [-rf] LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n");
207 printf("\n");
208}
209
210static const char* path_get_basename(const char* path)
211{
212 const char *p = strrchr(path, '/');
213 return p ? p + 1 : path;
214}
215
216static int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
217{
218 /* Perform the carry for the later subtraction by updating y. */
219 if (x->tv_usec < y->tv_usec) {
220 int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
221 y->tv_usec -= 1000000 * nsec;
222 y->tv_sec += nsec;
223 }
224 if (x->tv_usec - y->tv_usec > 1000000) {
225 int nsec = (x->tv_usec - y->tv_usec) / 1000000;
226 y->tv_usec += 1000000 * nsec;
227 y->tv_sec -= nsec;
228 }
229 /* Compute the time remaining to wait.
230 tv_usec is certainly positive. */
231 result->tv_sec = x->tv_sec - y->tv_sec;
232 result->tv_usec = x->tv_usec - y->tv_usec;
233 /* Return 1 if result is negative. */
234 return x->tv_sec < y->tv_sec;
235}
236
237struct str_item {
238 size_t len;
239 char* str;
240};
241
242static char* get_absolute_path(const char *path)
243{
244 if (*path == '/') {
245 return strdup(path);
246 } else {
247 size_t len = curdir_len + 1 + strlen(path) + 1;
248 char* result = (char*)malloc(len);
249 if (!strcmp(curdir, "/")) {
250 snprintf(result, len, "/%s", path);
251 } else {
252 snprintf(result, len, "%s/%s", curdir, path);
253 }
254 return result;
255 }
256}
257
258static char* get_realpath(const char* path)
259{
260 if (!path) return NULL;
261
262 int is_absolute = 0;
263 if (*path == '/') {
264 is_absolute = 1;
265 }
266
267 const char* p = path;
268 if (is_absolute) {
269 while (*p == '/') p++;
270 }
271 if (*p == '\0') {
272 return strdup("/");
273 }
274
275 int c_count = 1;
276 const char* start = p;
277 const char* end = p;
278 struct str_item* comps = NULL;
279
280 while (*p) {
281 if (*p == '/') {
282 p++;
283 end = p-1;
284 while (*p == '/') p++;
285 if (*p == '\0') break;
286 struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
287 if (!newcomps) {
288 free(comps);
289 printf("%s: out of memory?!\n", __func__);
290 return NULL;
291 }
292 comps = newcomps;
293 char *comp = (char*)malloc(end-start+1);
294 strncpy(comp, start, end-start);
295 comp[end-start] = '\0';
296 comps[c_count-1].len = end-start;
297 comps[c_count-1].str = comp;
298 c_count++;
299 start = p;
300 end = p;
301 }
302 p++;
303 }
304 if (p > start) {
305 if (start == end) {
306 end = p;
307 }
308 struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
309 if (!newcomps) {
310 free(comps);
311 printf("%s: out of memory?!\n", __func__);
312 return NULL;
313 }
314 comps = newcomps;
315 char *comp = (char*)malloc(end-start+1);
316 strncpy(comp, start, end-start);
317 comp[end-start] = '\0';
318 comps[c_count-1].len = end-start;
319 comps[c_count-1].str = comp;
320 }
321
322 struct str_item* comps_final = (struct str_item*)malloc(sizeof(struct str_item)*(c_count+1));
323 int o = 1;
324 if (is_absolute) {
325 comps_final[0].len = 1;
326 comps_final[0].str = (char*)"/";
327 } else {
328 comps_final[0].len = curdir_len;
329 comps_final[0].str = curdir;
330 }
331 size_t o_len = comps_final[0].len;
332
333 for (int i = 0; i < c_count; i++) {
334 if (!strcmp(comps[i].str, "..")) {
335 o--;
336 continue;
337 } else if (!strcmp(comps[i].str, ".")) {
338 continue;
339 }
340 o_len += comps[i].len;
341 comps_final[o].str = comps[i].str;
342 comps_final[o].len = comps[i].len;
343 o++;
344 }
345
346 o_len += o;
347 char* result = (char*)malloc(o_len);
348 char* presult = result;
349 for (int i = 0; i < o; i++) {
350 if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) {
351 *presult = '/';
352 presult++;
353 }
354 strncpy(presult, comps_final[i].str, comps_final[i].len);
355 presult+=comps_final[i].len;
356 *presult = '\0';
357 }
358 if (presult == result) {
359 *presult = '/';
360 presult++;
361 *presult = 0;
362 }
363
364 for (int i = 0; i < c_count; i++) {
365 free(comps[i].str);
366 }
367 free(comps);
368 free(comps_final);
369
370 return result;
371}
372
373static void handle_devinfo(afc_client_t afc, int argc, char** argv)
374{
375 plist_t info = NULL;
376 afc_error_t err = afc_get_device_info_plist(afc, &info);
377 if (err == AFC_E_SUCCESS && info) {
378 if (argc > 0 && !strcmp(argv[0], "--plain")) {
379 plist_write_to_stream(info, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_NONE);
380 } else {
381 plist_write_to_stream(info, stdout, PLIST_FORMAT_JSON, PLIST_OPT_NONE);
382 }
383 } else {
384 printf("Error: Failed to get device info: %s (%d)\n", afc_strerror(err), err);
385 }
386 plist_free(info);
387}
388
389static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf)
390{
391 plist_t info = NULL;
392 afc_error_t ret = afc_get_file_info_plist(afc, path, &info);
393 memset(stbuf, 0, sizeof(struct afc_file_stat));
394 if (ret != AFC_E_SUCCESS) {
395 return -1;
396 } else if (!info) {
397 return -1;
398 }
399 stbuf->st_size = plist_dict_get_uint(info, "st_size");
400 stbuf->st_blocks = plist_dict_get_uint(info, "st_blocks");
401 const char* s_ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
402 if (s_ifmt) {
403 if (!strcmp(s_ifmt, "S_IFREG")) {
404 stbuf->st_mode = S_IFREG;
405 } else if (!strcmp(s_ifmt, "S_IFDIR")) {
406 stbuf->st_mode = S_IFDIR;
407 } else if (!strcmp(s_ifmt, "S_IFLNK")) {
408 stbuf->st_mode = S_IFLNK;
409 } else if (!strcmp(s_ifmt, "S_IFBLK")) {
410 stbuf->st_mode = S_IFBLK;
411 } else if (!strcmp(s_ifmt, "S_IFCHR")) {
412 stbuf->st_mode = S_IFCHR;
413 } else if (!strcmp(s_ifmt, "S_IFIFO")) {
414 stbuf->st_mode = S_IFIFO;
415 } else if (!strcmp(s_ifmt, "S_IFSOCK")) {
416 stbuf->st_mode = S_IFSOCK;
417 }
418 }
419 stbuf->st_nlink = plist_dict_get_uint(info, "st_nlink");
420 stbuf->st_mtime = (time_t)(plist_dict_get_uint(info, "st_mtime") / 1000000000);
421 /* available on iOS 7+ */
422 stbuf->st_birthtime = (time_t)(plist_dict_get_uint(info, "st_birthtime") / 1000000000);
423 plist_free(info);
424 return 0;
425}
426
427static void handle_file_info(afc_client_t afc, int argc, char** argv)
428{
429 if (argc < 1) {
430 printf("Error: Missing PATH.\n");
431 return;
432 }
433
434 plist_t info = NULL;
435 char* abspath = get_absolute_path(argv[0]);
436 if (!abspath) {
437 printf("Error: Invalid argument\n");
438 return;
439 }
440 afc_error_t err = afc_get_file_info_plist(afc, abspath, &info);
441 if (err == AFC_E_SUCCESS && info) {
442 if (argc > 1 && !strcmp(argv[1], "--plain")) {
443 plist_write_to_stream(info, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_NONE);
444 } else {
445 plist_write_to_stream(info, stdout, PLIST_FORMAT_JSON, PLIST_OPT_NONE);
446 }
447 } else {
448 printf("Error: Failed to get file info for %s: %s (%d)\n", argv[0], afc_strerror(err), err);
449 }
450 plist_free(info);
451 free(abspath);
452}
453
454static void print_file_info(afc_client_t afc, const char* path, int list_verbose)
455{
456 struct afc_file_stat st;
457 get_file_info_stat(afc, path, &st);
458 if (list_verbose) {
459 char timebuf[64];
460 time_t t = st.st_mtime;
461 if (S_ISDIR(st.st_mode)) {
462 printf("drwxr-xr-x");
463 } else if (S_ISLNK(st.st_mode)) {
464 printf("lrwxrwxrwx");
465 } else {
466 if (S_ISFIFO(st.st_mode)) {
467 printf("f");
468 } else if (S_ISBLK(st.st_mode)) {
469 printf("b");
470 } else if (S_ISCHR(st.st_mode)) {
471 printf("c");
472 } else if (S_ISSOCK(st.st_mode)) {
473 printf("s");
474 } else {
475 printf("-");
476 }
477 printf("rw-r--r--");
478 }
479 printf(" ");
480 printf("%4d", st.st_nlink);
481 printf(" ");
482 printf("mobile");
483 printf(" ");
484 printf("mobile");
485 printf(" ");
486 printf("%10lld", (long long)st.st_size);
487 printf(" ");
488#ifdef _WIN32
489 strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t));
490#else
491 strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t));
492#endif
493 printf("%s", timebuf);
494 printf(" ");
495 }
496 if (S_ISDIR(st.st_mode)) {
497 cprintf(FG_CYAN);
498 } else if (S_ISLNK(st.st_mode)) {
499 cprintf(FG_MAGENTA);
500 } else if (S_ISREG(st.st_mode)) {
501 cprintf(FG_DEFAULT);
502 } else {
503 cprintf(FG_YELLOW);
504 }
505 cprintf("%s" COLOR_RESET "\n", path_get_basename(path));
506}
507
508static void handle_list(afc_client_t afc, int argc, char** argv)
509{
510 const char* path = NULL;
511 int list_verbose = 0;
512 if (argc < 1) {
513 path = curdir;
514 } else {
515 if (!strcmp(argv[0], "-l")) {
516 list_verbose = 1;
517 if (argc == 2) {
518 path = argv[1];
519 } else {
520 path = curdir;
521 }
522 } else {
523 path = argv[0];
524 }
525 }
526 char* abspath = get_absolute_path(path);
527 if (!abspath) {
528 printf("Error: Invalid argument\n");
529 return;
530 }
531 int abspath_is_root = strcmp(abspath, "/") == 0;
532 size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath);
533 char** entries = NULL;
534 afc_error_t err = afc_read_directory(afc, abspath, &entries);
535 if (err == AFC_E_READ_ERROR) {
536 print_file_info(afc, abspath, list_verbose);
537 return;
538 } else if (err != AFC_E_SUCCESS) {
539 printf("Error: Failed to list '%s': %s (%d)\n", path, afc_strerror(err), err);
540 free(abspath);
541 return;
542 }
543
544 char** p = entries;
545 while (p && *p) {
546 if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
547 p++;
548 continue;
549 }
550 size_t len = abspath_len + 1 + strlen(*p) + 1;
551 char* testpath = (char*)malloc(len);
552 if (abspath_is_root) {
553 snprintf(testpath, len, "/%s", *p);
554 } else {
555 snprintf(testpath, len, "%s/%s", abspath, *p);
556 }
557 print_file_info(afc, testpath, list_verbose);
558 free(testpath);
559 p++;
560 }
561 afc_dictionary_free(entries);
562 free(abspath);
563}
564
565static void handle_rename(afc_client_t afc, int argc, char** argv)
566{
567 if (argc != 2) {
568 printf("Error: Invalid number of arguments\n");
569 return;
570 }
571 char* srcpath = get_absolute_path(argv[0]);
572 if (!srcpath) {
573 printf("Error: Invalid argument\n");
574 return;
575 }
576 char* dstpath = get_absolute_path(argv[1]);
577 if (!dstpath) {
578 free(srcpath);
579 printf("Error: Invalid argument\n");
580 return;
581 }
582 afc_error_t err = afc_rename_path(afc, srcpath, dstpath);
583 if (err != AFC_E_SUCCESS) {
584 printf("Error: Failed to rename '%s' -> '%s': %s (%d)\n", argv[0], argv[1], afc_strerror(err), err);
585 }
586 free(srcpath);
587 free(dstpath);
588}
589
590static void handle_mkdir(afc_client_t afc, int argc, char** argv)
591{
592 for (int i = 0; i < argc; i++) {
593 char* abspath = get_absolute_path(argv[i]);
594 if (!abspath) {
595 printf("Error: Invalid argument '%s'\n", argv[i]);
596 continue;
597 }
598 afc_error_t err = afc_make_directory(afc, abspath);
599 if (err != AFC_E_SUCCESS) {
600 printf("Error: Failed to create directory '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
601 }
602 free(abspath);
603 }
604}
605
606static void handle_link(afc_client_t afc, int argc, char** argv)
607{
608 if (argc < 2) {
609 printf("Error: Invalid number of arguments\n");
610 return;
611 }
612 afc_link_type_t link_type = AFC_HARDLINK;
613 if (!strcmp(argv[0], "-s")) {
614 argc--;
615 argv++;
616 link_type = AFC_SYMLINK;
617 }
618 if (argc < 1 || argc > 2) {
619 printf("Error: Invalid number of arguments\n");
620 return;
621 }
622 const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1];
623 char* abs_link_name = get_absolute_path(link_name);
624 if (!abs_link_name) {
625 printf("Error: Invalid argument\n");
626 return;
627 }
628 afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name);
629 if (err != AFC_E_SUCCESS) {
630 printf("Error: Failed to create %s link for '%s' at '%s': %s (%d)\n", (link_type == AFC_HARDLINK) ? "hard" : "symbolic", argv[0], link_name, afc_strerror(err), err);
631 }
632}
633
634static int ask_yesno(const char* prompt)
635{
636 int ret = 0;
637#ifdef HAVE_READLINE
638 char* result = readline(prompt);
639 if (result && result[0] == 'y') {
640 ret = 1;
641 }
642#else
643 char cmdbuf[2] = {0, };
644 printf("%s", prompt);
645 fflush(stdout);
646 get_input(cmdbuf, sizeof(cmdbuf));
647 if (cmdbuf[0] == 'y') {
648 ret = 1;
649 }
650#endif
651#ifdef HAVE_READLINE
652 free(result);
653#endif
654 return ret;
655}
656
657static void handle_remove(afc_client_t afc, int argc, char** argv)
658{
659 int recursive = 0;
660 int force = 0;
661 int i = 0;
662 for (i = 0; i < argc; i++) {
663 if (!strcmp(argv[i], "--")) {
664 i++;
665 break;
666 } else if (!strcmp(argv[i], "-r")) {
667 recursive = 1;
668 } else if (!strcmp(argv[i], "-f")) {
669 force = 1;
670 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
671 recursive = 1;
672 force = 1;
673 } else {
674 break;
675 }
676 }
677 if (recursive && !force) {
678 if (!ask_yesno("WARNING: This operation will remove all contents of the given path(s). Continue? [y/N] ")) {
679 printf("Aborted.\n");
680 return;
681 }
682 }
683 for ( ; i < argc; i++) {
684 char* abspath = get_absolute_path(argv[i]);
685 if (!abspath) {
686 printf("Error: Invalid argument '%s'\n", argv[i]);
687 continue;
688 }
689 afc_error_t err;
690 if (recursive) {
691 err = afc_remove_path_and_contents(afc, abspath);
692 } else {
693 err = afc_remove_path(afc, abspath);
694 }
695 if (err != AFC_E_SUCCESS) {
696 printf("Error: Failed to remove '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
697 }
698 free(abspath);
699 }
700}
701
702static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite)
703{
704 uint64_t fh = 0;
705 afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh);
706 if (err != AFC_E_SUCCESS) {
707 printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
708 return 0;
709 }
710 if (file_exists(dstpath) && !force_overwrite) {
711 printf("Error: Failed to overwrite existing file without '-f' option: %s\n", dstpath);
712 return 0;
713 }
714 FILE *f = fopen(dstpath, "wb");
715 if (!f) {
716 printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno));
717 return 0;
718 }
719 struct timeval t1;
720 struct timeval t2;
721 struct timeval tdiff;
722 size_t bufsize = 0x100000;
723 char *buf = malloc(bufsize);
724 size_t total = 0;
725 int progress = 0;
726 int lastprog = 0;
727 if (file_size > 0x400000) {
728 progress = 1;
729 gettimeofday(&t1, NULL);
730 }
731 uint8_t succeed = 1;
732 while (err == AFC_E_SUCCESS) {
733 uint32_t bytes_read = 0;
734 size_t chunk = 0;
735 err = afc_file_read(afc, fh, buf, bufsize, &bytes_read);
736 if (bytes_read == 0) {
737 break;
738 }
739 while (chunk < bytes_read) {
740 size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f);
741 if (wr == 0) {
742 if (progress) {
743 printf("\n");
744 }
745 printf("Error: Failed to write to local file\n");
746 succeed = 0;
747 break;
748 }
749 chunk += wr;
750 }
751 total += chunk;
752 if (progress) {
753 int prog = (int) ((double) total / (double) file_size * 100.0f);
754 if (prog > lastprog) {
755 gettimeofday(&t2, NULL);
756 timeval_subtract(&tdiff, &t2, &t1);
757 double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
758 printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
759 fflush(stdout);
760 lastprog = prog;
761 }
762 }
763 }
764 if (progress) {
765 printf("\n");
766 }
767 if (err != AFC_E_SUCCESS) {
768 printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
769 succeed = 0;
770 }
771 free(buf);
772 fclose(f);
773 afc_file_close(afc, fh);
774 return succeed;
775}
776
777static int __mkdir(const char* path)
778{
779#ifdef _WIN32
780 return mkdir(path);
781#else
782 return mkdir(path, 0755);
783#endif
784}
785
786static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_get)
787{
788 plist_t info = NULL;
789 uint64_t file_size = 0;
790 afc_error_t err = afc_get_file_info_plist(afc, srcpath, &info);
791 if (err == AFC_E_OBJECT_NOT_FOUND) {
792 printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
793 return 0;
794 }
795 uint8_t is_dir = 0;
796 if (info) {
797 file_size = plist_dict_get_uint(info, "st_size");
798 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
799 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
800 plist_free(info);
801 }
802 uint8_t succeed = 1;
803 if (is_dir) {
804 if (!recursive_get) {
805 printf("Error: Failed to get a directory without '-r' option: %s\n", srcpath);
806 return 0;
807 }
808 char **entries = NULL;
809 err = afc_read_directory(afc, srcpath, &entries);
810 if (err != AFC_E_SUCCESS) {
811 printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
812 return 0;
813 }
814 char **p = entries;
815 size_t srcpath_len = strlen(srcpath);
816 uint8_t srcpath_is_root = strcmp(srcpath, "/") == 0;
817 // if directory exists, check force_overwrite flag
818 if (is_directory(dstpath)) {
819 if (!force_overwrite) {
820 printf("Error: Failed to write into existing directory without '-f': %s\n", dstpath);
821 return 0;
822 }
823 } else if (__mkdir(dstpath) != 0) {
824 printf("Error: Failed to create directory '%s': %s\n", dstpath, strerror(errno));
825 afc_dictionary_free(entries);
826 return 0;
827 }
828 while (p && *p) {
829 if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
830 p++;
831 continue;
832 }
833 size_t len = srcpath_is_root ? (strlen(*p) + 2) : (srcpath_len + 1 + strlen(*p) + 1);
834 char *testpath = (char *) malloc(len);
835 if (srcpath_is_root) {
836 snprintf(testpath, len, "/%s", *p);
837 } else {
838 snprintf(testpath, len, "%s/%s", srcpath, *p);
839 }
840 uint8_t dst_is_root = strcmp(srcpath, "/") == 0;
841 size_t dst_len = dst_is_root ? (strlen(*p) + 2) : (strlen(dstpath) + 1 + strlen(*p) + 1);
842 char *newdst = (char *) malloc(dst_len);
843 if (dst_is_root) {
844 snprintf(newdst, dst_len, "/%s", *p);
845 } else {
846 snprintf(newdst, dst_len, "%s/%s", dstpath, *p);
847 }
848 if (!get_file(afc, testpath, newdst, force_overwrite, recursive_get)) {
849 succeed = 0;
850 break;
851 }
852 free(testpath);
853 free(newdst);
854 p++;
855 }
856 afc_dictionary_free(entries);
857 } else {
858 succeed = get_single_file(afc, srcpath, dstpath, file_size, force_overwrite);
859 }
860 return succeed;
861}
862
863static void handle_get(afc_client_t afc, int argc, char **argv)
864{
865 if (argc < 1) {
866 printf("Error: Invalid number of arguments\n");
867 return;
868 }
869 uint8_t force_overwrite = 0, recursive_get = 0;
870 char *srcpath = NULL;
871 char *dstpath = NULL;
872 int i = 0;
873 for ( ; i < argc; i++) {
874 if (!strcmp(argv[i], "--")) {
875 i++;
876 break;
877 } else if (!strcmp(argv[i], "-r")) {
878 recursive_get = 1;
879 } else if (!strcmp(argv[i], "-f")) {
880 force_overwrite = 1;
881 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
882 recursive_get = 1;
883 force_overwrite = 1;
884 } else {
885 break;
886 }
887 }
888 if (argc - i == 1) {
889 char *tmp = strdup(argv[i]);
890 size_t src_len = strlen(tmp);
891 if (src_len > 1 && tmp[src_len - 1] == '/') {
892 tmp[src_len - 1] = '\0';
893 }
894 srcpath = get_absolute_path(tmp);
895 dstpath = strdup(path_get_basename(tmp));
896 free(tmp);
897 } else if (argc - i == 2) {
898 char *tmp = strdup(argv[i]);
899 size_t src_len = strlen(tmp);
900 if (src_len > 1 && tmp[src_len - 1] == '/') {
901 tmp[src_len - 1] = '\0';
902 }
903 srcpath = get_absolute_path(tmp);
904 dstpath = strdup(argv[i + 1]);
905 size_t dst_len = strlen(dstpath);
906 if (dst_len > 1 && dstpath[dst_len - 1] == '/') {
907 dstpath[dst_len - 1] = '\0';
908 }
909 free(tmp);
910 } else {
911 printf("Error: Invalid number of arguments\n");
912 return;
913 }
914
915 // target is a directory, put file under this target
916 if (is_directory(dstpath)) {
917 const char *basen = path_get_basename(srcpath);
918 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
919 size_t len = dst_is_root ? (strlen(basen) + 2) : (strlen(dstpath) + 1 + strlen(basen) + 1);
920 char *newdst = (char *) malloc(len);
921 if (dst_is_root) {
922 snprintf(newdst, len, "/%s", basen);
923 } else {
924 snprintf(newdst, len, "%s/%s", dstpath, basen);
925 }
926 get_file(afc, srcpath, newdst, force_overwrite, recursive_get);
927 free(srcpath);
928 free(newdst);
929 free(dstpath);
930 } else {
931 // target is not a dir or does not exist, just try to create or rewrite it
932 get_file(afc, srcpath, dstpath, force_overwrite, recursive_get);
933 free(srcpath);
934 free(dstpath);
935 }
936}
937
938static uint8_t put_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite)
939{
940 plist_t info = NULL;
941 afc_error_t ret = afc_get_file_info_plist(afc, dstpath, &info);
942 // file exists, only overwrite with '-f' option was set
943 if (ret == AFC_E_SUCCESS && info) {
944 plist_free(info);
945 if (!force_overwrite) {
946 printf("Error: Failed to write into existing file without '-f' option: %s\n", dstpath);
947 return 0;
948 }
949 }
950 FILE *f = fopen(srcpath, "rb");
951 if (!f) {
952 printf("Error: Failed to open local file '%s': %s\n", srcpath, strerror(errno));
953 return 0;
954 }
955 struct timeval t1;
956 struct timeval t2;
957 struct timeval tdiff;
958 struct stat fst;
959 int progress = 0;
960 size_t bufsize = 0x100000;
961 char *buf = malloc(bufsize);
962
963 fstat(fileno(f), &fst);
964 if (fst.st_size >= 0x400000) {
965 progress = 1;
966 gettimeofday(&t1, NULL);
967 }
968 size_t total = 0;
969 int lastprog = 0;
970 uint64_t fh = 0;
971 afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
972 uint8_t succeed = 1;
973 while (err == AFC_E_SUCCESS) {
974 uint32_t bytes_read = fread(buf, 1, bufsize, f);
975 if (bytes_read == 0) {
976 if (!feof(f)) {
977 if (progress) {
978 printf("\n");
979 }
980 printf("Error: Failed to read from local file\n");
981 succeed = 0;
982 }
983 break;
984 }
985 uint32_t chunk = 0;
986 while (chunk < bytes_read) {
987 uint32_t bytes_written = 0;
988 err = afc_file_write(afc, fh, buf + chunk, bytes_read - chunk, &bytes_written);
989 if (err != AFC_E_SUCCESS) {
990 if (progress) {
991 printf("\n");
992 }
993 printf("Error: Failed to write to device file\n");
994 succeed = 0;
995 break;
996 }
997 chunk += bytes_written;
998 }
999 total += chunk;
1000 if (progress) {
1001 int prog = (int) ((double) total / (double) fst.st_size * 100.0f);
1002 if (prog > lastprog) {
1003 gettimeofday(&t2, NULL);
1004 timeval_subtract(&tdiff, &t2, &t1);
1005 double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
1006 printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
1007 fflush(stdout);
1008 lastprog = prog;
1009 }
1010 }
1011 }
1012 if (progress) {
1013 printf("\n");
1014 }
1015 free(buf);
1016 afc_file_close(afc, fh);
1017 fclose(f);
1018 return succeed;
1019}
1020
1021static uint8_t put_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_put)
1022{
1023 if (is_directory(srcpath)) {
1024 if (!recursive_put) {
1025 printf("Error: Failed to put directory without '-r' option: %s\n", srcpath);
1026 return 0;
1027 }
1028 plist_t info = NULL;
1029 afc_error_t err = afc_get_file_info_plist(afc, dstpath, &info);
1030 //create if target directory does not exist
1031 plist_free(info);
1032 info = NULL;
1033 if (err == AFC_E_OBJECT_NOT_FOUND) {
1034 err = afc_make_directory(afc, dstpath);
1035 if (err != AFC_E_SUCCESS) {
1036 printf("Error: Failed to create directory '%s': %s (%d)\n", dstpath, afc_strerror(err), err);
1037 return 0;
1038 }
1039 } else if (!force_overwrite) {
1040 printf("Error: Failed to put existing directory without '-f' option: %s\n", dstpath);
1041 return 0;
1042 }
1043 afc_get_file_info_plist(afc, dstpath, &info);
1044 uint8_t is_dir = 0;
1045 if (info) {
1046 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1047 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1048 plist_free(info);
1049 }
1050 if (!is_dir) {
1051 printf("Error: Failed to create or access directory: '%s'\n", dstpath);
1052 return 0;
1053 }
1054
1055 // walk dir recursively to put files
1056 DIR *cur_dir = opendir(srcpath);
1057 if (cur_dir) {
1058 struct dirent *ep;
1059 while ((ep = readdir(cur_dir))) {
1060 if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
1061 continue;
1062 }
1063 char *fpath = string_build_path(srcpath, ep->d_name, NULL);
1064 if (fpath) {
1065 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
1066 size_t len = dst_is_root ? (strlen(ep->d_name) + 2) : (strlen(dstpath) + 1 + strlen(ep->d_name) + 1);
1067 char *newdst = (char *) malloc(len);
1068 if (dst_is_root) {
1069 snprintf(newdst, len, "/%s", ep->d_name);
1070 } else {
1071 snprintf(newdst, len, "%s/%s", dstpath, ep->d_name);
1072 }
1073 if (!put_file(afc, fpath, newdst, force_overwrite, recursive_put)) {
1074 free(newdst);
1075 free(fpath);
1076 return 0;
1077 }
1078 free(newdst);
1079 free(fpath);
1080 }
1081 }
1082 closedir(cur_dir);
1083 } else {
1084 printf("Error: Failed to visit directory: '%s': %s\n", srcpath, strerror(errno));
1085 return 0;
1086 }
1087 } else {
1088 return put_single_file(afc, srcpath, dstpath, force_overwrite);
1089 }
1090 return 1;
1091}
1092
1093static void handle_put(afc_client_t afc, int argc, char **argv)
1094{
1095 if (argc < 1) {
1096 printf("Error: Invalid number of arguments\n");
1097 return;
1098 }
1099 int i = 0;
1100 uint8_t force_overwrite = 0, recursive_put = 0;
1101 for ( ; i < argc; i++) {
1102 if (!strcmp(argv[i], "--")) {
1103 i++;
1104 break;
1105 } else if (!strcmp(argv[i], "-r")) {
1106 recursive_put = 1;
1107 } else if (!strcmp(argv[i], "-f")) {
1108 force_overwrite = 1;
1109 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
1110 recursive_put = 1;
1111 force_overwrite = 1;
1112 } else {
1113 break;
1114 }
1115 }
1116 if (i >= argc) {
1117 printf("Error: Invalid number of arguments\n");
1118 return;
1119 }
1120 char *srcpath = strdup(argv[i]);
1121 size_t src_len = strlen(srcpath);
1122 if (src_len > 1 && srcpath[src_len - 1] == '/') {
1123 srcpath[src_len - 1] = '\0';
1124 }
1125 char *dstpath = NULL;
1126 if (argc - i == 1) {
1127 dstpath = get_absolute_path(path_get_basename(srcpath));
1128 } else if (argc - i == 2) {
1129 char *tmp = strdup(argv[i + 1]);
1130 size_t dst_len = strlen(tmp);
1131 if (dst_len > 1 && tmp[dst_len - 1] == '/') {
1132 tmp[dst_len - 1] = '\0';
1133 }
1134 dstpath = get_absolute_path(tmp);
1135 free(tmp);
1136 } else {
1137 printf("Error: Invalid number of arguments\n");
1138 return;
1139 }
1140 plist_t info = NULL;
1141 afc_error_t err = afc_get_file_info_plist(afc, dstpath, &info);
1142 // target does not exist, put directly
1143 if (err == AFC_E_OBJECT_NOT_FOUND) {
1144 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1145 free(srcpath);
1146 free(dstpath);
1147 } else {
1148 uint8_t is_dir = 0;
1149 if (info) {
1150 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1151 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1152 plist_free(info);
1153 }
1154 // target is a directory, try to put under this directory
1155 if (is_dir) {
1156 const char *basen = path_get_basename(srcpath);
1157 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
1158 size_t len = dst_is_root ? (strlen(basen) + 2) : (strlen(dstpath) + 1 + strlen(basen) + 1);
1159 char *newdst = (char *) malloc(len);
1160 if (dst_is_root) {
1161 snprintf(newdst, len, "/%s", basen);
1162 } else {
1163 snprintf(newdst, len, "%s/%s", dstpath, basen);
1164 }
1165 free(dstpath);
1166 dstpath = get_absolute_path(newdst);
1167 free(newdst);
1168 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1169 } else {
1170 //target is common file, rewrite it
1171 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1172 }
1173 free(srcpath);
1174 free(dstpath);
1175 }
1176}
1177
1178static void handle_pwd(afc_client_t afc, int argc, char** argv)
1179{
1180 printf("%s\n", curdir);
1181}
1182
1183static void handle_cd(afc_client_t afc, int argc, char** argv)
1184{
1185 if (argc != 1) {
1186 printf("Error: Invalid number of arguments\n");
1187 return;
1188 }
1189
1190 if (!strcmp(argv[0], ".")) {
1191 return;
1192 }
1193
1194 if (!strcmp(argv[0], "..")) {
1195 if (!strcmp(curdir, "/")) {
1196 return;
1197 }
1198 char *p = strrchr(curdir, '/');
1199 if (!p) {
1200 strcpy(curdir, "/");
1201 return;
1202 }
1203 if (p == curdir) {
1204 *(p+1) = '\0';
1205 } else {
1206 *p = '\0';
1207 }
1208 return;
1209 }
1210
1211 char* path = get_realpath(argv[0]);
1212 int is_dir = 0;
1213 plist_t info = NULL;
1214 afc_error_t err = afc_get_file_info_plist(afc, path, &info);
1215 if (err == AFC_E_SUCCESS && info) {
1216 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1217 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1218 plist_free(info);
1219 } else {
1220 printf("Error: Failed to get file info for %s: %s (%d)\n", path, afc_strerror(err), err);
1221 free(path);
1222 return;
1223 }
1224
1225 if (!is_dir) {
1226 printf("Error: '%s' is not a valid directory\n", path);
1227 free(path);
1228 return;
1229 }
1230
1231 free(curdir);
1232 curdir = path;
1233 curdir_len = strlen(curdir);
1234}
1235
1236static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline)
1237{
1238 char **argv = NULL;
1239 int argc = 0;
1240 size_t maxlen = strlen(cmdline);
1241 const char* pos = cmdline;
1242 const char* qpos = NULL;
1243 char *tmpbuf = NULL;
1244 int tmplen = 0;
1245 int is_error = 0;
1246
1247 /* skip initial whitespace */
1248 while (isspace(*pos)) pos++;
1249 maxlen -= (pos - cmdline);
1250
1251 tmpbuf = (char*)malloc(maxlen+1);
1252
1253 while (!is_error) {
1254 if (*pos == '\\') {
1255 pos++;
1256 switch (*pos) {
1257 case '"':
1258 case '\'':
1259 case '\\':
1260 case ' ':
1261 tmpbuf[tmplen++] = *pos;
1262 pos++;
1263 break;
1264 default:
1265 printf("Error: Invalid escape sequence\n");
1266 is_error++;
1267 break;
1268 }
1269 } else if (*pos == '"' || *pos == '\'') {
1270 if (!qpos) {
1271 qpos = pos;
1272 } else {
1273 qpos = NULL;
1274 }
1275 pos++;
1276 } else if (*pos == '\0' || (!qpos && isspace(*pos))) {
1277 tmpbuf[tmplen] = '\0';
1278 if (*pos == '\0' && qpos) {
1279 printf("Error: Unmatched `%c`\n", *qpos);
1280 is_error++;
1281 break;
1282 }
1283 char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*));
1284 if (new_argv == NULL) {
1285 printf("Error: Out of memory?!\n");
1286 is_error++;
1287 break;
1288 }
1289 argv = new_argv;
1290 /* shrink buffer to actual argument size */
1291 argv[argc] = (char*)realloc(tmpbuf, tmplen+1);
1292 if (!argv[argc]) {
1293 printf("Error: Out of memory?!\n");
1294 is_error++;
1295 break;
1296 }
1297 argc++;
1298 tmpbuf = NULL;
1299 if (*pos == '\0') {
1300 break;
1301 }
1302 maxlen -= tmplen;
1303 tmpbuf = (char*)malloc(maxlen+1);
1304 tmplen = 0;
1305 while (isspace(*pos)) pos++;
1306 } else {
1307 tmpbuf[tmplen++] = *pos;
1308 pos++;
1309 }
1310 }
1311 if (tmpbuf) {
1312 free(tmpbuf);
1313 }
1314 if (is_error) {
1315 int i;
1316 for (i = 0; argv && i < argc; i++) free(argv[i]);
1317 free(argv);
1318 return;
1319 }
1320
1321 *p_argv = argv;
1322 *p_argc = argc;
1323}
1324
1325static int process_args(afc_client_t afc, int argc, char** argv)
1326{
1327 if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
1328 return -1;
1329 }
1330 else if (!strcmp(argv[0], "help")) {
1331 handle_help(afc, argc, argv);
1332 }
1333 else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) {
1334 handle_devinfo(afc, argc-1, argv+1);
1335 }
1336 else if (!strcmp(argv[0], "info")) {
1337 handle_file_info(afc, argc-1, argv+1);
1338 }
1339 else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) {
1340 handle_list(afc, argc-1, argv+1);
1341 }
1342 else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) {
1343 handle_rename(afc, argc-1, argv+1);
1344 }
1345 else if (!strcmp(argv[0], "mkdir")) {
1346 handle_mkdir(afc, argc-1, argv+1);
1347 }
1348 else if (!strcmp(argv[0], "ln")) {
1349 handle_link(afc, argc-1, argv+1);
1350 }
1351 else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) {
1352 handle_remove(afc, argc-1, argv+1);
1353 }
1354 else if (!strcmp(argv[0], "get")) {
1355 handle_get(afc, argc-1, argv+1);
1356 }
1357 else if (!strcmp(argv[0], "put")) {
1358 handle_put(afc, argc-1, argv+1);
1359 }
1360 else if (!strcmp(argv[0], "pwd")) {
1361 handle_pwd(afc, argc-1, argv+1);
1362 }
1363 else if (!strcmp(argv[0], "cd")) {
1364 handle_cd(afc, argc-1, argv+1);
1365 }
1366 else {
1367 printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]);
1368 }
1369 return 0;
1370}
1371
1372static void start_cmdline(afc_client_t afc)
1373{
1374 while (!stop_requested) {
1375 int argc = 0;
1376 char **argv = NULL;
1377 char prompt[128];
1378 int plen = curdir_len;
1379 char *ppath = curdir;
1380 int plim = (int)(sizeof(prompt)/2)-8;
1381 if (plen > plim) {
1382 ppath = curdir + (plen - plim);
1383 plen = plim;
1384 }
1385 snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath);
1386#ifdef HAVE_READLINE
1387 char* cmd = readline(prompt);
1388 if (!cmd || !*cmd) {
1389 free(cmd);
1390 continue;
1391 }
1392 add_history(cmd);
1393 parse_cmdline(&argc, &argv, cmd);
1394#else
1395 char cmdbuf[4096];
1396 printf("%s", prompt);
1397 fflush(stdout);
1398 get_input(cmdbuf, sizeof(cmdbuf));
1399 parse_cmdline(&argc, &argv, cmdbuf);
1400#endif
1401#ifdef HAVE_READLINE
1402 free(cmd);
1403#endif
1404 /* process arguments */
1405 if (argv && argv[0]) {
1406 if (process_args(afc, argc, argv) < 0) {
1407 break;
1408 }
1409 }
1410 }
1411}
1412
1413static void device_event_cb(const idevice_event_t* event, void* userdata)
1414{
1415 if (use_network && event->conn_type != CONNECTION_NETWORK) {
1416 return;
1417 } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
1418 return;
1419 }
1420 if (event->event == IDEVICE_DEVICE_ADD) {
1421 if (!udid) {
1422 udid = strdup(event->udid);
1423 }
1424 if (strcmp(udid, event->udid) == 0) {
1425 connected = 1;
1426 }
1427 } else if (event->event == IDEVICE_DEVICE_REMOVE) {
1428 if (strcmp(udid, event->udid) == 0) {
1429 connected = 0;
1430 printf("\n[disconnected]\n");
1431 handle_signal(SIGINT);
1432 }
1433 }
1434}
1435
1436int main(int argc, char** argv)
1437{
1438 const char* appid = NULL;
1439 int ret = 0;
1440 idevice_t device = NULL;
1441 lockdownd_client_t lockdown = NULL;
1442 lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
1443 lockdownd_service_descriptor_t service = NULL;
1444 afc_client_t afc = NULL;
1445 house_arrest_client_t house_arrest = NULL;
1446 const char* service_name = AFC_SERVICE_NAME;
1447 int use_container = 0;
1448
1449 int c = 0;
1450 const struct option longopts[] = {
1451 { "udid", required_argument, NULL, 'u' },
1452 { "network", no_argument, NULL, 'n' },
1453 { "help", no_argument, NULL, 'h' },
1454 { "debug", no_argument, NULL, 'd' },
1455 { "version", no_argument, NULL, 'v' },
1456 { "documents", required_argument, NULL, OPT_DOCUMENTS },
1457 { "container", required_argument, NULL, OPT_CONTAINER },
1458 { NULL, 0, NULL, 0}
1459 };
1460
1461 signal(SIGTERM, handle_signal);
1462#ifndef _WIN32
1463 signal(SIGQUIT, handle_signal);
1464 signal(SIGPIPE, SIG_IGN);
1465#endif
1466
1467 while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) {
1468 switch (c) {
1469 case 'd':
1470 idevice_set_debug_level(1);
1471 break;
1472 case 'u':
1473 if (!*optarg) {
1474 fprintf(stderr, "ERROR: UDID must not be empty!\n");
1475 print_usage(argc, argv, 1);
1476 return 2;
1477 }
1478 udid = strdup(optarg);
1479 break;
1480 case 'n':
1481 use_network = 1;
1482 break;
1483 case 'h':
1484 print_usage(argc, argv, 0);
1485 return 0;
1486 case 'v':
1487 printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
1488#ifdef HAVE_READLINE
1489 printf(" (readline)");
1490#endif
1491 printf("\n");
1492 return 0;
1493 case OPT_DOCUMENTS:
1494 if (!*optarg) {
1495 fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n");
1496 print_usage(argc, argv, 1);
1497 return 2;
1498 }
1499 appid = optarg;
1500 use_container = 0;
1501 break;
1502 case OPT_CONTAINER:
1503 if (!*optarg) {
1504 fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n");
1505 print_usage(argc, argv, 1);
1506 return 2;
1507 }
1508 appid = optarg;
1509 use_container = 1;
1510 break;
1511 default:
1512 print_usage(argc, argv, 1);
1513 return 2;
1514 }
1515 }
1516
1517 argc -= optind;
1518 argv += optind;
1519
1520 int num = 0;
1521 idevice_info_t *devices = NULL;
1522 idevice_get_device_list_extended(&devices, &num);
1523 int count = 0;
1524 for (int i = 0; i < num; i++) {
1525 if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) {
1526 count++;
1527 } else if (devices[i]->conn_type == CONNECTION_USBMUXD) {
1528 count++;
1529 }
1530 }
1531 idevice_device_list_extended_free(devices);
1532 if (count == 0) {
1533 fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
1534 return 1;
1535 }
1536
1537 idevice_events_subscribe(&context, device_event_cb, NULL);
1538
1539 while (!connected && !stop_requested) {
1540#ifdef _WIN32
1541 Sleep(100);
1542#else
1543 usleep(100000);
1544#endif
1545 }
1546 if (stop_requested) {
1547 return 0;
1548 }
1549
1550 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
1551 if (ret != IDEVICE_E_SUCCESS) {
1552 if (udid) {
1553 fprintf(stderr, "ERROR: Device %s not found!\n", udid);
1554 } else {
1555 fprintf(stderr, "ERROR: No device found!\n");
1556 }
1557 return 1;
1558 }
1559
1560 do {
1561 if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
1562 fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
1563 ret = 1;
1564 break;
1565 }
1566
1567 if (appid) {
1568 service_name = HOUSE_ARREST_SERVICE_NAME;
1569 }
1570
1571 ldret = lockdownd_start_service(lockdown, service_name, &service);
1572 if (ldret != LOCKDOWN_E_SUCCESS) {
1573 fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret);
1574 ret = 1;
1575 break;
1576 }
1577
1578 if (appid) {
1579 house_arrest_client_new(device, service, &house_arrest);
1580 if (!house_arrest) {
1581 fprintf(stderr, "Could not start document sharing service!\n");
1582 ret = 1;
1583 break;
1584 }
1585
1586 if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) {
1587 fprintf(stderr, "Could not send house_arrest command!\n");
1588 ret = 1;
1589 break;
1590 }
1591
1592 plist_t dict = NULL;
1593 if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) {
1594 fprintf(stderr, "Could not get result from document sharing service!\n");
1595 break;
1596 }
1597 plist_t node = plist_dict_get_item(dict, "Error");
1598 if (node) {
1599 char *str = NULL;
1600 plist_get_string_val(node, &str);
1601 fprintf(stderr, "ERROR: %s\n", str);
1602 if (str && !strcmp(str, "InstallationLookupFailed")) {
1603 fprintf(stderr, "The App '%s' is either not present on the device, or the 'UIFileSharingEnabled' key is not set in its Info.plist. Starting with iOS 8.3 this key is mandatory to allow access to an app's Documents folder.\n", appid);
1604 }
1605 free(str);
1606 plist_free(dict);
1607 break;
1608 }
1609 plist_free(dict);
1610 afc_client_new_from_house_arrest_client(house_arrest, &afc);
1611 } else {
1612 afc_client_new(device, service, &afc);
1613 }
1614 lockdownd_service_descriptor_free(service);
1615 lockdownd_client_free(lockdown);
1616 lockdown = NULL;
1617
1618 curdir = strdup("/");
1619 curdir_len = 1;
1620
1621 if (argc > 0) {
1622 // command line mode
1623 process_args(afc, argc, argv);
1624 } else {
1625 // interactive mode
1626 start_cmdline(afc);
1627 }
1628
1629 } while (0);
1630
1631 if (afc) {
1632 afc_client_free(afc);
1633 }
1634 if (lockdown) {
1635 lockdownd_client_free(lockdown);
1636 }
1637 idevice_free(device);
1638
1639 return ret;
1640}